#!/usr/bin/env python3 # File: logout-manager.py # License: CC-BY-SA 4.0 # Author: bgstack15 # Startdate: 2019-06-01 # Title: GTK3 based logout manager # Purpose: Primarily for fluxbox, this tool provides a graphical menu for various session control commands like shutdown, logout, and reboot # History: # Usage: # This is a bit for reference, but also to provide myself a little shutdown options menu, like xfce4, because fluxbox doesn't really provide one. # Reference: # https://www.linuxquestions.org/questions/slackware-14/how-do-i-run-menu-and-logout-from-the-command-line-in-fluxbox-864919/ # /mnt/public/work/python/hotplug2/ # icon handling https://python-gtk-3-tutorial.readthedocs.io/en/latest/iconview.html # accelerator keys https://askubuntu.com/questions/655452/python-gtk3-keyboard-accelerators # gtk3 widget signals https://developer.gnome.org/gtk3/unstable/GtkWidget.html#GtkWidget-button-press-event # /usr/share/wicd/gtk/gui.py netentry.py wicd.ui # combined with next ref: scale down valid icon https://stackoverflow.com/questions/42800482/how-to-set-size-of-a-gtk-image-in-python # https://stackoverflow.com/questions/6090241/how-can-i-get-the-full-file-path-of-an-icon-name # use custom icon theme https://lazka.github.io/pgi-docs/Gtk-3.0/classes/IconTheme.html#Gtk.IconTheme.set_custom_theme # Improve: # actually execute the commands # only show debug info when DEBUG=1 or similar. # support global conf file, and user conf file # far future: provide graphical way to change commands run # Documentation: import gi, warnings, configparser gi.require_version("Gtk","3.0") from gi.repository import Gtk from gi.repository import Gdk from gi.repository.GdkPixbuf import Pixbuf from pathlib import Path logout_manager_version="2019-06-09a" application_icon=("system-log-out") # graphical classes and functions def get_scaled_icon(icon_name, size=24, fallback_icon_name = "", icon_theme = "default"): # return a Gtk.Image.new_from_pixbuf # ripped from https://stackoverflow.com/questions/42800482/how-to-set-size-of-a-gtk-image-in-python and combined with https://stackoverflow.com/questions/6090241/how-can-i-get-the-full-file-path-of-an-icon-name # further ref for lookup_icon function: https://lazka.github.io/pgi-docs/Gtk-3.0/flags.html#Gtk.IconLookupFlags # if a file exists by the specific name, use it. if Path(icon_name).is_file(): iconfilename = icon_name else: if icon_theme != "default": this_theme = Gtk.IconTheme.new() this_theme.set_custom_theme(icon_theme) else: this_theme = Gtk.IconTheme.get_default() try: icon_info = this_theme.lookup_icon(icon_name, size, 0) iconfilename = icon_info.get_filename() except: icon_info = this_theme.lookup_icon(fallback_icon_name, size, 0) iconfilename = icon_info.get_filename() #print(iconfilename) return Gtk.Image.new_from_pixbuf(Pixbuf.new_from_file_at_scale( filename=iconfilename, width=size, height=size, preserve_aspect_ratio=True)) class MainWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="Log out options") # for window icon liststore = Gtk.ListStore(Pixbuf, str) iconview = Gtk.IconView.new() iconview.set_model(liststore) iconview.set_pixbuf_column(0) iconview.set_text_column(1) pixbuf24 = Gtk.IconTheme.get_default().load_icon(application_icon, 24, 0) pixbuf32 = Gtk.IconTheme.get_default().load_icon(application_icon, 32, 0) pixbuf48 = Gtk.IconTheme.get_default().load_icon(application_icon, 48, 0) pixbuf64 = Gtk.IconTheme.get_default().load_icon(application_icon, 64, 0) pixbuf96 = Gtk.IconTheme.get_default().load_icon(application_icon, 96, 0) self.set_icon_list([pixbuf24, pixbuf32, pixbuf48, pixbuf64, pixbuf96]); # accel is for when you are not using the "set_use_underline" function. #accel = Gtk.AccelGroup() #accel.connect(Gdk.keyval_from_name('D'), Gdk.ModifierType.MOD1_MASK, 0, self.on_button2_accel) #self.add_accel_group(accel) # buttons self.grid = Gtk.Grid() self.add(self.grid) self.button0 = Gtk.Button(label="Loc_k") self.button0.connect("button-press-event", self.on_button0_press_event) self.button0.connect("activate", self.on_button0_press_event) # activate covers ALT+L action and spacebar when selected self.buttonicon0 = get_scaled_icon(config.get_lock_icon(), config.get_icon_size(),config.get_lock_fallback_icon(), config.get_icon_theme()) self.button0.set_image(self.buttonicon0) self.button0.set_tooltip_text("Hide session and require authentication to return to it") self.button0.set_always_show_image(True) self.button0.set_use_underline(True) self.grid.add(self.button0) self.button1 = Gtk.Button(label="_Logout") self.button1.connect("button-press-event", self.on_button1_press_event) self.button1.connect("activate", self.on_button1_press_event) # activate covers ALT+L action and spacebar when selected self.buttonicon1 = get_scaled_icon(config.get_logout_icon(), config.get_icon_size(),config.get_logout_fallback_icon(), config.get_icon_theme()) self.button1.set_image(self.buttonicon1) self.button1.set_tooltip_text("Close the current user session") self.button1.set_always_show_image(True) self.button1.set_use_underline(True) self.grid.add(self.button1) self.buttonHibernate = Gtk.Button(label="_Hibernate") self.buttonHibernate.connect("button-press-event", self.on_buttonHibernate_press_event) self.buttonHibernate.connect("activate", self.on_buttonHibernate_press_event) # activate covers ALT+L action and spacebar when selected #self.buttoniconHibernate = Gtk.Image() #self.buttoniconHibernate.set_from_icon_name("system-hibernate",24) self.buttoniconHibernate = get_scaled_icon(config.get_hibernate_icon(), config.get_icon_size(),config.get_hibernate_fallback_icon(), config.get_icon_theme()) self.buttonHibernate.set_image(self.buttoniconHibernate) self.buttonHibernate.set_tooltip_text("Save state to disk and power off") self.buttonHibernate.set_always_show_image(True) self.buttonHibernate.set_use_underline(True) self.buttonHibernate.set_sensitive(True if config.get_can_hibernate() else False) self.grid.add(self.buttonHibernate) self.button2 = Gtk.Button(label="_Shutdown") self.button2.connect("button-press-event", self.on_button2_press_event) self.button2.connect("activate", self.on_button2_accel) # unnecessary because the "activate" suffices above. #self.button2.connect("mnemonic-activate", self.on_button2_accel) self.buttonicon2 = Gtk.Image() self.buttonicon2 = get_scaled_icon(config.get_shutdown_icon(), config.get_icon_size(), config.get_shutdown_fallback_icon(), config.get_icon_theme()) self.button2.set_image(self.buttonicon2) self.button2.set_tooltip_text("Power off the computer") self.button2.set_always_show_image(True) self.button2.set_use_underline(True) self.grid.add(self.button2) self.button3 = Gtk.Button(label="_Reboot") self.button3.connect("button-press-event", self.on_button3_press_event) self.button3.connect("activate", self.on_button3_press_event) self.buttonicon3 = Gtk.Image() self.buttonicon3 = get_scaled_icon(config.get_reboot_icon(), config.get_icon_size(), config.get_reboot_fallback_icon(), config.get_icon_theme()) self.button3.set_image(self.buttonicon3) self.button3.set_tooltip_text("Reboot the computer back to the login screen") self.button3.set_always_show_image(True) self.button3.set_use_underline(True) self.grid.add(self.button3) self.button4 = Gtk.Button(label="_Cancel") self.button4.connect("button-press-event", self.on_button4_press_event) self.button4.connect("activate", self.on_button4_press_event) self.button4.set_tooltip_text("Do nothing; just close this window") self.button4.set_use_underline(True) self.grid.attach(self.button4,0,1,8,1) # hibernate button def on_buttonHibernate_press_event(self, *args): self.do_hibernate(self.buttonHibernate) # lock button def on_button0_press_event(self, *args): self.do_lock(self.button0) # logout button def on_button1_press_event(self, *args): self.do_logout(self.button1) # shutdown button def on_button2_press_event(self, widget, event): # check if left or right click if event.type == Gdk.EventType.BUTTON_PRESS: if event.button == 1: self.do_shutdown(widget) # eventbutton == 3 is the right-click, and its reference is my hello3.py #elif event.button == 3: # self.on_button1_right_clicked(widget) # global accelerator key, when not using the set_use_underline function ## shutdown button from accelerator key #def on_button2_accel(self, *args): # self.do_shutdown(self.button2) # accelerator key from set_use_underline function # shutdown button from accelerator key def on_button2_accel(self, *args): self.do_shutdown(self.button2) # reboot button def on_button3_press_event(self, *args): self.do_reboot(self.button3) # cancel button def on_button4_press_event(self, *args): self.cancel(self.button4) def do_shutdown(self, *args): #print(dir(self.props)) Actions.shutdown() def do_hibernate(self, widget): Actions.hibernate() def do_lock(self, widget): Actions.lock() def do_logout(self, widget): Actions.logout() def do_reboot(self, widget): Actions.reboot() def cancel(self, widget): print("Cancel any logout action.") Gtk.main_quit() class Actions: @staticmethod def hibernate(): #print("need to run the /sys/power/state trick, if available") print(config.get_hibernate_command()) @staticmethod def lock(): #print("please lock the screen.") print(config.get_lock_command()) @staticmethod def logout(): #print("please log out of current session!") print(config.get_logout_command()) @staticmethod def reboot(): #print("please reboot.") print(config.get_reboot_command()) @staticmethod def shutdown(): #print("please shut yourself down!") print(config.get_shutdown_command()) class Config: def __init__(self): # load defaults which can be overwritten self.hibernate_command = "" self.lock_command = "" self.logout_command = "" self.reboot_command = "" self.shutdown_command = "" self.hibernate_icon = "system-hibernate" self.hibernate_fallback_icon = "system-hibernate" self.lock_icon = "system-lock-screen" self.lock_fallback_icon = "system-lock-screen" self.logout_icon = "system-log-out" self.logout_fallback_icon = "system-log-out" self.reboot_icon = "system-reboot" self.reboot_fallback_icon = "system-reboot" self.shutdown_icon = "system-shutdown" self.shutdown_fallback_icon = "system-shutdown" self.icon_size = 24 self.icon_theme = "default" self.can_hibernate = False def set_hibernate_command(self,hibernate_command): self.hibernate_command = hibernate_command def set_lock_command(self,lock_command): self.lock_command = lock_command def set_logout_command(self,logout_command): self.logout_command = logout_command def set_reboot_command(self,reboot_command): self.reboot_command = reboot_command def set_shutdown_command(self,shutdown_command): self.shutdown_command = shutdown_command def set_hibernate_icon(self,hibernate_icon): self.hibernate_icon = hibernate_icon def set_lock_icon(self,lock_icon): self.lock_icon = lock_icon def set_logout_icon(self,logout_icon): self.logout_icon = logout_icon def set_reboot_icon(self,reboot_icon): self.reboot_icon = reboot_icon def set_shutdown_icon(self,shutdown_icon): self.shutdown_icon = shutdown_icon def set_icon_size(self,icon_size): self.icon_size = int(icon_size) def set_icon_theme(self,icon_theme): self.icon_theme = icon_theme def set_can_hibernate(self,can_hibernate): print("Setting can_hibernate:",can_hibernate) self.can_hibernate = bool(can_hibernate) def get_hibernate_command(self): return self.hibernate_command def get_lock_command(self): return self.lock_command def get_logout_command(self): return self.logout_command def get_reboot_command(self): return self.reboot_command def get_shutdown_command(self): return self.shutdown_command def get_hibernate_icon(self): return self.hibernate_icon def get_lock_icon(self): return self.lock_icon def get_logout_icon(self): return self.logout_icon def get_reboot_icon(self): return self.reboot_icon def get_shutdown_icon(self): return self.shutdown_icon def get_hibernate_fallback_icon(self): return self.hibernate_fallback_icon def get_lock_fallback_icon(self): return self.lock_fallback_icon def get_logout_fallback_icon(self): return self.logout_fallback_icon def get_reboot_fallback_icon(self): return self.reboot_fallback_icon def get_shutdown_fallback_icon(self): return self.shutdown_fallback_icon def get_icon_size(self): return self.icon_size def get_icon_theme(self): return self.icon_theme def get_can_hibernate(self): return self.can_hibernate # Read config config_in = configparser.ConfigParser() config_in.read('logout-manager.conf') config = Config() try: ci = config_in['logout-manager'] except: # no definition print("Using default commands") try: ci_icons = config_in['icons'] except: # no definition print("Using default icons") # load up our custom class, which stores the defaults in case we do not set them here if 'hibernate_command' in ci: config.set_hibernate_command(ci['hibernate_command']) if 'lock_command' in ci: config.set_lock_command(ci['lock_command']) if 'logout_command' in ci: config.set_logout_command(ci['logout_command']) if 'reboot_command' in ci: config.set_reboot_command(ci['reboot_command']) if 'shutdown_command' in ci: config.set_shutdown_command(ci['shutdown_command']) if 'hibernate' in ci_icons: config.set_hibernate_icon(ci_icons['hibernate']) if 'lock' in ci_icons: config.set_lock_icon(ci_icons['lock']) if 'logout' in ci_icons: config.set_logout_icon(ci_icons['logout']) if 'reboot' in ci_icons: config.set_reboot_icon(ci_icons['reboot']) if 'shutdown' in ci_icons: config.set_shutdown_icon(ci_icons['shutdown']) if 'size' in ci_icons: config.set_icon_size(ci_icons['size']) if 'theme' in ci_icons: config.set_icon_theme(ci_icons['theme']) # store the info about if hibernate is an option can_hibernate = False try: with open('/sys/power/state') as r: line = r.read() if 'disk' in line: can_hibernate = True except: pass config.set_can_hibernate(can_hibernate) # DEBUG, raw from conf file and system status print("Raw values:") for item in config_in.sections(): print("["+item+"]") for key in config_in[item]: print(key+" = "+config_in[item][key]) print("Can hibernate:",can_hibernate) # DEBUG, stored values print("Stored values:") print(config.get_hibernate_command()) print(config.get_lock_command()) print(config.get_logout_command()) print(config.get_reboot_command()) print(config.get_shutdown_command()) print(config.get_hibernate_icon()) print(config.get_lock_icon()) print(config.get_logout_icon()) print(config.get_reboot_icon()) print(config.get_shutdown_icon()) print(config.get_icon_size()) print(config.get_icon_theme()) print("Can hibernate:",config.get_can_hibernate()) # MAIN LOOP win = MainWindow() win.connect("destroy", Gtk.main_quit) win.show_all() Gtk.main()