diff options
Diffstat (limited to 'logout-manager-tcl.py')
-rwxr-xr-x | logout-manager-tcl.py | 417 |
1 files changed, 0 insertions, 417 deletions
diff --git a/logout-manager-tcl.py b/logout-manager-tcl.py deleted file mode 100755 index 127bd54..0000000 --- a/logout-manager-tcl.py +++ /dev/null @@ -1,417 +0,0 @@ -#!/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: -# logout-manager-tcl.py -# References: -# http://effbot.org/tkinterbook/button.htm -# http://effbot.org/tkinterbook/tkinter-application-windows.htm -# http://effbot.org/tkinterbook/ -# pass parameters to function of tkinter.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 -# tooltips https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter/41381685#41381685 -# Improve: -# Dependencies: -# Devuan: python3-tk python3-pil.imagetk python3-cairosvg -# el7: python36-tkinter python36-pillow-tk ( pip3 install cairosvg ) - -import glob, os, platform, re, sys -import tkinter as tk -from functools import partial -from pathlib import Path -from sys import path -from dotenv import load_dotenv -# loading PIL.ImageTk after tkinter makes ImageTk use the PIL version, which supports PNG. This is important on tcl < 8.6 (that is, el7) -from PIL import Image, ImageTk - -LM_USE_SVG = 0 -try: - from cairosvg import svg2png - LM_USE_SVG = 1 -except: - print("WARNING: Unable to import cairosvg. No svg images will be displayed.") - LM_USE_SVG = 0 - -# all this to load the libpath -try: - defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() - if 'debian' in thisplatform or 'devuan' in thisplatform: - defaultdir="/etc/default" - # load_dotenv keeps existing environment variables as higher precedent - load_dotenv(os.path.join(defaultdir,"logout-manager")) -except: - pass -if 'LOGOUT_MANAGER_LIBPATH' in os.environ: - for i in os.environ['LOGOUT_MANAGER_LIBPATH'].split(":"): - sys.path.append(i) -import lmlib - -# graphical classes and functions -print("Loading graphics...") - -class Tooltip: - - ''' - It creates a tooltip for a given widget as the mouse goes on it. - - see: - - http://stackoverflow.com/questions/3221956/ - what-is-the-simplest-way-to-make-tooltips- - in-tkinter/36221216#36221216 - - http://www.daniweb.com/programming/software-development/ - code/484591/a-tooltip-class-for-tkinter - - - Originally written by vegaseat on 2014.09.09. - - - Modified to include a delay time by Victor Zaccardo on 2016.03.25. - - - Modified - - to correct extreme right and extreme bottom behavior, - - to stay inside the screen whenever the tooltip might go out on - the top but still the screen is higher than the tooltip, - - to use the more flexible mouse positioning, - - to add customizable background color, padding, waittime and - wraplength on creation - by Alberto Vassena on 2016.11.05. - - Tested on Ubuntu 16.04/16.10, running Python 3.5.2 - - TODO: themes styles support - ''' - - def __init__(self, widget, - *, - bg='#FFFFEA', - pad=(5, 3, 5, 3), - text='widget info', - waittime=400, - wraplength=250): - - self.waittime = waittime # in miliseconds, originally 500 - self.wraplength = wraplength # in pixels, originally 180 - self.widget = widget - self.text = text - self.widget.bind("<Enter>", self.onEnter) - self.widget.bind("<Leave>", self.onLeave) - self.widget.bind("<ButtonPress>", self.onLeave) - self.bg = bg - self.pad = pad - self.id = None - self.tw = None - - def onEnter(self, event=None): - self.schedule() - - def onLeave(self, event=None): - self.unschedule() - self.hide() - - def schedule(self): - self.unschedule() - self.id = self.widget.after(self.waittime, self.show) - - def unschedule(self): - id_ = self.id - self.id = None - if id_: - self.widget.after_cancel(id_) - - def show(self): - def tip_pos_calculator(widget, label, - *, - tip_delta=(10, 5), pad=(5, 3, 5, 3)): - - w = widget - - s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight() - - width, height = (pad[0] + label.winfo_reqwidth() + pad[2], - pad[1] + label.winfo_reqheight() + pad[3]) - - mouse_x, mouse_y = w.winfo_pointerxy() - - x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1] - x2, y2 = x1 + width, y1 + height - - x_delta = x2 - s_width - if x_delta < 0: - x_delta = 0 - y_delta = y2 - s_height - if y_delta < 0: - y_delta = 0 - - offscreen = (x_delta, y_delta) != (0, 0) - - if offscreen: - - if x_delta: - x1 = mouse_x - tip_delta[0] - width - - if y_delta: - y1 = mouse_y - tip_delta[1] - height - - offscreen_again = y1 < 0 # out on the top - - if offscreen_again: - # No further checks will be done. - - # TIP: - # A further mod might automagically augment the - # wraplength when the tooltip is too high to be - # kept inside the screen. - y1 = 0 - - return x1, y1 - - bg = self.bg - pad = self.pad - widget = self.widget - - # creates a toplevel window - self.tw = tk.Toplevel(widget) - - # Leaves only the label and removes the app window - self.tw.wm_overrideredirect(True) - - win = tk.Frame(self.tw, - background=bg, - borderwidth=0) - label = tk.Label(win, - text=self.text, - justify=tk.LEFT, - background=bg, - relief=tk.SOLID, - borderwidth=0, - wraplength=self.wraplength) - - label.grid(padx=(pad[0], pad[2]), - pady=(pad[1], pad[3]), - sticky=tk.NSEW) - win.grid() - - x, y = tip_pos_calculator(widget, label) - - self.tw.wm_geometry("+%d+%d" % (x, y)) - - def hide(self): - tw = self.tw - if tw: - tw.destroy() - self.tw = None - -def tryint(s): - try: - return int(s) - except: - return s - -def sort_sizes(x): - # Original reference so#46228101 - value = x.split("/")[5] - 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) - try: - f0 = int(f[0]) - except: - f0 = 0 - try: - f1 = int(f[1]) - except: - f1 = 0 - if type == "all": - return f0 * 100 + f1 if len(f) >= 2 else 0 - else: - return f0 - -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: - try: - theme = lmlib.get_gtk3_default_icon_theme() - except: - theme = "hicolor" - - # first, find all files underneath /usr/share/icons/$THEME/$SIZE - print("Finding filename of icon, theme=",theme,"category=",category,"name=",name) - # to exclude the scalable/ contents, replace dir 5 asterisk with [0-9]* - results = [] - base_dir="/usr/share/icons/" - file_filters = ".*" - if LM_USE_SVG == 0: - file_filters = ".{png,PNG}" - # I have no idea if this is xdg icon theme compliant, but it is a valiant attempt. - # 1. try (requested) req-theme, req-category, req-name first - results = glob.glob(base_dir+theme+"/*/"+category+"/"+name+file_filters) - # 2. try req-theme, (generic) gen-category, req-name - if len(results) == 0: - # no results with that category, so try all categories - results = glob.glob(base_dir+theme+"/*/*/"+name+file_filters) - # 3. try "gnome", req-category, req-name - if len(results) == 0: - results = glob.glob(base_dir+"gnome"+"/*/"+category+"/"+name+file_filters) - # 4. try "gnome", gen-category, req-name - if len(results) == 0: - results = glob.glob(base_dir+"gnome"+"/*/*/"+name+file_filters) - # 5. try "hicolor", req-category, req-name - if len(results) == 0: - results = glob.glob(base_dir+"hicolor"+"/*/"+category+"/"+name+file_filters) - # 6. try "hicolor", gen-category, req-name - if len(results) == 0: - results = glob.glob(base_dir+"hicolor"+"/*/*/"+name+file_filters) - - # 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 photoimage_from_svg(filename = "",size = "48"): - # this one works, but does not allow me to set the size. - # this is kept as an example of how to open a svg without saving to a file. - # open svg - item = svg2png(url=filename, parent_width = size, parent_height = size) - return ImageTk.PhotoImage(data=item) - -def empty_photoimage(size=24): - photo = Image.new("RGBA",[size,size]) - return ImageTk.PhotoImage(image=photo) - -def image_from_svg(filename = "",size = "48"): - # open svg - if LM_USE_SVG == 1: - svg2png(url=filename,write_to="/tmp/lm_temp_image.png",parent_width = size,parent_height = size) - photo = Image.open("/tmp/lm_temp_image.png") - else: - photo = Image.new("RGBA",[size,size]) - return photo - -def get_scaled_icon(icon_name, size = 24, icon_theme = "default", fallback_icon_name = ""): - 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": - # this should not happen, because the Initialize_config should have checked gtk3 default value. - icon_theme = "hicolor" - # 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 we think we have derived the correct filename - try: - print("Trying icon file",iconfilename) - # try an svg - if re.compile(".*\.svg").match(iconfilename): - print("Trying svg...") - photo = image_from_svg(filename=iconfilename, size=size) - else: - photo = Image.open(iconfilename) - except Exception as f: - print("Error with icon file.") - print(f) - return empty_photoimage() - photo.thumbnail(size=[size, size]) - try: - photo = ImageTk.PhotoImage(photo) - except Exception as e: - print("Error was ",e) - # If I ever add HiDPI support, multiple size here by the factor. So, size * 1.25 - return photo - -class App: - def __init__(self, master): - frame = tk.Frame(master) - frame.grid(row=0) - - self.photoLock = get_scaled_icon(config.get_lock_icon(), config.get_icon_size(), config.get_icon_theme()) - self.buttonLock = tk.Button(frame, text="Lock", underline=3, command=partial(actions.lock,config), image=self.photoLock, compound=tk.LEFT) - master.bind_all("<Alt-k>", partial(actions.lock,config)) - Tooltip(self.buttonLock, text="Hide session and require authentication to return to it") - self.buttonLock.grid(row=0,column=0) - - self.photoLogout = get_scaled_icon(config.get_logout_icon(), config.get_icon_size(), config.get_icon_theme()) - self.buttonLogout = tk.Button(frame, text="Logout", underline=0, command=lambda: actions.logout(config), image=self.photoLogout, compound=tk.LEFT) - master.bind_all("<Alt-l>", partial(actions.logout,config)) - Tooltip(self.buttonLogout, text="Close the current user session") - 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 = tk.Button(frame, text="Hibernate", underline=0, command=lambda: actions.hibernate(config), image=self.photoHibernate, compound=tk.LEFT) - master.bind_all("<Alt-h>", partial(actions.hibernate,config)) - Tooltip(self.buttonHibernate, text="Save state to disk and power off") - self.buttonHibernate.grid(row=0,column=2) - - self.photoShutdown = get_scaled_icon(config.get_shutdown_icon(), config.get_icon_size(), config.get_icon_theme()) - self.buttonShutdown = tk.Button(frame, text="Shutdown", underline=0, command=lambda: actions.shutdown(config), image=self.photoShutdown, compound=tk.LEFT) - master.bind_all("<Alt-s>", partial(actions.shutdown,config)) - Tooltip(self.buttonShutdown, text="Power off the computer") - self.buttonShutdown.grid(row=0,column=3) - - self.photoReboot = get_scaled_icon(config.get_reboot_icon(), config.get_icon_size(), config.get_icon_theme()) - self.buttonReboot = tk.Button(frame, text="Reboot", underline=0, command=lambda: actions.reboot(config), image=self.photoReboot, compound=tk.LEFT) - master.bind_all("<Alt-r>", partial(actions.reboot,config)) - Tooltip(self.buttonReboot, text="Reboot the computer back to the login screen") - self.buttonReboot.grid(row=0,column=4) - - self.buttonCancel = tk.Button(frame, text="Cancel", underline=0, command=self.quitaction) - master.bind_all("<Alt-c>", self.quitaction) - Tooltip(self.buttonCancel, text="Do nothing; just close this window") - self.buttonCancel.grid(row=1,columnspan=8,sticky=tk.W+tk.E) - - # 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!") - -# load configs -config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) -actions = lmlib.Actions - -# MAIN LOOP -root = tk.Tk() -root.title("Log out options") -imgicon = get_scaled_icon(config.get_logout_icon(),24,config.get_icon_theme()) -root.tk.call('wm','iconphoto', root._w, imgicon) -app = App(root) -root.mainloop() -try: - root.destroy() -except: - pass |