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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
#!/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 <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
# 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("<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.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("<Alt-l>", 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("<Alt-h>", 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("<Alt-s>", 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("<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 = 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
|