#!/usr/bin/env python3 # File: logout-manager-tcl.py # License: CC-BY-SA 4.0 # Author: bgstack15 # Startdate: 2019-06-12 20:05 # Title: Tcl/tk-based logout manager # Purpose: A tcl/tk graphical program for selecting shutdown, logout, etc. # History: # Usage: # References: # http://effbot.org/tkinterbook/button.htm # http://effbot.org/tkinterbook/tkinter-application-windows.htm # http://effbot.org/tkinterbook/ # pass parameters to function of Button(command=) https://stackoverflow.com/questions/38749620/python-3-tkinter-button-commands#38750155 # alternate for passing params https://stackoverflow.com/questions/6920302/how-to-pass-arguments-to-a-button-command-in-tkinter # https://stackoverflow.com/questions/18537918/set-window-icon#18538416 # the exact syntax for master.bind https://stackoverflow.com/questions/16082243/how-to-bind-ctrl-in-python-tkinter # https://pillow.readthedocs.io/en/stable/reference/ImageTk.html # gtk-3.0 default icon theme https://coderwall.com/p/no3qfa/setting-gtk2-and-gtk3-theme-via-config-file # homedir https://stackoverflow.com/questions/4028904/how-to-get-the-home-directory-in-python # natural sort https://stackoverflow.com/questions/46228101/sort-list-of-strings-by-two-substrings-using-lambda-function/46228199#46228199 # Improve: # add tooltips # add svg support # Dependencies: # python36-tkinter | python3-tk # python36-pillow-tk | python3-pil.imagetk import os, sys, configparser, glob, re from tkinter import * from functools import partial from pathlib import Path from PIL import Image, ImageTk sys.path.append("/home/bgirton/dev/logout-manager") import lmlib #if TkVersion < 8.6: # USE_PRIVATE_TCL_IMAGES = 1 # print("Loading private PIL translation layter") # def PhotoImage_from_png(file): # return ImageTk.PhotoImage(file=file) config = lmlib.Initialize_config() actions = lmlib.Actions # graphical classes and functions print("Loading graphics...") def get_gtk3_default_theme(): # abstracted so it does not clutter get_scaled_icon name = "hicolor" gtk3_config_path = os.path.join(os.path.expanduser("~"),".config","gtk-3.0","settings.ini") gtk3_config = configparser.ConfigParser() gtk3_config.read(gtk3_config_path) try: if 'Settings' in gtk3_config: name = gtk3_config['Settings']['gtk-icon-theme-name'] elif 'settings' in gtk3_config: name = gtk3_config['settings']['gtk-icon-theme-name'] except: # supposed failsafe name = "hicolor" return name def tryint(s): try: return int(s) except: return s def sort_sizes(x): # I don't even know how this works. I mashed it together from so#46228101 # WORKS return [tryint(c) for c in re.split('([0-9]+)',x.split("/")[5])] value = x.split("/")[5] #return int(re.split("[^0-9]+",value)[0]) * 100 + int( re.split("[^0-9]+",value)[1] if len(value.split("@")) >= 2 else 0 ) return mynum(value, "all") def mynum(x, type = "all"): # return the complicated numerical value for the weird size options f = re.split("[^0-9]+",x) if type == "all": return int(f[0]) * 100 + int( f[1] if len(f) >= 2 else 0 ) else: return int(f[0]) def find_best_size_match(size, thelist): # return item from sorted thelist whose split("/")[5] is the first to meet or exceed the requested size try: default = thelist[-1] except: default = None return next(( i for i in thelist if mynum(i.split("/")[5],"real") >= size ), default) def get_filename_of_icon(name, theme = "hicolor", size = 48, category = "actions"): # poor man's attempt at walking through fd.o icon theme filename = None # example: Adwaita system-log-out if theme == "default" or theme is None: theme = "hicolor" # first, find all files underneath /usr/share/icons/$THEME/$SIZE print("Finding filename of icon, theme=",theme,"category=",category,"name=",name) # WORKHERE: this glob affects if scalable/ dir is included. When support for svg is added (which is beyond PIL), need to add a conditional and use a second glob if USE_SVG=1. results = glob.glob("/usr/share/icons/"+theme+"/*[0-9]*/"+category+"/"+name+".*") # the sort arranges it so a Numix/24 dir comes before a Numix/24@2x dir results = sorted(results, key=sort_sizes) #print(results) # now find the first one that matches filename = find_best_size_match(size,results) return filename def get_scaled_icon(icon_name, size = 24, fallback_icon_name = "", icon_theme = "default"): iconfilename = None # if name is a specific filename, just use it. if Path(icon_name).is_file(): #print("This is a file:",icon_name) iconfilename = icon_name else: if icon_theme == "default": # retrieve default theme from config file #print("Discovering default icon theme...") icon_theme = get_gtk3_default_theme() # so now that icon_theme is defined, let us go find the icon that matches the requested name and size, in the actions category #print("Using icon theme",icon_theme) iconfilename = get_filename_of_icon(name=icon_name, theme=icon_theme, size=size, category=config.get_icon_category()) # So now that we think we have derived the correct filename... try: print("Trying icon file",iconfilename) photo = Image.open(iconfilename) except: print("Error with icon file.") return None # If I ever add HiDPI stuff, multiple size here by the factor. So, size * 1.25 photo.thumbnail(size=[size, size]) try: photo2 = ImageTk.PhotoImage(photo) except Exception as e: print("Error was ",e) return photo2 class App: def __init__(self, master): frame = Frame(master) frame.grid(row=0) self.buttonLock = Button(frame, text="Lock", underline=3, command=partial(actions.lock,config)) self.buttonLock.grid(row=0,column=0) # WORKS master.bind_all("", something) # PASSES 2 params when expecting 1 master.bind_all("", self.buttonLock.invoke) master.bind_all("", partial(actions.lock,config)) # WORKS, for basic image loading. #self.photoLogout = get_scaled_icon("/usr/share/icons/Adwaita/48x48/actions/system-log-out.png") #self.buttonLogout = Button(frame, text="Logout", underline=0, command=lambda: actions.logout(config), image=self.photoLogout, compound=TOP) #self.photoLogout1 = Image.open("/usr/share/icons/Adwaita/48x48/actions/system-log-out.png") #self.photoLogout1.thumbnail(size=[24,24]) #self.photoLogout = ImageTk.PhotoImage(self.photoLogout1,size="24x24") #self.photoLogout = get_scaled_icon("/usr/share/icons/Adwaita/48x48/actions/system-log-out.png", 24) #self.photoLogout = get_scaled_icon("system-log-out", 24, icon_theme="default") self.photoLogout = get_scaled_icon(config.get_logout_icon(), config.get_icon_size(), config.get_icon_theme()) self.buttonLogout = Button(frame, text="Logout", underline=0, command=lambda: actions.logout(config), image=self.photoLogout, compound=LEFT) master.bind_all("", partial(actions.logout,config)) self.buttonLogout.grid(row=0,column=1) self.photoHibernate = get_scaled_icon(config.get_hibernate_icon(), config.get_icon_size(), config.get_icon_theme()) self.buttonHibernate = Button(frame, text="Hibernate", underline=0, command=lambda: actions.hibernate(config), image=self.photoHibernate, compound=LEFT) self.buttonHibernate.grid(row=0,column=2) master.bind_all("", partial(actions.hibernate,config)) self.photoShutdown = get_scaled_icon(config.get_shutdown_icon(), config.get_icon_size(), config.get_icon_theme()) self.buttonShutdown = Button(frame, text="Shutdown", underline=0, command=lambda: actions.shutdown(config), image=self.photoShutdown, compound=LEFT) self.buttonShutdown.grid(row=0,column=3) master.bind_all("", partial(actions.shutdown,config)) self.photoReboot = get_scaled_icon(config.get_reboot_icon(), config.get_icon_size(), config.get_icon_theme()) self.buttonReboot = Button(frame, text="Reboot", underline=0, command=lambda: actions.reboot(config), image=self.photoReboot, compound=LEFT) self.buttonReboot.grid(row=0,column=4) master.bind_all("", partial(actions.reboot,config)) #self.buttonCancel = Button(frame, text="Cancel", underline=0, command=frame.quit) self.buttonCancel = Button(frame, text="Cancel", underline=0, command=self.quitaction) self.buttonCancel.grid(row=1,columnspan=8,sticky=W+E) master.bind_all("", self.quitaction) # Found this after trial and error. def quitaction(self,b=None): print("Cancel any logout action.") root.destroy() # Left here as an example for a mster.bind_all that works. #def something(event=None): # print("Got here!") root = Tk() # MAIN LOOP root.title("Log out options") #root.iconbitmap(r'/usr/share/icons/Numix/48/actions/system-logout.svg') imgicon = get_scaled_icon(config.get_logout_icon(),24) #if USE_PRIVATE_TCL_IMAGES is None: # #imgicon = PhotoImage(file=os.path.join("/usr/share/icons/Adwaita/24x24/actions","system-log-out.png")) # imgicon = get_scaled_icon(config.get_logout_icon(),24) #else: # #imgicon = PhotoImage_from_png(file=os.path.join("/usr/share/icons/Adwaita/24x24/actions","system-log-out.png")) # imgicon = get_scaled_icon(config.get_logout_icon(),24) root.tk.call('wm','iconphoto', root._w, imgicon) app = App(root) root.mainloop() try: root.destroy() except: pass