1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
#!/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
# 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 <Alt-k> 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, 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
# graphical classes and functions
print("Loading graphics...")
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 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):
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("<Alt-k>", something)
# PASSES 2 params when expecting 1 master.bind_all("<Alt-k>", self.buttonLock.invoke)
master.bind_all("<Alt-k>", 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.buttonLogout = Button(frame, text="Logout", underline=0, command=lambda: actions.logout(config), image=self.photoLogout, compound=LEFT)
master.bind_all("<Alt-l>", 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)
master.bind_all("<Alt-h>", partial(actions.hibernate,config))
self.buttonShutdown = Button(frame, text="Shutdown", underline=0, command=lambda: actions.shutdown(config))
self.buttonShutdown.grid(row=0,column=3)
master.bind_all("<Alt-s>", partial(actions.shutdown,config))
self.buttonReboot = Button(frame, text="Reboot", underline=0, command=lambda: actions.reboot(config))
self.buttonReboot.grid(row=0,column=4)
master.bind_all("<Alt-r>", 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("<Alt-c>", 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 = PhotoImage(file=os.path.join("/usr/share/icons/Adwaita/24x24/actions","system-log-out.png"))
root.tk.call('wm','iconphoto', root._w, imgicon)
app = App(root)
root.mainloop()
try:
root.destroy()
except:
pass
|