From bf4475bf865947110f020194d10a8cd05aa18310 Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 19 Mar 2020 15:43:41 -0400 Subject: add alternative trayicon shell script --- alt/logout-manager-trayicon.sh | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100755 alt/logout-manager-trayicon.sh diff --git a/alt/logout-manager-trayicon.sh b/alt/logout-manager-trayicon.sh new file mode 100755 index 0000000..22cca5f --- /dev/null +++ b/alt/logout-manager-trayicon.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env sh +# File: logout-manager-trayicon.sh +# License: CC-BY-SA 4.0 +# Author: bgstack15 +# Startdate: 2020-03-19 13:34 +# Title: Tray icon for logout-manager +# Purpose: To show a tray icon for logout options +# History: +# Alternative version of a trayicon that depends on mktrayicon +# Usage: +# Reference: +# keyboard-leds-trayicons +# Improve: +# Dependencies: +# raw: mktrayicon, awk, xset +# devuan: mktrayicon, mawk | gawk, x11-xserver-utils +# Documentation: +# This script works just fine. I just want to learn how to do this in python, and have icons on the menu. + +# CONFIG FILES +test -z "${LMT_GLOBAL_CONF}" && LMT_GLOBAL_CONF=/etc/logout-manager-trayicon.conf +test -z "${LMT_USER_CONF}" && LMT_USER_CONF="${HOME}/.config/logout-manager-trayicon.conf" +# also accept LMT_CONF + +# FUNCTIONS + +get_conf() { + # Ripped from framework.sh + # call: get_conf "${conffile}" + local _infile="$1" + local _tmpfile1="$( mktemp )" + sed -e 's/^\s*//;s/\s*$//;/^[#$]/d;s/\s*[^\]#.*$//;' "${_infile}" | grep -viE "^$" | while read _line ; + do + local _left="$( echo "${_line}" | cut -d'=' -f1 )" + eval "_thisval=\"\${${_left}}\"" + test -z "${_thisval}" && echo "${_line}" >> "${_tmpfile1}" + done + test -f "${_tmpfile1}" && { . "${_tmpfile1}" 1>/dev/null 2>&1 ; } + /bin/rm -rf "${_tmpfile1}" 1>/dev/null 2>&1 +} + +clean_lmt() { + { test -e "${lmicon}" && echo "q" > "${lmicon}" ; } 1>/dev/null 2>&1 & +} + +# LOAD CONFIGS +# order is important! The last one called gets precedence. +# instead of simply dot-sourcing the conf file, pass it to get_conf which only applies new values, so this process's environment is preserved +for thisconf in "${LMT_GLOBAL_CONF}" "${LMT_USER_CONF}" "${LMT_CONF}" ; +do + test -r "${thisconf}" && get_conf "${thisconf}" +done + +# DEFAULTS in case configs did not have these values +test -z "${LMT_ICON}" && LMT_ICON=logout + +# INITIALIZATION + +lmicon="/var/run/user/$( id -u )/${$}.logout-manager.icon" + +case "${1}" in + "run-program") + result="$( ps -eo 'pid,user,command:80' | grep -E 'logout-manager --from-[t]rayicon' | awk '{print $1}' )" + if test -n "${result}" + then + kill "${result}" + else + /usr/bin/logout-manager --from-trayicon + fi + ;; + *) + test "ON" = "ON" && { + mkfifo "${lmicon}" + mktrayicon "${lmicon}" & + echo "i ${LMT_ICON}" > "${lmicon}" + echo "m lock,logout-manager-cli.py lock|logout,logout-manager-cli.py logout|hibernate,logout-manager-cli.py hibernate|shutdown,logout-manager-cli.py shutdown|reboot,logout-manager-cli.py reboot|-----|Logged in as ${USER}|hide tray icon,echo 'q' > ${lmicon} ; kill -10 $$ 1>/dev/null 2>&1" > "${lmicon}" + echo "c $0 run-program" > "${lmicon}" + echo "t ${DRYRUN:+DRYRUN MODE: }Logged in as $USER" > "${lmicon}" + } + ;; +esac + +trap 'trap "" 2 10 ; clean_lmt' 2 10 # CTRL-C SIGUSR1 -- cgit From 9938ce0de63ae8ea780daed3d6551832cb48c96b Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 20 Mar 2020 15:27:14 -0400 Subject: add lm trayicon, and fix #1 Add program, its menu entry, and xdg autostart entry (disabled) Fix #1: cli executes valid command but still shows help message --- src/Makefile | 6 +- .../xdg/autostart/logout-manager-trayicon.desktop | 13 ++ src/usr/bin/logout-manager-cli.py | 1 + src/usr/bin/logout-manager-trayicon | 163 +++++++++++++++++++++ .../applications/logout-manager-trayicon.desktop | 12 ++ 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/etc/xdg/autostart/logout-manager-trayicon.desktop create mode 100755 src/usr/bin/logout-manager-trayicon create mode 100644 src/usr/share/applications/logout-manager-trayicon.desktop diff --git a/src/Makefile b/src/Makefile index 96607d2..010b71e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,7 +18,7 @@ # Dependencies: APPNAME = logout-manager -APPVERSION = 0.0.1 +APPVERSION = 0.0.2 SRCDIR = $(CURDIR) prefix = /usr SYSCONFDIR = $(DESTDIR)/etc @@ -32,6 +32,7 @@ APPDIR = $(SHAREDIR)/$(APPNAME) APPSDIR = $(SHAREDIR)/applications BASHCDIR = $(SHAREDIR)/bash-completion/completions SUDOERSDIR = $(SYSCONFDIR)/sudoers.d +XDGAUTODIR = $(SYSCONFDIR)/xdg/autostart awkbin :=$(shell which awk) cpbin :=$(shell which cp) @@ -67,7 +68,7 @@ install: @${echobin} Installing files to ${DESTDIR} ${installbin} -d ${SYSCONFDIR} ${DEFAULTDIR} ${BINDIR} \ ${APPSDIR} ${APPDIR} ${DOCDIR} ${BASHCDIR} ${SUDOERSDIR} \ - ${LIBEXECDIR}/${APPNAME} + ${LIBEXECDIR}/${APPNAME} ${XDGAUTODIR} ${cpbin} -pr ${SRCDIR}/etc/*.* ${SYSCONFDIR} ${cpbin} -pr ${SRCDIR}/etc/sysconfig/* ${DEFAULTDIR} ${cpbin} -pr ${SRCDIR}/usr/bin/* ${BINDIR} @@ -77,6 +78,7 @@ install: ${installbin} -m 0644 -t ${BASHCDIR} ${SRCDIR}/usr/share/bash-completion/completions/* ${installbin} -m 0640 -t ${SUDOERSDIR} ${SRCDIR}/etc/sudoers.d/* ${installbin} -m 0755 -t ${LIBEXECDIR}/${APPNAME} ${SRCDIR}/usr/libexec/${APPNAME}/* + ${installbin} -m 0644 -t ${XDGAUTODIR} ${SRCDIR}/etc/xdg/autostart/* # symlink, when alternatives is not being used ${lnbin} -s logout-manager-gtk.py ${BINDIR}/logout-manager diff --git a/src/etc/xdg/autostart/logout-manager-trayicon.desktop b/src/etc/xdg/autostart/logout-manager-trayicon.desktop new file mode 100644 index 0000000..a1c5713 --- /dev/null +++ b/src/etc/xdg/autostart/logout-manager-trayicon.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Categories=Utility;TrayIcon; +Comment=Shows tray icon for easy logout options +Exec=/usr/bin/logout-manager-trayicon +GenericName=Logout menu on a tray icon +Icon=system-log-out +Keywords=shutdown;hibernate;lockscreen;logout;reboot; +Name=Logout manager tray icon +StartupNotify=true +Terminal=false +Type=Application +Version=1.0 +Hidden=true diff --git a/src/usr/bin/logout-manager-cli.py b/src/usr/bin/logout-manager-cli.py index 64ea133..8fd78b4 100755 --- a/src/usr/bin/logout-manager-cli.py +++ b/src/usr/bin/logout-manager-cli.py @@ -60,6 +60,7 @@ if config.can_hibernate: if args.action in allowed_actions: func = getattr(globals()['actions'],args.action) func(config) + sys.exit(0) elif args.action: eprint("Unable to take action: %s" % str(args.action)) sys.exit(1) diff --git a/src/usr/bin/logout-manager-trayicon b/src/usr/bin/logout-manager-trayicon new file mode 100755 index 0000000..b6471f9 --- /dev/null +++ b/src/usr/bin/logout-manager-trayicon @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# File: logout-manager-trayicon +# License: CC-BY-SA 4.0 +# Author: bgstack15 +# 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.py +# 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 +# dep-devuan: python3-psutil + +import gi, os, platform, re, sys, psutil, signal +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 = 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 + +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() diff --git a/src/usr/share/applications/logout-manager-trayicon.desktop b/src/usr/share/applications/logout-manager-trayicon.desktop new file mode 100644 index 0000000..0379a29 --- /dev/null +++ b/src/usr/share/applications/logout-manager-trayicon.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Utility;TrayIcon; +Comment=Shows tray icon for easy logout options +Exec=/usr/bin/logout-manager-trayicon +GenericName=Logout menu on a tray icon +Icon=system-log-out +Keywords=shutdown;hibernate;lockscreen;logout;reboot; +Name=Logout manager tray icon +StartupNotify=true +Terminal=false +Type=Application +Version=1.0 -- cgit From 50a1de5c136c4c7e8a7c4a588e82e902e39c0815 Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 20 Mar 2020 17:46:19 -0400 Subject: move debian/ to stackrpms --- README.md | 3 +++ debian/README.Debian | 5 ----- debian/changelog | 5 ----- debian/compat | 1 - debian/control | 17 ----------------- debian/copyright | 29 ----------------------------- debian/do-not-install | 1 - debian/logout-manager.conffiles | 2 -- debian/logout-manager.dsc | 14 -------------- debian/logout-manager.install | 0 debian/logout-manager.lintian-overrides | 4 ---- debian/logout-manager.postinst | 9 --------- debian/logout-manager.prerm | 11 ----------- debian/patches/series | 1 - debian/rules | 18 ------------------ debian/source/format | 1 - debian/source/lintian-overrides | 2 -- debian/source/local-options | 2 -- debian/watch | 2 -- 19 files changed, 3 insertions(+), 124 deletions(-) delete mode 100644 debian/README.Debian delete mode 100644 debian/changelog delete mode 100644 debian/compat delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100644 debian/do-not-install delete mode 100644 debian/logout-manager.conffiles delete mode 100644 debian/logout-manager.dsc delete mode 100644 debian/logout-manager.install delete mode 100644 debian/logout-manager.lintian-overrides delete mode 100644 debian/logout-manager.postinst delete mode 100644 debian/logout-manager.prerm delete mode 100644 debian/patches/series delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/source/lintian-overrides delete mode 100644 debian/source/local-options delete mode 100644 debian/watch diff --git a/README.md b/README.md index aa626bb..0e69c79 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # Overview for logout-manager See the [full readme](src/usr/share/doc/logout-manager/README.md) farther down in the source tree. + +## Alt directory +The `alt` directory contains alternative files, including a shell script implementation of logout-manager-trayicon, which depends on `mktrayicon`. diff --git a/debian/README.Debian b/debian/README.Debian deleted file mode 100644 index 810fad6..0000000 --- a/debian/README.Debian +++ /dev/null @@ -1,5 +0,0 @@ -logout-manager for Devuan - -No changes - - -- Ben Stack Wed, 11 Mar 2020 08:38:11 -0400 diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index b206280..0000000 --- a/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -logout-manager (0.0.1-1) obs; urgency=low - - * Initial release. - - -- Ben Stack Wed, 11 Mar 2020 08:38:11 -0400 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index 48082f7..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -12 diff --git a/debian/control b/debian/control deleted file mode 100644 index f7edf05..0000000 --- a/debian/control +++ /dev/null @@ -1,17 +0,0 @@ -Source: logout-manager -Section: x11 -Priority: optional -Maintainer: Ben Stack -Build-Depends: debhelper (>=12~) -Standards-Version: 4.1.4 -Homepage: https://bgstack15.wordpress.com/ - -Package: logout-manager -Architecture: all -Multi-Arch: foreign -Depends: ${misc:Depends}, ${shlibs:Depends} -Description: provide simple menu for logout-type actions - Designed for minimal DEs and window managers that - lack a menu for logging out, this tool provides - such a menu. - diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 6b39eaf..0000000 --- a/debian/copyright +++ /dev/null @@ -1,29 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: logout-manager -Source: -# -# Please double check copyright with the licensecheck(1) command. - -Files: .gitignore - README.md - src/Makefile - src/etc/bash_completion.d/logout-manager - src/etc/logout-manager.conf - src/etc/sudoers.d/30_logout-manager_sudo - src/etc/sysconfig/logout-manager - src/usr/bin/logout-manager-cli.py - src/usr/bin/logout-manager-gtk.py - src/usr/bin/logout-manager-ncurses.py - src/usr/bin/logout-manager-tcl.py - src/usr/libexec/logout-manager/lm-helper - src/usr/share/applications/logout-manager.desktop - src/usr/share/doc/logout-manager/README.md - src/usr/share/doc/logout-manager/logout-manager.conf.example - src/usr/share/logout-manager/__pycache__/lmlib.cpython-37.pyc - src/usr/share/logout-manager/lmlib.py -Copyright: __NO_COPYRIGHT_NOR_LICENSE__ -License: __NO_COPYRIGHT_NOR_LICENSE__ - -#---------------------------------------------------------------------------- -# Files marked as NO_LICENSE_TEXT_FOUND may be covered by the following -# license/copyright files. diff --git a/debian/do-not-install b/debian/do-not-install deleted file mode 100644 index e752d8b..0000000 --- a/debian/do-not-install +++ /dev/null @@ -1 +0,0 @@ -usr/bin/logout-manager diff --git a/debian/logout-manager.conffiles b/debian/logout-manager.conffiles deleted file mode 100644 index 65e8592..0000000 --- a/debian/logout-manager.conffiles +++ /dev/null @@ -1,2 +0,0 @@ -etc/logout-manager.conf -etc/default/logout-manager diff --git a/debian/logout-manager.dsc b/debian/logout-manager.dsc deleted file mode 100644 index 63ef881..0000000 --- a/debian/logout-manager.dsc +++ /dev/null @@ -1,14 +0,0 @@ -Format: 3.0 (quilt) -Source: logout-manager -Binary: logout-manager -Architecture: all -Version: 0.0.1-1 -Maintainer: Ben Stack -Homepage: https://bgstack15.wordpress.com/ -Standards-Version: 4.1.4 -Build-Depends: debhelper (>= 12~) -Package-List: - logout-manager deb x11 optional arch=all -Files: - 00000000000000000000000000000000 1 logout-manager.orig.tar.gz - 00000000000000000000000000000000 1 logout-manager.debian.tar.xz diff --git a/debian/logout-manager.install b/debian/logout-manager.install deleted file mode 100644 index e69de29..0000000 diff --git a/debian/logout-manager.lintian-overrides b/debian/logout-manager.lintian-overrides deleted file mode 100644 index 01f15e1..0000000 --- a/debian/logout-manager.lintian-overrides +++ /dev/null @@ -1,4 +0,0 @@ -binary-without-manpage -copyright-has-url-from-dh_make-boilerplate -copyright-without-copyright-notice -script-with-language-extension diff --git a/debian/logout-manager.postinst b/debian/logout-manager.postinst deleted file mode 100644 index b98d697..0000000 --- a/debian/logout-manager.postinst +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -e -#DEBHELPER# -case "$1" in - configure|abort-upgrade|abort-remove|abort-deconfigure) - update-alternatives --install /usr/bin/logout-manager logout-manager /usr/bin/logout-manager-gtk.py 80 - update-alternatives --install /usr/bin/logout-manager logout-manager /usr/bin/logout-manager-tcl.py 70 - update-alternatives --install /usr/bin/logout-manager logout-manager /usr/bin/logout-manager-ncurses.py 60 - ;; -esac diff --git a/debian/logout-manager.prerm b/debian/logout-manager.prerm deleted file mode 100644 index c0c50d3..0000000 --- a/debian/logout-manager.prerm +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e -#DEBHELPER# -case "$1" in - remove|deconfigure) - update-alternatives --remove logout-manager /usr/bin/logout-manager-gtk.py - update-alternatives --remove logout-manager /usr/bin/logout-manager-tcl.py - update-alternatives --remove logout-manager /usr/bin/logout-manager-ncurses.py - ;; - upgrade|failed-upgrade) - ;; -esac diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 4a97dfa..0000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -# You must remove unused comment lines for the released package. diff --git a/debian/rules b/debian/rules deleted file mode 100755 index c1fee5d..0000000 --- a/debian/rules +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/make -f -# You must remove unused comment lines for the released package. -#export DH_VERBOSE = 1 -#export DEB_BUILD_MAINT_OPTIONS = hardening=+all -#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed -APPNAME=logout-manager - -%: - dh $@ --sourcedirectory=src - -override_dh_auto_install: - dh_auto_install -- prefix=/usr DEFAULTDIR='$$(DESTDIR)/etc/default' - -override_dh_gencontrol: - printf "misc:Depends=" > debian/${APPNAME}.substvars - make -C src deplist DISTRO=devuan SEPARATOR=',' | grep -vE 'make\[[0-9]' >> debian/${APPNAME}.substvars - dh_gencontrol diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides deleted file mode 100644 index 2da37ed..0000000 --- a/debian/source/lintian-overrides +++ /dev/null @@ -1,2 +0,0 @@ -file-without-copyright-information -missing-license-paragraph-in-dep5-copyright diff --git a/debian/source/local-options b/debian/source/local-options deleted file mode 100644 index 00131ee..0000000 --- a/debian/source/local-options +++ /dev/null @@ -1,2 +0,0 @@ -#abort-on-upstream-changes -#unapply-patches diff --git a/debian/watch b/debian/watch deleted file mode 100644 index fc70498..0000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -# You must remove unused comment lines for the released package. -version=4 -- cgit From 3d0911850e5afa7b0f631b6c06dc2caefc38a15d Mon Sep 17 00:00:00 2001 From: B Stack Date: Wed, 1 Apr 2020 17:07:54 -0400 Subject: fix #2 and #3 distro name python 3.8 removed platform.platform() in favor of distro.linux_distribution(). This change uses the new format. --- src/usr/bin/logout-manager-trayicon | 14 ++++++++++---- src/usr/share/logout-manager/lmlib.py | 10 +++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/usr/bin/logout-manager-trayicon b/src/usr/bin/logout-manager-trayicon index b6471f9..012e2bd 100755 --- a/src/usr/bin/logout-manager-trayicon +++ b/src/usr/bin/logout-manager-trayicon @@ -2,6 +2,11 @@ # 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 @@ -15,10 +20,11 @@ # 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 -# dep-devuan: python3-psutil +# dep-pip: psutil distro +# dep-devuan: python3-psutil python3-distro -import gi, os, platform, re, sys, psutil, signal +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 @@ -27,7 +33,7 @@ from dotenv import load_dotenv # all this to load the libpath try: defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() + 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 diff --git a/src/usr/share/logout-manager/lmlib.py b/src/usr/share/logout-manager/lmlib.py index 40ee3a0..0bf0a0c 100644 --- a/src/usr/share/logout-manager/lmlib.py +++ b/src/usr/share/logout-manager/lmlib.py @@ -12,8 +12,11 @@ # platform info https://stackoverflow.com/questions/110362/how-can-i-find-the-current-os-in-python/10091465#10091465 # Improve: # Documentation: +# Dependencies: +# dep-devuan: python3-distro -import configparser, platform, os, subprocess +import configparser, os, subprocess +from distro import linux_distribution logout_manager_version="2020-03-10a" @@ -260,9 +263,10 @@ def Initialize_config(infile): # set icon category # written primarily for el7 which uses "app" for the system-reboot icons, etc. - a = platform.dist() + a, b, _ = linux_distribution() + a = a.lower() try: - if a[0] == "redhat" and int(a[1].split(".")[0]) <= 7: + if ("red hat" in a or "redhat" in a) and int(b.split(".")[0]) <= 7: config.set_icon_category("apps") except: pass -- cgit From 81aec2b35e2f61319080fabf9cd3c43113eadbae Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 2 Apr 2020 08:58:32 -0400 Subject: add man pages and drop .py endings --- src/Makefile | 44 ++- src/usr/bin/logout-manager-cli | 70 ++++ src/usr/bin/logout-manager-cli.py | 70 ---- src/usr/bin/logout-manager-gtk | 252 +++++++++++++ src/usr/bin/logout-manager-gtk.py | 252 ------------- src/usr/bin/logout-manager-ncurses | 193 ++++++++++ src/usr/bin/logout-manager-ncurses.py | 193 ---------- src/usr/bin/logout-manager-tcl | 417 +++++++++++++++++++++ src/usr/bin/logout-manager-tcl.py | 417 --------------------- src/usr/bin/logout-manager-trayicon | 2 +- src/usr/share/doc/logout-manager/README.md | 2 +- src/usr/share/logout-manager/lmlib.py | 4 +- src/usr/share/man/man1/logout-manager-cli.1.md | 25 ++ src/usr/share/man/man1/logout-manager-gtk.1.md | 16 + src/usr/share/man/man1/logout-manager-ncurses.1.md | 16 + src/usr/share/man/man1/logout-manager-tcl.1.md | 16 + .../share/man/man1/logout-manager-trayicon.1.md | 18 + src/usr/share/man/man5/logout-manager.conf.5.md | 57 +++ src/usr/share/man/man7/logout-manager.7.md | 45 +++ 19 files changed, 1159 insertions(+), 950 deletions(-) create mode 100755 src/usr/bin/logout-manager-cli delete mode 100755 src/usr/bin/logout-manager-cli.py create mode 100755 src/usr/bin/logout-manager-gtk delete mode 100755 src/usr/bin/logout-manager-gtk.py create mode 100755 src/usr/bin/logout-manager-ncurses delete mode 100755 src/usr/bin/logout-manager-ncurses.py create mode 100755 src/usr/bin/logout-manager-tcl delete mode 100755 src/usr/bin/logout-manager-tcl.py create mode 100644 src/usr/share/man/man1/logout-manager-cli.1.md create mode 100644 src/usr/share/man/man1/logout-manager-gtk.1.md create mode 100644 src/usr/share/man/man1/logout-manager-ncurses.1.md create mode 100644 src/usr/share/man/man1/logout-manager-tcl.1.md create mode 100644 src/usr/share/man/man1/logout-manager-trayicon.1.md create mode 100644 src/usr/share/man/man5/logout-manager.conf.5.md create mode 100644 src/usr/share/man/man7/logout-manager.7.md diff --git a/src/Makefile b/src/Makefile index 010b71e..009da93 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,9 +16,11 @@ # Document: # Includes a nice way to dynamically generate dependencies as self-reported by all the files. # Dependencies: +# exclude-raw: go-md2man +# exclude-devuan: go-md2man APPNAME = logout-manager -APPVERSION = 0.0.2 +APPVERSION = 0.0.3 SRCDIR = $(CURDIR) prefix = /usr SYSCONFDIR = $(DESTDIR)/etc @@ -27,10 +29,11 @@ DEFAULTDIR = $(DESTDIR)/etc/sysconfig BINDIR = $(DESTDIR)$(prefix)/bin SHAREDIR = $(DESTDIR)$(prefix)/share LIBEXECDIR = $(DESTDIR)$(prefix)/libexec -DOCDIR = $(SHAREDIR)/doc/$(APPNAME) APPDIR = $(SHAREDIR)/$(APPNAME) APPSDIR = $(SHAREDIR)/applications BASHCDIR = $(SHAREDIR)/bash-completion/completions +DOCDIR = $(SHAREDIR)/doc/$(APPNAME) +MANDIR = $(SHAREDIR)/man SUDOERSDIR = $(SYSCONFDIR)/sudoers.d XDGAUTODIR = $(SYSCONFDIR)/xdg/autostart @@ -39,8 +42,10 @@ cpbin :=$(shell which cp) echobin :=$(shell which echo) findbin :=$(shell which find) grepbin :=$(shell which grep) +gzipbin :=$(shell which gzip) installbin :=$(shell which install) lnbin :=$(shell which ln) +md2manbin :=$(shell which go-md2man) rmbin :=$(shell which rm) sedbin :=$(shell which sed) sortbin :=$(shell which sort) @@ -48,23 +53,28 @@ truebin :=$(shell which true) uniqbin :=$(shell which uniq) xargsbin :=$(shell which xargs) +SEPARATOR ?=, + all: - ${echobin} "No compilation in this package." + @${echobin} "No compilation in this package." -.PHONY: clean install uninstall list deplist deplist_opts +.PHONY: clean install uninstall list deplist deplist_opts install_files install_man list: @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | ${awkbin} -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | ${sortbin} | ${grepbin} -E -v -e '^[^[:alnum:]]' -e '^$@$$' deplist: @if test -z "$(DISTRO)" ; then ${echobin} "Please run \`make deplist\` with DISTRO= one of: `make deplist_opts 2>&1 1>/dev/null | ${xargsbin}`. Aborted." ; exit 1 ; fi - @${grepbin} -h --exclude='Makefile' --exclude-dir='doc' -A5 -riIE dependencies $(SRCDIR) | ${awkbin} -v 'distro=$(DISTRO)' 'tolower($$0) ~ distro {$$1="";$$2="";print}' | ${awkbin} 'BEGIN{cmd="${xargsbin} -n1"} $$0 !~ /\(/{print $$0 | cmd ; close(cmd);} $$0 ~ /\(/{print;}' | ${sortbin} | ${uniqbin} | ${sedbin} -r -e 's/$$/$(SEPARATOR)/' | ${xargsbin} + @# deplist 2020-03-24 input must be comma separated + @${grepbin} -h --exclude-dir='doc' -riIE '\&2 - @${echobin} "devuan" 1>&2 + @# deplist_opts 2020-03-24 find all available dependency domains + @${grepbin} -h -o -riIE '\&2 -install: +install: install_files install_man + +install_files: @${echobin} Installing files to ${DESTDIR} ${installbin} -d ${SYSCONFDIR} ${DEFAULTDIR} ${BINDIR} \ ${APPSDIR} ${APPDIR} ${DOCDIR} ${BASHCDIR} ${SUDOERSDIR} \ @@ -80,17 +90,23 @@ install: ${installbin} -m 0755 -t ${LIBEXECDIR}/${APPNAME} ${SRCDIR}/usr/libexec/${APPNAME}/* ${installbin} -m 0644 -t ${XDGAUTODIR} ${SRCDIR}/etc/xdg/autostart/* # symlink, when alternatives is not being used - ${lnbin} -s logout-manager-gtk.py ${BINDIR}/logout-manager + ${lnbin} -s logout-manager-gtk ${BINDIR}/logout-manager || : + +install_man: +ifeq ($(md2manbin),) + @${echobin} Cannot install man pages, because go-md2man is not found. + @false +endif + ${installbin} -d ${MANDIR}/man1 ${MANDIR}/man5 ${MANDIR}/man7 + @#${md2manbin} < ${SRCDIR}/usr/share/man/man6/${APPNAME}.6.md | ${gzipbin} > ${MANDIR}/man6/${APPNAME}.6.gz + for tm in $$( ${findbin} ${SRCDIR}/usr/share/man ! -type d -name '*[0-9].md' -printf '%P\n' ) ; do ${md2manbin} < ${SRCDIR}/usr/share/man/$${tm} > ${MANDIR}/$${tm%%.md}.gz ; done uninstall: @${echobin} SRCDIR=${SRCDIR} - ${rmbin} -f $$( ${findbin} ${SRCDIR} -mindepth 1 ! -type d -printf '%p\n' | ${sedbin} -r -e "s:^${SRCDIR}:${DESTDIR}:" ) ${DEFAULTDIR}/${APPNAME} ${BINDIR}/logout-manager - - # absolute minimum directories to remove - #${rmbin} -rf ${APPDIR} ${SYSCONFDIR}/${APPNAME} ${DOCDIR} + ${rmbin} -f $$( ${findbin} ${SRCDIR} -mindepth 1 ! -type d ! -name 'Makefile' -printf '%p\n' | ${sedbin} -r -e "s:^${SRCDIR}:${DESTDIR}:" -e '/man[0-9]\/.*[0-9]\.md$$/{s:\.md$$:.gz:}' ) ${DEFAULTDIR}/${APPNAME} ${BINDIR}/logout-manager # remove all installed directories that are now blank. rmdir ${DEFAULTDIR} 2>/dev/null ; for word in $$( ${findbin} ${SRCDIR} -mindepth 1 -type d -printf '%p\n' | ${sedbin} -r -e "s:^${SRCDIR}:${DESTDIR}:" | ${awkbin} '{ print length, $$0 }' | sort -rn | ${awkbin} '{print $$2}' ) ; do ${findbin} $${word} -mindepth 1 1>/dev/null 2>&1 | read 1>/dev/null 2>&1 || { rmdir "$${word}" 2>/dev/null || ${truebin} ; } ; done clean: - -${echobin} "target $@ not implemented yet! Gotta say unh." + -@${echobin} "target $@ not implemented yet! Gotta say unh." diff --git a/src/usr/bin/logout-manager-cli b/src/usr/bin/logout-manager-cli new file mode 100755 index 0000000..fde7d55 --- /dev/null +++ b/src/usr/bin/logout-manager-cli @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# File: logout-manager-cli +# License: CC-BY-SA 4.0 +# Author: bgstack15 +# Startdate: 2020-03-10 18:40 +# Title: cli logout manager +# Purpose: Feature completeness in this package +# History: +# Usage: +# logout-manager-cli +# Reference: +# https://stackoverflow.com/questions/39092149/argparse-how-to-make-mutually-exclusive-arguments-optional/39092229#39092229 +# https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554 +# Improve: +# Dependencies: +# Devuan: python3-dotenv python3 +# Documentation: + +import os, platform, sys, argparse +from dotenv import load_dotenv + +# 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 + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +logout_manager_cli_version="2020-03-10" + +parser = argparse.ArgumentParser(description="run logout-manager commands using cli") +parser.add_argument('action', help='which action to take',nargs='?', choices=('lock','logout','hibernate','shutdown','reboot')) +parser.add_argument("-d","--debug", nargs='?', default=0, type=int, choices=range(0,11), help="Set debug level.") +parser.add_argument("-n","--dryrun", action='store_true', help="only report. Useful for checking if hibernate is allowed.") +parser.add_argument("-V","--version", action="version", version="%(prog)s " + logout_manager_cli_version) + +args = parser.parse_args() + +# load configs +# in cli, must happen after arparse to benefit from debug value +config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) +actions = lmlib.Actions + +# MAIN LOOP +allowed_actions=['lock','logout','shutdown','reboot'] +if config.can_hibernate: + allowed_actions.append('hibernate') + +if args.action in allowed_actions: + func = getattr(globals()['actions'],args.action) + func(config) + sys.exit(0) +elif args.action: + eprint("Unable to take action: %s" % str(args.action)) + sys.exit(1) + +# if we get here, no action was used +parser.print_help() +sys.exit(2) diff --git a/src/usr/bin/logout-manager-cli.py b/src/usr/bin/logout-manager-cli.py deleted file mode 100755 index 8fd78b4..0000000 --- a/src/usr/bin/logout-manager-cli.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -# File: logout-manager-cli.py -# License: CC-BY-SA 4.0 -# Author: bgstack15 -# Startdate: 2020-03-10 18:40 -# Title: cli logout manager -# Purpose: Feature completeness in this package -# History: -# Usage: -# logout-manager-cli.py -# Reference: -# https://stackoverflow.com/questions/39092149/argparse-how-to-make-mutually-exclusive-arguments-optional/39092229#39092229 -# https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554 -# Improve: -# Dependencies: -# Devuan: python3-dotenv python3 -# Documentation: - -import os, platform, sys, argparse -from dotenv import load_dotenv - -# 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 - -def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - -logout_manager_cli_version="2020-03-10" - -parser = argparse.ArgumentParser(description="run logout-manager commands using cli") -parser.add_argument('action', help='which action to take',nargs='?', choices=('lock','logout','hibernate','shutdown','reboot')) -parser.add_argument("-d","--debug", nargs='?', default=0, type=int, choices=range(0,11), help="Set debug level.") -parser.add_argument("-n","--dryrun", action='store_true', help="only report. Useful for checking if hibernate is allowed.") -parser.add_argument("-V","--version", action="version", version="%(prog)s " + logout_manager_cli_version) - -args = parser.parse_args() - -# load configs -# in cli, must happen after arparse to benefit from debug value -config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) -actions = lmlib.Actions - -# MAIN LOOP -allowed_actions=['lock','logout','shutdown','reboot'] -if config.can_hibernate: - allowed_actions.append('hibernate') - -if args.action in allowed_actions: - func = getattr(globals()['actions'],args.action) - func(config) - sys.exit(0) -elif args.action: - eprint("Unable to take action: %s" % str(args.action)) - sys.exit(1) - -# if we get here, no action was used -parser.print_help() -sys.exit(2) diff --git a/src/usr/bin/logout-manager-gtk b/src/usr/bin/logout-manager-gtk new file mode 100755 index 0000000..389ad70 --- /dev/null +++ b/src/usr/bin/logout-manager-gtk @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# File: logout-manager-gtk +# 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 +# https://stackoverflow.com/questions/4090804/how-can-i-pass-variables-between-two-classes-windows-in-pygtk +# 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 +# Dependencies: +# Devuan: python3-dotenv +# Documentation: + +import gi, os, platform, sys +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 +from dotenv import load_dotenv + +# 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 +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: + try: + icon_info = this_theme.lookup_icon(fallback_icon_name, size, 0) + iconfilename = icon_info.get_filename() + except: + # no icon in the current theme. Try a hard-coded fallback: + try: + # if debuglev 3 + print("Error: could not find default icon for", icon_name+", so using fallback.") + this_theme = Gtk.IconTheme.new() + this_theme.set_custom_theme("Numix-Circle") + icon_info = this_theme.lookup_icon(icon_name, size, 0) + iconfilename = icon_info.get_filename() + except: + print("Error: Could not find any icon for", icon_name) + return None + #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, config, actions): + self.actions = actions + self.config = config + 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(config.application_icon, 24, 0) + pixbuf32 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 32, 0) + pixbuf48 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 48, 0) + pixbuf64 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 64, 0) + pixbuf96 = Gtk.IconTheme.get_default().load_icon(config.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)) + self.actions.shutdown(self.config) + + def do_hibernate(self, widget): + self.actions.hibernate(self.config) + + def do_lock(self, widget): + self.actions.lock(self.config) + + def do_logout(self, widget): + self.actions.logout(self.config) + + def do_reboot(self, widget): + self.actions.reboot(self.config) + + def cancel(self, widget): + print("Cancel any logout action.") + Gtk.main_quit() + +# load configs +config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) +actions = lmlib.Actions + +# MAIN LOOP +win = MainWindow(config, actions) +win.connect("destroy", Gtk.main_quit) +win.show_all() +Gtk.main() diff --git a/src/usr/bin/logout-manager-gtk.py b/src/usr/bin/logout-manager-gtk.py deleted file mode 100755 index 553fc41..0000000 --- a/src/usr/bin/logout-manager-gtk.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python3 -# File: logout-manager-gtk.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 -# https://stackoverflow.com/questions/4090804/how-can-i-pass-variables-between-two-classes-windows-in-pygtk -# 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 -# Dependencies: -# Devuan: python3-dotenv -# Documentation: - -import gi, os, platform, sys -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 -from dotenv import load_dotenv - -# 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 -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: - try: - icon_info = this_theme.lookup_icon(fallback_icon_name, size, 0) - iconfilename = icon_info.get_filename() - except: - # no icon in the current theme. Try a hard-coded fallback: - try: - # if debuglev 3 - print("Error: could not find default icon for", icon_name+", so using fallback.") - this_theme = Gtk.IconTheme.new() - this_theme.set_custom_theme("Numix-Circle") - icon_info = this_theme.lookup_icon(icon_name, size, 0) - iconfilename = icon_info.get_filename() - except: - print("Error: Could not find any icon for", icon_name) - return None - #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, config, actions): - self.actions = actions - self.config = config - 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(config.application_icon, 24, 0) - pixbuf32 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 32, 0) - pixbuf48 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 48, 0) - pixbuf64 = Gtk.IconTheme.get_default().load_icon(config.application_icon, 64, 0) - pixbuf96 = Gtk.IconTheme.get_default().load_icon(config.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)) - self.actions.shutdown(self.config) - - def do_hibernate(self, widget): - self.actions.hibernate(self.config) - - def do_lock(self, widget): - self.actions.lock(self.config) - - def do_logout(self, widget): - self.actions.logout(self.config) - - def do_reboot(self, widget): - self.actions.reboot(self.config) - - def cancel(self, widget): - print("Cancel any logout action.") - Gtk.main_quit() - -# load configs -config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) -actions = lmlib.Actions - -# MAIN LOOP -win = MainWindow(config, actions) -win.connect("destroy", Gtk.main_quit) -win.show_all() -Gtk.main() diff --git a/src/usr/bin/logout-manager-ncurses b/src/usr/bin/logout-manager-ncurses new file mode 100755 index 0000000..7ff5e18 --- /dev/null +++ b/src/usr/bin/logout-manager-ncurses @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# File: logout-manager-ncurses +# License: MIT +# Author: adamlamers, bgstack15 +# Startdate: 2020-03-09 17:06 +# Title: ncurses based logout manager +# Usage: +# logout-manager-ncurses +# Reference: +# https://docs.python.org/3/howto/curses.html +# ripped straight from http://adamlamers.com/post/FTPD9KNRA8CT +# https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554 +# https://robinislam.me/blog/reading-environment-variables-in-python/ +# Improve: +# Dependencies: +# dep-devuan: python3-dotenv +# Documentation: +# Improvements for CursesMenu class over origin: +# accepts number key inputs +# accepts enabled attribute +# add "zeroindex" bool + +import curses, os, platform, sys +from dotenv import load_dotenv + +# 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 + +class CursesMenu(object): + + INIT = {'type' : 'init'} + + def __init__(self, menu_options): + self.screen = curses.initscr() + self.menu_options = menu_options + self.selected_option = 0 + self._previously_selected_option = None + self.running = True + self._zero_offset = 1 + try: + self._zero_offset = 0 if bool(self.menu_options['zeroindex']) else 1 + except: + pass + + #init curses and curses input + curses.noecho() + curses.cbreak() + curses.start_color() + curses.curs_set(0) #Hide cursor + self.screen.keypad(1) + + #set up color pair for highlighted option + curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) + self.hilite_color = curses.color_pair(1) + self.normal_color = curses.A_NORMAL + + def prompt_selection(self, parent=None): + if parent is None: + lastoption = "Cancel" + else: + lastoption = "Return to previous menu ({})".format(parent['title']) + + option_count = len(self.menu_options['options']) + + input_key = None + + ENTER_KEY = ord('\n') + NUM_KEYS = [ord(str(i)) for i in range(self._zero_offset,option_count+1+self._zero_offset)] + done = False + while not done: + if self.selected_option != self._previously_selected_option: + self._previously_selected_option = self.selected_option + + self.screen.border(0) + self._draw_title() + for option in range(option_count): + if self.selected_option == option: + self._draw_option(option, self.hilite_color) + else: + self._draw_option(option, self.normal_color) + + if self.selected_option == option_count: + self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset, + lastoption), self.hilite_color) + else: + self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset, + lastoption), self.normal_color) + + max_y, max_x = self.screen.getmaxyx() + if input_key is not None: + self.screen.addstr(max_y-3, max_x - 5, "{:3}".format(self.selected_option+self._zero_offset)) + self.screen.refresh() + + + input_key = self.screen.getch() + down_keys = [curses.KEY_DOWN, ord('j')] + up_keys = [curses.KEY_UP, ord('k')] + exit_keys = [ord('q')] + + if input_key in down_keys: + if self.selected_option < option_count: + self.selected_option += 1 + else: + self.selected_option = 0 + + if input_key in up_keys: + if self.selected_option > 0: + self.selected_option -= 1 + else: + self.selected_option = option_count + + if input_key in exit_keys: + self.selected_option = option_count #auto select exit and return + break + + if input_key == ENTER_KEY or input_key in NUM_KEYS: + if input_key in NUM_KEYS: + self.selected_option=int(chr(input_key))-self._zero_offset + done = True + try: + done = self.menu_options['options'][self.selected_option]['enabled'] + except: + pass + return self.selected_option + + def _draw_option(self, option_number, style): + thistext = self.menu_options['options'][option_number]['title'] + try: + if self.menu_options['options'][option_number]['enabled'] == False: thistext += " (disabled)" + except: + pass + self.screen.addstr(4 + option_number, + 4, + "{:2} - {}".format(option_number+self._zero_offset, thistext), + style) + + def _draw_title(self): + self.screen.addstr(2, 2, self.menu_options['title'], curses.A_STANDOUT) + self.screen.addstr(3, 2, self.menu_options['subtitle'], curses.A_BOLD) + + def display(self): + selected_option = self.prompt_selection() + i, _ = self.screen.getmaxyx() + curses.endwin() + #os.system('clear') + if selected_option < len(self.menu_options['options']): + selected_opt = self.menu_options['options'][selected_option] + return selected_opt + else: + self.running = False + return {'title' : 'Cancel', 'type' : 'exitmenu'} + +# load configs +config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) +actions = lmlib.Actions + +# MAIN LOOP +menu = { + 'title' : 'Logout Manager', + 'type' : 'menu', + 'subtitle' : 'Use arrows or number keys', + 'zeroindex' : False, + 'options' : [ + {'title': 'Lock', 'type': 'action', 'action': 'lock'}, + {'title': 'Logout', 'type': 'action', 'action': 'logout'}, + {'title': 'Hibernate', 'type': 'action', 'action': 'hibernate', 'enabled': config.can_hibernate}, + {'title': 'Shutdown', 'type': 'action', 'action': 'shutdown'}, + {'title': 'Reboot', 'type': 'action', 'action': 'reboot'} + ] +} +m = CursesMenu(menu) +selected_action = m.display() + +if selected_action['type'] == 'exitmenu': + print("Cancel any logout action.") +elif selected_action['type'] == 'command': + os.system(selected_action['command']) +elif selected_action['type'] == 'action': + #a = selected_action['action']: + func = getattr(globals()['actions'],selected_action['action']) + func(config) diff --git a/src/usr/bin/logout-manager-ncurses.py b/src/usr/bin/logout-manager-ncurses.py deleted file mode 100755 index 1500d85..0000000 --- a/src/usr/bin/logout-manager-ncurses.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 -# File: logout-manager-ncurses.py -# License: MIT -# Author: adamlamers, bgstack15 -# Startdate: 2020-03-09 17:06 -# Title: ncurses based logout manager -# Usage: -# logout-manager-ncurses.py -# Reference: -# https://docs.python.org/3/howto/curses.html -# ripped straight from http://adamlamers.com/post/FTPD9KNRA8CT -# https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554 -# https://robinislam.me/blog/reading-environment-variables-in-python/ -# Improve: -# Dependencies: -# Devuan: python3-dotenv -# Documentation: -# Improvements for CursesMenu class over origin: -# accepts number key inputs -# accepts enabled attribute -# add "zeroindex" bool - -import curses, os, platform, sys -from dotenv import load_dotenv - -# 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 - -class CursesMenu(object): - - INIT = {'type' : 'init'} - - def __init__(self, menu_options): - self.screen = curses.initscr() - self.menu_options = menu_options - self.selected_option = 0 - self._previously_selected_option = None - self.running = True - self._zero_offset = 1 - try: - self._zero_offset = 0 if bool(self.menu_options['zeroindex']) else 1 - except: - pass - - #init curses and curses input - curses.noecho() - curses.cbreak() - curses.start_color() - curses.curs_set(0) #Hide cursor - self.screen.keypad(1) - - #set up color pair for highlighted option - curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) - self.hilite_color = curses.color_pair(1) - self.normal_color = curses.A_NORMAL - - def prompt_selection(self, parent=None): - if parent is None: - lastoption = "Cancel" - else: - lastoption = "Return to previous menu ({})".format(parent['title']) - - option_count = len(self.menu_options['options']) - - input_key = None - - ENTER_KEY = ord('\n') - NUM_KEYS = [ord(str(i)) for i in range(self._zero_offset,option_count+1+self._zero_offset)] - done = False - while not done: - if self.selected_option != self._previously_selected_option: - self._previously_selected_option = self.selected_option - - self.screen.border(0) - self._draw_title() - for option in range(option_count): - if self.selected_option == option: - self._draw_option(option, self.hilite_color) - else: - self._draw_option(option, self.normal_color) - - if self.selected_option == option_count: - self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset, - lastoption), self.hilite_color) - else: - self.screen.addstr(4 + option_count, 4, "{:2} - {}".format(option_count+self._zero_offset, - lastoption), self.normal_color) - - max_y, max_x = self.screen.getmaxyx() - if input_key is not None: - self.screen.addstr(max_y-3, max_x - 5, "{:3}".format(self.selected_option+self._zero_offset)) - self.screen.refresh() - - - input_key = self.screen.getch() - down_keys = [curses.KEY_DOWN, ord('j')] - up_keys = [curses.KEY_UP, ord('k')] - exit_keys = [ord('q')] - - if input_key in down_keys: - if self.selected_option < option_count: - self.selected_option += 1 - else: - self.selected_option = 0 - - if input_key in up_keys: - if self.selected_option > 0: - self.selected_option -= 1 - else: - self.selected_option = option_count - - if input_key in exit_keys: - self.selected_option = option_count #auto select exit and return - break - - if input_key == ENTER_KEY or input_key in NUM_KEYS: - if input_key in NUM_KEYS: - self.selected_option=int(chr(input_key))-self._zero_offset - done = True - try: - done = self.menu_options['options'][self.selected_option]['enabled'] - except: - pass - return self.selected_option - - def _draw_option(self, option_number, style): - thistext = self.menu_options['options'][option_number]['title'] - try: - if self.menu_options['options'][option_number]['enabled'] == False: thistext += " (disabled)" - except: - pass - self.screen.addstr(4 + option_number, - 4, - "{:2} - {}".format(option_number+self._zero_offset, thistext), - style) - - def _draw_title(self): - self.screen.addstr(2, 2, self.menu_options['title'], curses.A_STANDOUT) - self.screen.addstr(3, 2, self.menu_options['subtitle'], curses.A_BOLD) - - def display(self): - selected_option = self.prompt_selection() - i, _ = self.screen.getmaxyx() - curses.endwin() - #os.system('clear') - if selected_option < len(self.menu_options['options']): - selected_opt = self.menu_options['options'][selected_option] - return selected_opt - else: - self.running = False - return {'title' : 'Cancel', 'type' : 'exitmenu'} - -# load configs -config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) -actions = lmlib.Actions - -# MAIN LOOP -menu = { - 'title' : 'Logout Manager', - 'type' : 'menu', - 'subtitle' : 'Use arrows or number keys', - 'zeroindex' : False, - 'options' : [ - {'title': 'Lock', 'type': 'action', 'action': 'lock'}, - {'title': 'Logout', 'type': 'action', 'action': 'logout'}, - {'title': 'Hibernate', 'type': 'action', 'action': 'hibernate', 'enabled': config.can_hibernate}, - {'title': 'Shutdown', 'type': 'action', 'action': 'shutdown'}, - {'title': 'Reboot', 'type': 'action', 'action': 'reboot'} - ] -} -m = CursesMenu(menu) -selected_action = m.display() - -if selected_action['type'] == 'exitmenu': - print("Cancel any logout action.") -elif selected_action['type'] == 'command': - os.system(selected_action['command']) -elif selected_action['type'] == 'action': - #a = selected_action['action']: - func = getattr(globals()['actions'],selected_action['action']) - func(config) diff --git a/src/usr/bin/logout-manager-tcl b/src/usr/bin/logout-manager-tcl new file mode 100755 index 0000000..17e0eed --- /dev/null +++ b/src/usr/bin/logout-manager-tcl @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +# File: logout-manager-tcl +# 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 +# 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 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("", self.onEnter) + self.widget.bind("", self.onLeave) + self.widget.bind("", 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("", 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("", 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("", 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("", 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("", 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("", 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 diff --git a/src/usr/bin/logout-manager-tcl.py b/src/usr/bin/logout-manager-tcl.py deleted file mode 100755 index 127bd54..0000000 --- a/src/usr/bin/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 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("", self.onEnter) - self.widget.bind("", self.onLeave) - self.widget.bind("", 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("", 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("", 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("", 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("", 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("", 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("", 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 diff --git a/src/usr/bin/logout-manager-trayicon b/src/usr/bin/logout-manager-trayicon index 012e2bd..7a6fd25 100755 --- a/src/usr/bin/logout-manager-trayicon +++ b/src/usr/bin/logout-manager-trayicon @@ -12,7 +12,7 @@ # 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.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 diff --git a/src/usr/share/doc/logout-manager/README.md b/src/usr/share/doc/logout-manager/README.md index d34f02c..112d5f6 100644 --- a/src/usr/share/doc/logout-manager/README.md +++ b/src/usr/share/doc/logout-manager/README.md @@ -22,7 +22,7 @@ Be aware that this is insecure. See man `fluxbox-remote(1)`. `apt-cache search logout` shows [lxsession-logout](http://manpages.ubuntu.com/manpages/precise/en/man1/lxsession-logout.1.html) which was compiled, as well as does not provide configurable options for changing executed commands or icons. ## License -[logout-manager-ncurses.py](src/usr/bin/logout-manager-ncurses.py) is licensed under the [MIT license](http://choosealicense.com/licenses/mit) and is derived almost entirely from [adamlamers](http://adamlamers.com/post/FTPD9KNRA8CT). +[logout-manager-ncurses](src/usr/bin/logout-manager-ncurses) is licensed under the [MIT license](http://choosealicense.com/licenses/mit) and is derived almost entirely from [adamlamers](http://adamlamers.com/post/FTPD9KNRA8CT). Everything else is licensed under [CC-BY-SA 4.0](https://choosealicense.com/licenses/cc-by-sa-4.0/). ## Description diff --git a/src/usr/share/logout-manager/lmlib.py b/src/usr/share/logout-manager/lmlib.py index 0bf0a0c..ba4980b 100644 --- a/src/usr/share/logout-manager/lmlib.py +++ b/src/usr/share/logout-manager/lmlib.py @@ -7,7 +7,7 @@ # Purpose: Store the common elements for operating a logout-manager # History: # Usage: -# In a logout-manager-gtk.py program +# In a logout-manager-gtk program # Reference: # platform info https://stackoverflow.com/questions/110362/how-can-i-find-the-current-os-in-python/10091465#10091465 # Improve: @@ -18,7 +18,7 @@ import configparser, os, subprocess from distro import linux_distribution -logout_manager_version="2020-03-10a" +logout_manager_version="2020-04-01a" class Actions: diff --git a/src/usr/share/man/man1/logout-manager-cli.1.md b/src/usr/share/man/man1/logout-manager-cli.1.md new file mode 100644 index 0000000..a775770 --- /dev/null +++ b/src/usr/share/man/man1/logout-manager-cli.1.md @@ -0,0 +1,25 @@ +logout-manager-cli 1 "April 2020" logout-manager "General Commands Manual" +================================================== +# NAME +logout-manager-cli - command line interface for invoking logout options +# SYNOPSIS +logout-manager-cli [OPTIONS] [ACTION] +# DESCRIPTION +Use this interface to `logout-manager`(7) from the command line or in scripts. Parameters can be passed to determine the action to take. +# OPTIONS + +-h, --help show a help screen + +-d [0-10] set debug level + +-n Dryrun only! Do not take action. Useful with `hibernate` to determine if hibernate is allowed on this system. + +-V --verbose be verbose +# ACTIONS +One action may be specified on the command line. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7),`logout-manager.conf`(5) diff --git a/src/usr/share/man/man1/logout-manager-gtk.1.md b/src/usr/share/man/man1/logout-manager-gtk.1.md new file mode 100644 index 0000000..2130f6b --- /dev/null +++ b/src/usr/share/man/man1/logout-manager-gtk.1.md @@ -0,0 +1,16 @@ +logout-manager-gtk 1 "April 2020" logout-manager "General Commands Manual" +================================================== +# NAME +logout-manager-gtk - gtk3 interface for invoking logout options +# SYNOPSIS +logout-manager-gtk +# DESCRIPTION +Use this interface to `logout-manager`(7) in an X11 graphical environment. Using gtk3, this program displays buttons with text and icons to present a simple menu for invoking different logout-related commands. +# ACTIONS +When a button is selected, an action is invoked. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7),`logout-manager.conf`(5) diff --git a/src/usr/share/man/man1/logout-manager-ncurses.1.md b/src/usr/share/man/man1/logout-manager-ncurses.1.md new file mode 100644 index 0000000..4053ce3 --- /dev/null +++ b/src/usr/share/man/man1/logout-manager-ncurses.1.md @@ -0,0 +1,16 @@ +logout-manager-ncurses 1 "April 2020" logout-manager "General Commands Manual" +================================================== +# NAME +logout-manager-ncurses - ncurses interface for invoking logout options +# SYNOPSIS +logout-manager-ncurses +# DESCRIPTION +Use this interface to `logout-manager`(7) in a console or terminal window. It displays numerized options, which can also be navigated with arrow keys or vim-style [jk] keys. The presented menu shows a list of actions for invoking different logout-related commands. +# ACTIONS +When an entry is selected, an action is invoked. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7),`logout-manager.conf`(5) diff --git a/src/usr/share/man/man1/logout-manager-tcl.1.md b/src/usr/share/man/man1/logout-manager-tcl.1.md new file mode 100644 index 0000000..e6ec55f --- /dev/null +++ b/src/usr/share/man/man1/logout-manager-tcl.1.md @@ -0,0 +1,16 @@ +logout-manager-tcl 1 "April 2020" logout-manager "General Commands Manual" +================================================== +# NAME +logout-manager-tcl - Tcl/tk interface for invoking logout options +# SYNOPSIS +logout-manager-tcl +# DESCRIPTION +Use this interface to `logout-manager`(7) in an X11 graphical environment. Using the python3 tkinter library, this program displays buttons with text and icons to present a simple menu for invoking different logout-related commands. +# ACTIONS +When a button is selected, an action is invoked. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7),`logout-manager.conf`(5) diff --git a/src/usr/share/man/man1/logout-manager-trayicon.1.md b/src/usr/share/man/man1/logout-manager-trayicon.1.md new file mode 100644 index 0000000..1f33e73 --- /dev/null +++ b/src/usr/share/man/man1/logout-manager-trayicon.1.md @@ -0,0 +1,18 @@ +logout-manager-trayicon 1 "April 2020" logout-manager "General Commands Manual" +================================================== +# NAME +logout-manager-trayicon - system tray icon for invoking logout options +# SYNOPSIS +logout-manager-trayicon +# DESCRIPTION +This interface to `logout-manager`(7) displays a system tray (notification area) icon. `Right-clicking` the icon displays a popup menu with options for invoking different logout-related commands. +A disabled menu entry also shows the currently-logged in user, as well as a notification if `DRYRUN` is set. +`Double-clicking` the icon will invoke `logout-manager` which is usually aliases to one of the available interfaces for `logout-manager`(7) +# ACTIONS +When a button from the menu is selected, an action is invoked. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7),`logout-manager.conf`(5) diff --git a/src/usr/share/man/man5/logout-manager.conf.5.md b/src/usr/share/man/man5/logout-manager.conf.5.md new file mode 100644 index 0000000..6b5cce3 --- /dev/null +++ b/src/usr/share/man/man5/logout-manager.conf.5.md @@ -0,0 +1,57 @@ +logout-manager.conf 5 "April 2020" logout-manager "File Formats and Conventions" +================================================== +# NAME +logout-manager.conf - the configuration file for logout-manager +# FILE FORMAT +The file has an ini-style syntax and consists of sections and parameters. A section begins with the name of the section in square brackets and continues until the next section begins. An example: + + [section] + key = "value" + key2 = value2 + +Put a value in double quotes if you need the white space preserved. +logout-manager.conf must be a regular file, and readable by all users who are permitted to run logout-manager. This is usually the same users who are permitted to log in to a graphical session. +# SECTIONS AND EXAMPLES +### The [logout-manager] section +Define the command for a given action. + +`Section parameters` + +lock_command, logout_command, hibernate_command, shutdown_command, reboot_command + The value will be invoked upon selection of the named action. Place a command with its parameters in double quotes. + Defaults: + + lock_command="/usr/libexec/logout-manager/lm-helper lock" + logout_command="/usr/libexec/logout-manager/lm-helper logout" + hibernate_command="sudo /usr/libexec/logout-manager/lm-helper hibernate" + reboot_command="sudo /usr/libexec/logout-manager/lm-helper reboot" + shutdown_command="sudo /usr/libexec/logout-manager/lm-helper shutdown" +### The [icons] section +Configuration options for which icons to display, for the gtk3, tcl, and trayicon front-ends. + +`Section parameters` + +size (integer) + The height and width, in pixels, of the icons for the gtk3 and tcl frontends. + Default: 24 + +theme + The gtk3 icon theme. Options include your currently installed themes in `/usr/share/icons`, or the string *default* which loads the current gtk3 theme as defined in `${HOME}/.config/gtk-3.0/settings.ini`, value `gtk-icon-theme-name`. + Default: default + +lock, logout, hibernate, shutdown, reboot + Each of these entries can be given an icon name, e.g., "system-reboot", or a full path to a specific file. + Defaults: + + lock = system-lock-screen + logout = system-log-out + hibernate = system-hibernate + shutdown = system-shutdown + reboot = system-reboot +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager`(7) +Icon theme specification `http://www.freedesktop.org/wiki/Specifications/icon-theme-spec/` diff --git a/src/usr/share/man/man7/logout-manager.7.md b/src/usr/share/man/man7/logout-manager.7.md new file mode 100644 index 0000000..de4d05e --- /dev/null +++ b/src/usr/share/man/man7/logout-manager.7.md @@ -0,0 +1,45 @@ +logout-manager 7 "April 2020" logout-manager "Common configuration options" +================================================== +# NAME +logout-manager - common configuration options +# SYNOPSIS +This program manages a list of options for common logout commands, and can be used by a number of front-ends, including: + +`logout-manager-cli`(1) +`logout-manager-gtk`(1) +`logout-manager-ncurses`(1) +`logout-manager-tcl`(1) +`logout-manager-trayicon`(1) + +Of the listed front-ends, the trayicon is not recommended for aliasing to the generic name `logout-manager`. + +The library for logout-manager uses a number of environment variables; see below. +# ENVIRONMENT VARIABLES +If the defaults file (nominally `/etc/sysconfig/logout-manager`) does not define the following variables, you can define them in your own situation. + +`LOGOUT_MANAGER_LIBPATH`=/usr/share/logout-manager + +`LOGOUT_MANAGER_CONF`=/etc/logout-manager.conf + +Additional environment variables are used. +`DRYRUN`=1 If DRYRUN is set to any non-null value, the actions will instead only display what would be executed and will not execute the commands. + +# ACTIONS +An action is one of the following. + +**lock** hide and password-protect current screen + +**logout** close this graphical session + +**hibernate** save session memory to disk and power off. Note that not all hardware supports this. + +**shutdown** power off + +**reboot** restarts the system + +# AUTHOR +bgstack15 `https://bgstack15.wordpress.com/` +# COPYRIGHT +CC-BY-SA 4.0 +# SEE ALSO +`logout-manager.conf`(5),`logout-manager-cli`(1),`logout-manager-gtk`(1),`logout-manager-ncurses`(1),`logout-manager-tcl`(1),`logout-manager-trayicon`(1), -- cgit From fdd4d7e29cd9d11bf9b03b28b2f3ae0eaf5f31c9 Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 2 Apr 2020 09:06:07 -0400 Subject: add changelog to readme --- src/usr/share/doc/logout-manager/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/usr/share/doc/logout-manager/README.md b/src/usr/share/doc/logout-manager/README.md index 112d5f6..53c2713 100644 --- a/src/usr/share/doc/logout-manager/README.md +++ b/src/usr/share/doc/logout-manager/README.md @@ -38,3 +38,12 @@ This project is partially a programming playground for the [original author](htt ## Downsides * This whole thing is more complex than just logging out of my user session, and selecting a logout-type action from the display manager. * Depends on sudo instead of using native tools. + +## Changelog +### 0.0.3 +* 2020-04-02 +* Add man pages +* drop .py endings +* Adapt to python 3.8 + * fix [#2](https://gitlab.com/bgstack15/logout-manager/-/issues/2) AttributeError: module 'platform' has no attribute 'dist' + * fix [#3](https://gitlab.com/bgstack15/logout-manager/-/issues/3) for Devuan Ceres 4: platform.platform() does not show "Devuan" -- cgit From 7f6237ffdb4ce899efbb9442a1e3ebee8634810f Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 2 Apr 2020 13:00:58 -0400 Subject: fix minor doc and build issues --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 371e499..41f8b69 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ debian/logout-manager/ debian/files *.substvars gitmessage +/debian -- cgit From d55c9d99b1268b16c019ca89ba516b2c5f93232f Mon Sep 17 00:00:00 2001 From: B Stack Date: Thu, 2 Apr 2020 13:48:50 -0400 Subject: actually apply those fixes --- src/Makefile | 3 +-- src/usr/bin/logout-manager-trayicon | 4 ++-- src/usr/share/logout-manager/lmlib.py | 2 +- src/usr/share/man/man7/logout-manager.7.md | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Makefile b/src/Makefile index 009da93..28b65c8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -98,8 +98,7 @@ ifeq ($(md2manbin),) @false endif ${installbin} -d ${MANDIR}/man1 ${MANDIR}/man5 ${MANDIR}/man7 - @#${md2manbin} < ${SRCDIR}/usr/share/man/man6/${APPNAME}.6.md | ${gzipbin} > ${MANDIR}/man6/${APPNAME}.6.gz - for tm in $$( ${findbin} ${SRCDIR}/usr/share/man ! -type d -name '*[0-9].md' -printf '%P\n' ) ; do ${md2manbin} < ${SRCDIR}/usr/share/man/$${tm} > ${MANDIR}/$${tm%%.md}.gz ; done + for tm in $$( ${findbin} ${SRCDIR}/usr/share/man ! -type d -name '*[0-9].md' -printf '%P\n' ) ; do ${md2manbin} < ${SRCDIR}/usr/share/man/$${tm} | ${gzipbin} > ${MANDIR}/$${tm%%.md}.gz ; done uninstall: @${echobin} SRCDIR=${SRCDIR} diff --git a/src/usr/bin/logout-manager-trayicon b/src/usr/bin/logout-manager-trayicon index 7a6fd25..4aed3b5 100755 --- a/src/usr/bin/logout-manager-trayicon +++ b/src/usr/bin/logout-manager-trayicon @@ -20,8 +20,8 @@ # 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 +# dep-pip: psutil, distro +# dep-devuan: python3-psutil, python3-distro import gi, os, re, sys, psutil, signal from distro import linux_distribution diff --git a/src/usr/share/logout-manager/lmlib.py b/src/usr/share/logout-manager/lmlib.py index ba4980b..3bcbc32 100644 --- a/src/usr/share/logout-manager/lmlib.py +++ b/src/usr/share/logout-manager/lmlib.py @@ -13,7 +13,7 @@ # Improve: # Documentation: # Dependencies: -# dep-devuan: python3-distro +# dep-devuan: python3-distro, python3:any import configparser, os, subprocess from distro import linux_distribution diff --git a/src/usr/share/man/man7/logout-manager.7.md b/src/usr/share/man/man7/logout-manager.7.md index de4d05e..339d99b 100644 --- a/src/usr/share/man/man7/logout-manager.7.md +++ b/src/usr/share/man/man7/logout-manager.7.md @@ -11,7 +11,7 @@ This program manages a list of options for common logout commands, and can be us `logout-manager-tcl`(1) `logout-manager-trayicon`(1) -Of the listed front-ends, the trayicon is not recommended for aliasing to the generic name `logout-manager`. +Of the listed front-ends, the trayicon and cli are not recommended for aliasing to the short name `logout-manager`. The library for logout-manager uses a number of environment variables; see below. # ENVIRONMENT VARIABLES @@ -42,4 +42,4 @@ bgstack15 `https://bgstack15.wordpress.com/` # COPYRIGHT CC-BY-SA 4.0 # SEE ALSO -`logout-manager.conf`(5),`logout-manager-cli`(1),`logout-manager-gtk`(1),`logout-manager-ncurses`(1),`logout-manager-tcl`(1),`logout-manager-trayicon`(1), +`logout-manager.conf`(5), `logout-manager-cli`(1), `logout-manager-gtk`(1), `logout-manager-ncurses`(1), `logout-manager-tcl`(1), `logout-manager-trayicon`(1) -- cgit From 03a6e58ff8754567fc8b6daca10fe241935572ba Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 3 Apr 2020 08:43:52 -0400 Subject: fix #4 fix rest of frontends for py3.8 distro --- src/usr/bin/logout-manager-cli | 8 +++++--- src/usr/bin/logout-manager-gtk | 6 ++++-- src/usr/bin/logout-manager-ncurses | 6 ++++-- src/usr/bin/logout-manager-tcl | 7 ++++--- src/usr/bin/logout-manager-trayicon | 1 + 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/usr/bin/logout-manager-cli b/src/usr/bin/logout-manager-cli index fde7d55..c325db4 100755 --- a/src/usr/bin/logout-manager-cli +++ b/src/usr/bin/logout-manager-cli @@ -13,16 +13,17 @@ # https://stackoverflow.com/questions/3061/calling-a-function-of-a-module-by-using-its-name-a-string/12025554#12025554 # Improve: # Dependencies: -# Devuan: python3-dotenv python3 +# dep-devuan: python3-dotenv, python3 # Documentation: -import os, platform, sys, argparse +import os, sys, argparse +from distro import linux_distribution from dotenv import load_dotenv # all this to load the libpath try: defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() + 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 @@ -32,6 +33,7 @@ except: 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 eprint(*args, **kwargs): diff --git a/src/usr/bin/logout-manager-gtk b/src/usr/bin/logout-manager-gtk index 389ad70..400594d 100755 --- a/src/usr/bin/logout-manager-gtk +++ b/src/usr/bin/logout-manager-gtk @@ -25,10 +25,11 @@ # support global conf file, and user conf file # far future: provide graphical way to change commands run # Dependencies: -# Devuan: python3-dotenv +# dep-devuan: python3-dotenv # Documentation: import gi, os, platform, sys +from distro import linux_distribution gi.require_version("Gtk", "3.0") from gi.repository import Gtk from gi.repository import Gdk @@ -39,7 +40,7 @@ from dotenv import load_dotenv # all this to load the libpath try: defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() + 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 @@ -49,6 +50,7 @@ except: 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 # graphical classes and functions diff --git a/src/usr/bin/logout-manager-ncurses b/src/usr/bin/logout-manager-ncurses index 7ff5e18..67b18d7 100755 --- a/src/usr/bin/logout-manager-ncurses +++ b/src/usr/bin/logout-manager-ncurses @@ -20,13 +20,14 @@ # accepts enabled attribute # add "zeroindex" bool -import curses, os, platform, sys +import curses, os, sys +from distro import linux_distribution from dotenv import load_dotenv # all this to load the libpath try: defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() + 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 @@ -36,6 +37,7 @@ except: 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 class CursesMenu(object): diff --git a/src/usr/bin/logout-manager-tcl b/src/usr/bin/logout-manager-tcl index 17e0eed..9cadc5c 100755 --- a/src/usr/bin/logout-manager-tcl +++ b/src/usr/bin/logout-manager-tcl @@ -26,11 +26,11 @@ # Devuan: python3-tk python3-pil.imagetk python3-cairosvg # el7: python36-tkinter python36-pillow-tk ( pip3 install cairosvg ) -import glob, os, platform, re, sys +import glob, os, re, sys import tkinter as tk +from distro import linux_distribution 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 @@ -46,7 +46,7 @@ except: # all this to load the libpath try: defaultdir="/etc/sysconfig" - thisplatform = platform.platform().lower() + 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 @@ -56,6 +56,7 @@ except: 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 # graphical classes and functions diff --git a/src/usr/bin/logout-manager-trayicon b/src/usr/bin/logout-manager-trayicon index 4aed3b5..9eaaa32 100755 --- a/src/usr/bin/logout-manager-trayicon +++ b/src/usr/bin/logout-manager-trayicon @@ -43,6 +43,7 @@ except: 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(): -- cgit From 923e9f3642ab6c982c0195b366ceec85ce24007f Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 3 Apr 2020 09:11:57 -0400 Subject: fix #5 cli: multiple problems Option -n now acts as a dry run. The man page -V option is documented correctly now. --- src/usr/bin/logout-manager-cli | 7 ++++++- src/usr/share/man/man1/logout-manager-cli.1.md | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/usr/bin/logout-manager-cli b/src/usr/bin/logout-manager-cli index c325db4..974fd4b 100755 --- a/src/usr/bin/logout-manager-cli +++ b/src/usr/bin/logout-manager-cli @@ -6,6 +6,7 @@ # Title: cli logout manager # Purpose: Feature completeness in this package # History: +# 2020-04-03 fix #5 -n does nothing # Usage: # logout-manager-cli # Reference: @@ -39,7 +40,7 @@ import lmlib def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -logout_manager_cli_version="2020-03-10" +logout_manager_cli_version="2020-04-03" parser = argparse.ArgumentParser(description="run logout-manager commands using cli") parser.add_argument('action', help='which action to take',nargs='?', choices=('lock','logout','hibernate','shutdown','reboot')) @@ -49,6 +50,10 @@ parser.add_argument("-V","--version", action="version", version="%(prog)s " + lo args = parser.parse_args() +# handle -n +if args.dryrun: + os.environ["DRYRUN"] = "from-parameters" + # load configs # in cli, must happen after arparse to benefit from debug value config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) diff --git a/src/usr/share/man/man1/logout-manager-cli.1.md b/src/usr/share/man/man1/logout-manager-cli.1.md index a775770..d4cb02c 100644 --- a/src/usr/share/man/man1/logout-manager-cli.1.md +++ b/src/usr/share/man/man1/logout-manager-cli.1.md @@ -14,7 +14,7 @@ Use this interface to `logout-manager`(7) from the command line or in scripts. P -n Dryrun only! Do not take action. Useful with `hibernate` to determine if hibernate is allowed on this system. --V --verbose be verbose +-V --version Display version and exit # ACTIONS One action may be specified on the command line. See "ACTIONS" section of `logout-manager`(7) man page for a list of available actions. # AUTHOR -- cgit From b3b61e8d9c84a93b04e0d33e402cba8df897ab5c Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 3 Apr 2020 09:20:53 -0400 Subject: fix #6 xscreensaver invocation is incorrect It is now xscreensaver-command -lock --- src/usr/libexec/logout-manager/lm-helper | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/usr/libexec/logout-manager/lm-helper b/src/usr/libexec/logout-manager/lm-helper index 6372827..700e86f 100755 --- a/src/usr/libexec/logout-manager/lm-helper +++ b/src/usr/libexec/logout-manager/lm-helper @@ -1,7 +1,7 @@ #!/bin/sh # Dependencies: -# Devuan: wmctrl sudo -# el7: wmctrl sudo +# dep-devuan: wmctrl, sudo +# dep-el7: wmctrl, sudo case "${1}" in help) # show this help screen { @@ -18,9 +18,9 @@ case "${1}" in lock) # lock the current screen if test -z "${DRYRUN}" ; then - xscreensaver --locknow + xscreensaver-command -lock else - echo "xscreensaver --locknow" + echo "xscreensaver-command -lock" fi ;; logout) # log out the current user of the graphical session @@ -36,7 +36,7 @@ case "${1}" in fi ;; *) - echo "Gotta say unh! Feature not yet implemented for \"${_wm}\"" 1>&2 + echo "Gotta say unh! Feature not yet implemented for \"${_wm}\". Please report this to bgstack15@gmail.com" 1>&2 exit 1 ;; esac -- cgit From 85fee7e775d21361681bf0413d729f872ec1d710 Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 3 Apr 2020 10:15:16 -0400 Subject: bump version to 0.0.4 --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 28b65c8..6407823 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,7 +20,7 @@ # exclude-devuan: go-md2man APPNAME = logout-manager -APPVERSION = 0.0.3 +APPVERSION = 0.0.4 SRCDIR = $(CURDIR) prefix = /usr SYSCONFDIR = $(DESTDIR)/etc -- cgit