From 8ef7f6d4d0b27f7a8b74fd7a63438342d1efe33d Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 13 Jun 2019 23:11:28 -0400 Subject: add rudimentary freedesktop.org icon theme support After much ripping off of StackOverflow, the python/tcl frontend now has the functions in place to use /usr/share/icons themes, as long as they are using png files (tcl8.6). More work is needed on tcl8.5 (no png support). SVGs are not supported, but are planned for future commits. Many functions are added here that search for the valid filenames, and sort them by size-dirname, then find the first one that meets or exceeds the requested size. --- logout-manager-tcl.py | 116 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/logout-manager-tcl.py b/logout-manager-tcl.py index c605dc0..b9d9997 100755 --- a/logout-manager-tcl.py +++ b/logout-manager-tcl.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 # Startdate: 2019-06-12 20:05 +# Dependencies: +# python36-tkinter | python3-tk +# python36-pillow-tk | python3-pil.imagetk # WORKHERE: # add icons # add full headers @@ -12,12 +15,18 @@ # 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 -import os, sys -sys.path.append("/home/bgirton/dev/logout-manager") -import lmlib +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 config = lmlib.Initialize_config() actions = lmlib.Actions @@ -25,13 +34,89 @@ actions = lmlib.Actions # graphical classes and functions print("Loading graphics...") -def icon_bitmap(name = "", theme = "default", size = "48"): - # WORKHERE: can build some tool that loops through the icon theme dirs +def get_gtk3_default_theme(): + # abstracted so it does not clutter get_scaled_icon + name = None + 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 + if type == "all": + return int(re.split("[^0-9]+",x)[0]) * 100 + int( re.split("[^0-9]+",x)[1] if len(re.split("[^0-9]+",x)) >= 2 else 0 ) + else: + return int(re.split("[^0-9]+",x)[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 + return next(( i for i in thelist if mynum(i.split("/")[5],"real") >= size ), thelist[-1]) + +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 + + # first, find all files underneath /usr/share/icons/$THEME/$SIZE + print("Finding filename of icon.") + results = glob.glob("/usr/share/icons/"+theme+"/*/"+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 the only thing that is found during the looping is a svg, return None - # if the valid file provided is svg, then try a few derivations to get a png, else return None - imgicon = PhotoImage(file=os.path.join("/usr/share/icons/Adwaita/24x24/actions","system-log-out.png")) - return imgicon + 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) + + # 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]) + photo2 = ImageTk.PhotoImage(photo) + return photo2 class App: def __init__(self, master): @@ -39,15 +124,22 @@ class App: frame.grid(row=0) self.buttonLock = Button(frame, text="Lock", underline=3, command=partial(actions.lock,config)) - #self.buttonLock.pack(side=LEFT) 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)) - self.buttonLogout = Button(frame, text="Logout", underline=0, command=lambda: actions.logout(config)) - self.buttonLogout.grid(row=0,column=1) + # 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.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.buttonHibernate = Button(frame, text="Hibernate", underline=0, command=lambda: actions.hibernate(config)) self.buttonHibernate.grid(row=0,column=2) -- cgit