#!/usr/bin/env python3 # File: logout-manager-trayicon # License: CC-BY-SA 4.0 # Author: bgstack15 # Startdate: 2020-03-20 # Title: Logout Manager tray icon # Purpose: An easy menu from the system tray in a panel for a window manager or desktop environment # History: # 2020-04-01 update for python 3.8 # Reference: # icon work https://stackoverflow.com/questions/45162862/how-do-i-set-an-icon-for-the-whole-application-using-pygobject # button right click must be from "button-press-event" and import Gdk https://python-gtk-3-tutorial.readthedocs.io/en/latest/menus.html # useful reference https://lazka.github.io/pgi-docs/Gtk-3.0/classes/Button.html#Gtk.Button # systray info https://github.com/PiSupply/PiJuice/blob/master/Software/Source/src/pijuice_tray.py # logout-manager-gtk # how to determine double click https://stackoverflow.com/questions/60009648/is-there-a-better-way-to-handle-double-click-in-pygobject # interactive python3 shell and help(Gdk.EventType) # https://developer.gnome.org/gtk3/unstable/GtkWidget.html#GtkWidget-button-press-event # find running processes https://thispointer.com/python-get-list-of-all-running-processes-and-sort-by-highest-memory-usage/ # send signals https://stackoverflow.com/questions/15080500/how-can-i-send-a-signal-from-a-python-program # https://docs.python.org/3.8/library/signal.html#module-signal # Dependencies: # dep-pip: psutil, distro # dep-devuan: python3-psutil, python3-distro import gi, os, re, sys, psutil, signal from distro import linux_distribution gi.require_version("Gtk","3.0") from gi.repository import Gtk from gi.repository import Gdk from dotenv import load_dotenv # all this to load the libpath try: defaultdir="/etc/sysconfig" thisplatform = linux_distribution()[0].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) sys.path.append("/usr/share/logout-manager") import lmlib def is_dryrun(): result = False try: if "DRYRUN" in os.environ and os.environ["DRYRUN"] != "": result = True except: pass return result def run_or_kill_logout_manager(): #print("Run or kill logout manager!") _lm_is_running = False lmregex = re.compile("logout-manager.*--from-trayicon") lmprintregex = re.compile("logout-manager") thisproc = None for proc in psutil.process_iter(): try: cmdline = " ".join(proc.cmdline()) #if lmprintregex.search(cmdline) != None: print("Checking \"" + cmdline + "\"") if lmregex.search(cmdline) != None: _lm_is_running = True thisproc = proc break except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass if _lm_is_running: #print("Stopping the following process.") #print(thisproc) os.kill(thisproc.pid,signal.SIGHUP) else: # start new instance #print("Please start a new instance") # this actually returns the new pid, and we could choose to signal only this pid. # but we will not at this point. newpid = os.spawnvp(os.P_NOWAIT,"logout-manager",["logout-manager","--from-trayicon"]) print("spawned",newpid) class MainIcon(Gtk.StatusIcon): def __init__(self,config,actions): Gtk.StatusIcon.__init__(self) self.config = config self.actions = actions self.set_from_icon_name(self.config.get_logout_icon()) loggedin_str = "Logged in as " + str(os.environ["USER"]) tooltiptext = loggedin_str if is_dryrun(): tooltiptext = "DRYRUN MODE: " + tooltiptext self.set_tooltip_text(tooltiptext) self.traymenu = Gtk.Menu() self.add_action_to_menu("Loc_k",self.config.get_lock_icon(),self.on_lock_menuitem) self.add_action_to_menu("_Logout",self.config.get_logout_icon(),self.on_logout_menuitem) self.add_action_to_menu("_Hibernate",self.config.get_hibernate_icon(),self.on_hibernate_menuitem) self.add_action_to_menu("_Shutdown",self.config.get_shutdown_icon(),self.on_shutdown_menuitem) self.add_action_to_menu("_Reboot",self.config.get_reboot_icon(),self.on_reboot_menuitem) # separator i = Gtk.SeparatorMenuItem.new() i.show() self.traymenu.append(i) # logged in as i = Gtk.MenuItem.new_with_label(tooltiptext) i.set_sensitive(False) i.show() self.traymenu.append(i) # hide tray icon i = Gtk.MenuItem.new_with_mnemonic("Hide _tray icon") i.show() i.connect("activate", self.exit) self.traymenu.append(i) self.connect("button-press-event", self.on_button_press_event) self.connect("popup-menu", self.show_menu) def on_button_press_event(self, b_unknown, event: Gdk.EventButton): if Gdk.EventType._2BUTTON_PRESS == event.type: run_or_kill_logout_manager() def exit(self, widget): quit() def show_menu(self, widget, event_button, event_time): self.traymenu.popup(None, None, self.position_menu, self, event_button, Gtk.get_current_event_time()) def on_lock_menuitem(self, widget): self.actions.lock(self.config) def on_logout_menuitem(self, widget): self.actions.logout(self.config) def on_hibernate_menuitem(self, widget): self.actions.hibernate(self.config) def on_shutdown_menuitem(self, widget): self.actions.shutdown(self.config) def on_reboot_menuitem(self, widget): self.actions.reboot(self.config) def add_action_to_menu(self,label_str,icon_str,function_func): i = Gtk.ImageMenuItem.new_with_mnemonic(label_str) j = Gtk.Image.new_from_icon_name(icon_str,32) j.show() i.set_image(j) i.set_always_show_image(True) i.show() i.connect("activate", function_func) self.traymenu.append(i) # load configs config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) actions = lmlib.Actions icon = MainIcon(config,actions) Gtk.main()