diff options
34 files changed, 706 insertions, 19 deletions
@@ -1,3 +1,8 @@ *.2019-* *.swp __pycache__ +*debhelper* +debian/logout-manager/ +debian/files +*.substvars +gitmessage diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa626bb --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Overview for logout-manager +See the [full readme](src/usr/share/doc/logout-manager/README.md) farther down in the source tree. diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..810fad6 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,5 @@ +logout-manager for Devuan + +No changes + + -- Ben Stack <bgstack15@gmail.com> Wed, 11 Mar 2020 08:38:11 -0400 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..b206280 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +logout-manager (0.0.1-1) obs; urgency=low + + * Initial release. + + -- Ben Stack <bgstack15@gmail.com> Wed, 11 Mar 2020 08:38:11 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +12 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..f7edf05 --- /dev/null +++ b/debian/control @@ -0,0 +1,17 @@ +Source: logout-manager +Section: x11 +Priority: optional +Maintainer: Ben Stack <bgstack15@gmail.com> +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 new file mode 100644 index 0000000..6b39eaf --- /dev/null +++ b/debian/copyright @@ -0,0 +1,29 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: logout-manager +Source: <url://example.com> +# +# 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 new file mode 100644 index 0000000..e752d8b --- /dev/null +++ b/debian/do-not-install @@ -0,0 +1 @@ +usr/bin/logout-manager diff --git a/debian/logout-manager.conffiles b/debian/logout-manager.conffiles new file mode 100644 index 0000000..65e8592 --- /dev/null +++ b/debian/logout-manager.conffiles @@ -0,0 +1,2 @@ +etc/logout-manager.conf +etc/default/logout-manager diff --git a/debian/logout-manager.dsc b/debian/logout-manager.dsc new file mode 100644 index 0000000..63ef881 --- /dev/null +++ b/debian/logout-manager.dsc @@ -0,0 +1,14 @@ +Format: 3.0 (quilt) +Source: logout-manager +Binary: logout-manager +Architecture: all +Version: 0.0.1-1 +Maintainer: Ben Stack <bgstack15@gmail.com> +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 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/debian/logout-manager.install diff --git a/debian/logout-manager.lintian-overrides b/debian/logout-manager.lintian-overrides new file mode 100644 index 0000000..01f15e1 --- /dev/null +++ b/debian/logout-manager.lintian-overrides @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000..b98d697 --- /dev/null +++ b/debian/logout-manager.postinst @@ -0,0 +1,9 @@ +#!/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 new file mode 100644 index 0000000..c0c50d3 --- /dev/null +++ b/debian/logout-manager.prerm @@ -0,0 +1,11 @@ +#!/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 new file mode 100644 index 0000000..4a97dfa --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +# You must remove unused comment lines for the released package. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..c1fee5d --- /dev/null +++ b/debian/rules @@ -0,0 +1,18 @@ +#!/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 new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides new file mode 100644 index 0000000..2da37ed --- /dev/null +++ b/debian/source/lintian-overrides @@ -0,0 +1,2 @@ +file-without-copyright-information +missing-license-paragraph-in-dep5-copyright diff --git a/debian/source/local-options b/debian/source/local-options new file mode 100644 index 0000000..00131ee --- /dev/null +++ b/debian/source/local-options @@ -0,0 +1,2 @@ +#abort-on-upstream-changes +#unapply-patches diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..fc70498 --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +# You must remove unused comment lines for the released package. +version=4 diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..96607d2 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,94 @@ +# File: Makefile for logout-manager +# Location: logout-manager source package +# Author: bgstack15 +# Startdate: 2020-03-10 +# Title: Makefile for logout-manager source package +# Purpose: To use traditional Unix make utility +# History: +# Usage: +# Reference: +# https://stackoverflow.com/questions/4219255/how-do-you-get-the-list-of-targets-in-a-makefile/26339924#26339924 +# https://stackoverflow.com/questions/19105241/how-do-you-conditionally-call-a-target-based-on-a-target-variable-makefile/19107231#19107231 +# https://stackoverflow.com/questions/5917576/sort-a-text-file-by-line-length-including-spaces +# https://superuser.com/questions/352289/bash-scripting-test-for-empty-directory/667100#667100 +# bgscripts Makefile +# Improve: +# Document: +# Includes a nice way to dynamically generate dependencies as self-reported by all the files. +# Dependencies: + +APPNAME = logout-manager +APPVERSION = 0.0.1 +SRCDIR = $(CURDIR) +prefix = /usr +SYSCONFDIR = $(DESTDIR)/etc +DEFAULTDIR = $(DESTDIR)/etc/sysconfig +# for debian use '$(DESTDIR)/etc/default' +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 +SUDOERSDIR = $(SYSCONFDIR)/sudoers.d + +awkbin :=$(shell which awk) +cpbin :=$(shell which cp) +echobin :=$(shell which echo) +findbin :=$(shell which find) +grepbin :=$(shell which grep) +installbin :=$(shell which install) +lnbin :=$(shell which ln) +rmbin :=$(shell which rm) +sedbin :=$(shell which sed) +sortbin :=$(shell which sort) +truebin :=$(shell which true) +uniqbin :=$(shell which uniq) +xargsbin :=$(shell which xargs) + +all: + ${echobin} "No compilation in this package." + +.PHONY: clean install uninstall list deplist deplist_opts + +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_opts: + @${echobin} "el7" 1>&2 + @${echobin} "devuan" 1>&2 + +install: + @${echobin} Installing files to ${DESTDIR} + ${installbin} -d ${SYSCONFDIR} ${DEFAULTDIR} ${BINDIR} \ + ${APPSDIR} ${APPDIR} ${DOCDIR} ${BASHCDIR} ${SUDOERSDIR} \ + ${LIBEXECDIR}/${APPNAME} + ${cpbin} -pr ${SRCDIR}/etc/*.* ${SYSCONFDIR} + ${cpbin} -pr ${SRCDIR}/etc/sysconfig/* ${DEFAULTDIR} + ${cpbin} -pr ${SRCDIR}/usr/bin/* ${BINDIR} + ${cpbin} -pr ${SRCDIR}/usr/share/applications/* ${APPSDIR} + ${cpbin} -pr ${SRCDIR}/usr/share/${APPNAME}/*.* ${APPDIR} + ${cpbin} -pr ${SRCDIR}/usr/share/doc/${APPNAME}/* ${DOCDIR} + ${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}/* + # symlink, when alternatives is not being used + ${lnbin} -s logout-manager-gtk.py ${BINDIR}/logout-manager + +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} + + # 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." diff --git a/src/etc/logout-manager.conf b/src/etc/logout-manager.conf new file mode 100644 index 0000000..51b86d1 --- /dev/null +++ b/src/etc/logout-manager.conf @@ -0,0 +1,16 @@ +[logout-manager] +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" + +[icons] +size = 24 +#theme = default +# use names as used by the icon theme, or give a full path here +hibernate = system-hibernate +lock = system-lock-screen +logout = system-log-out +reboot = system-reboot +shutdown = system-shutdown diff --git a/src/etc/sudoers.d/30_logout-manager_sudo b/src/etc/sudoers.d/30_logout-manager_sudo new file mode 100644 index 0000000..ba621eb --- /dev/null +++ b/src/etc/sudoers.d/30_logout-manager_sudo @@ -0,0 +1,3 @@ +# File: /etc/sudoers.d/30_logout-manager_sudo +Defaults env_keep += "DRYRUN VERBOSE" +ALL ALL = (root) NOPASSWD: /usr/libexec/logout-manager/lm-helper * diff --git a/src/etc/sysconfig/logout-manager b/src/etc/sysconfig/logout-manager new file mode 100644 index 0000000..1f4ecf6 --- /dev/null +++ b/src/etc/sysconfig/logout-manager @@ -0,0 +1,2 @@ +LOGOUT_MANAGER_LIBPATH=/usr/share/logout-manager +LOGOUT_MANAGER_CONF=/etc/logout-manager.conf diff --git a/src/usr/bin/logout-manager-cli.py b/src/usr/bin/logout-manager-cli.py new file mode 100755 index 0000000..64ea133 --- /dev/null +++ b/src/usr/bin/logout-manager-cli.py @@ -0,0 +1,69 @@ +#!/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) +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/logout-manager-gtk.py b/src/usr/bin/logout-manager-gtk.py index c6f7a6b..553fc41 100755 --- a/logout-manager-gtk.py +++ b/src/usr/bin/logout-manager-gtk.py @@ -24,15 +24,31 @@ # 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, sys +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 -sys.path.append("/home/bgirton/dev/logout-manager") +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 @@ -225,7 +241,8 @@ class MainWindow(Gtk.Window): print("Cancel any logout action.") Gtk.main_quit() -config = lmlib.Initialize_config() +# load configs +config = lmlib.Initialize_config(os.environ['LOGOUT_MANAGER_CONF']) actions = lmlib.Actions # MAIN LOOP diff --git a/src/usr/bin/logout-manager-ncurses.py b/src/usr/bin/logout-manager-ncurses.py new file mode 100755 index 0000000..1500d85 --- /dev/null +++ b/src/usr/bin/logout-manager-ncurses.py @@ -0,0 +1,193 @@ +#!/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/logout-manager-tcl.py b/src/usr/bin/logout-manager-tcl.py index 8c4bc6a..127bd54 100755 --- a/logout-manager-tcl.py +++ b/src/usr/bin/logout-manager-tcl.py @@ -7,6 +7,7 @@ # 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 @@ -22,18 +23,17 @@ # 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 +# Devuan: python3-tk python3-pil.imagetk python3-cairosvg # el7: python36-tkinter python36-pillow-tk ( pip3 install cairosvg ) -import glob, re +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 -path.append("/home/bgirton/dev/logout-manager") -import lmlib LM_USE_SVG = 0 try: @@ -43,8 +43,20 @@ except: print("WARNING: Unable to import cairosvg. No svg images will be displayed.") LM_USE_SVG = 0 -config = lmlib.Initialize_config() -actions = lmlib.Actions +# 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...") @@ -388,9 +400,12 @@ class App: #def something(event=None): # print("Got here!") -root = tk.Tk() +# 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) diff --git a/src/usr/libexec/logout-manager/lm-helper b/src/usr/libexec/logout-manager/lm-helper new file mode 100755 index 0000000..6372827 --- /dev/null +++ b/src/usr/libexec/logout-manager/lm-helper @@ -0,0 +1,74 @@ +#!/bin/sh +# Dependencies: +# Devuan: wmctrl sudo +# el7: wmctrl sudo +case "${1}" in + help) # show this help screen + { + echo "Usage: ${0}: [command]" + echo "used by logout-manager to perform actions like reboot, lock screen, etc." + echo "" + echo "Commands:" + grep -E '^\s{3}[A-Za-z]+\)' "${0}" | tr -dc '[A-Za-z\n ]' | sed -r -e 's/\s+/ /g;' | grep -v "HIDDEN\s*$" | while read a therest ; do echo " ${a}: ${therest}" ; done + } + ;; + options) # used by bash_completion function HIDDEN + grep -E '^\s{3}[A-Za-z]+\)' "${0}" | awk '{print $1}' | tr -dc '[A-Za-z\n]' | grep -vE 'options|help' + ;; + lock) # lock the current screen + if test -z "${DRYRUN}" ; + then + xscreensaver --locknow + else + echo "xscreensaver --locknow" + fi + ;; + logout) # log out the current user of the graphical session + # determine DE/WM and act accordingly + _wm="$( wmctrl -m | awk '/Name:/{$1="";print;}' | xargs )" + case "${_wm}" in + Fluxbox) + if test -z "${DRYRUN}" ; + then + fluxbox-remote exit + else + echo "fluxbox-remote exit" + fi + ;; + *) + echo "Gotta say unh! Feature not yet implemented for \"${_wm}\"" 1>&2 + exit 1 + ;; + esac + ;; + hibernate) # save system state to disk and power off + # this method is linux only + if test -z "${DRYRUN}" ; + then + printf 'disk' | tee /sys/power/state + else + echo "printf 'disk' | tee /sys/power/state" + fi + ;; + shutdown) # power off + if test -z "${DRYRUN}" ; + then + shutdown -h now + else + echo "shutdown -h now" + fi + ;; + reboot) # restart the system + if test -z "${DRYRUN}" ; + then + shutdown -r now + else + echo "shutdown -r now" + fi + ;; + *) # HIDE + echo "invalid choice: ${1}" 1>&2 + exit 1 + ;; +esac +: diff --git a/src/usr/share/applications/logout-manager.desktop b/src/usr/share/applications/logout-manager.desktop new file mode 100644 index 0000000..4522a66 --- /dev/null +++ b/src/usr/share/applications/logout-manager.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Settings;HardwareSettings; +Comment=Prompt for common actions including lock screen, logout, etc. +Exec=/usr/bin/logout-manager +GenericName=Logout menu +Icon=system-log-out +Keywords=shutdown;hibernate;lockscreen;logout;reboot; +Name=Logout... +StartupNotify=true +Terminal=false +Type=Application +Version=1.0 diff --git a/src/usr/share/bash-completion/completions/logout-manager b/src/usr/share/bash-completion/completions/logout-manager new file mode 100644 index 0000000..fd1267f --- /dev/null +++ b/src/usr/share/bash-completion/completions/logout-manager @@ -0,0 +1,12 @@ +# File: /etc/bash_completion.d/logout-manager +# Reference: +# bgscripts-core: /usr/bin/bp +# man complete + +_lm_helper() { + local cur prev words cword; + _init_completion || return + COMPREPLY=($( compgen -W "$( ~/dev/logout-manager/src/usr/libexec/logout-manager/lm-helper options )" -- "$cur" )) + return 0 +} && \ +complete -F _lm_helper -o bashdefault lm-helper diff --git a/src/usr/share/doc/logout-manager/README.md b/src/usr/share/doc/logout-manager/README.md new file mode 100644 index 0000000..d34f02c --- /dev/null +++ b/src/usr/share/doc/logout-manager/README.md @@ -0,0 +1,40 @@ +# README for logout-manager +## Introduction +Logout Manager is a python3 utility that provides a simple menu for logout-type actions. The supported actions are presented: + * Lock + * Logout + * Hibernate (if supported by hardware) + * Shutdown + * Reboot + +## Customization +The `lm-helper` logout command needs to be customized for every desktop environment. Some may need extra configurationon the window manager/desktop environment side. + +### Fluxbox +For Fluxbox, you need to set a value in ~/.fluxbox/init + + session.screen0.allowRemoteActions: true + +Be aware that this is insecure. See man `fluxbox-remote(1)`. + +## Alternatives +[oblogout](https://launchpad.net/oblogout) looks really old so I did not investigate personally, but it sounds like it does the same thing I am trying to do. +`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). +Everything else is licensed under [CC-BY-SA 4.0](https://choosealicense.com/licenses/cc-by-sa-4.0/). + +## Description +This project is partially a programming playground for the [original author](https://bgstack15.wordpress.com) and partially a useful project for his migration to [Fluxbox](http://fluxbox.org/) on the desktop. + +## Upsides +* This project is the first to [demonstrate SVG images in tkinter in python3](https://bgstack15.wordpress.com/2019/07/13/display-svg-in-tkinter-python3/) that I could find on the Internet. +* This project demonstrates how to have the Makefile and debian/rules build a dependency list, from the Dependencies tags of the files themselves. +* I have learned how to work with ncurses, gtk, and tcl in python3. +* This will make Fluxbox systems easier to use for general users. +* Does not use dbus! + +## 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. diff --git a/logout-manager.conf b/src/usr/share/doc/logout-manager/logout-manager.conf.example index 1a14909..1a14909 100644 --- a/logout-manager.conf +++ b/src/usr/share/doc/logout-manager/logout-manager.conf.example diff --git a/lmlib.py b/src/usr/share/logout-manager/lmlib.py index fe6d4a4..40ee3a0 100644 --- a/lmlib.py +++ b/src/usr/share/logout-manager/lmlib.py @@ -13,36 +13,45 @@ # Improve: # Documentation: -import configparser, platform, os +import configparser, platform, os, subprocess -logout_manager_version="2019-06-21a" +logout_manager_version="2020-03-10a" class Actions: + def __take_action(command): + print(command) + command=str(command).split() + command2=[] + for i in command: + command2.append(str(i.strip('"'))) + command=command2 + subprocess.run(command) + @staticmethod def hibernate(config, event=None): #print("need to run the /sys/power/state trick, if available") - print(config.get_hibernate_command()) + Actions.__take_action(config.get_hibernate_command()) @staticmethod def lock(config, event=None): #print("please lock the screen.") - print(config.get_lock_command()) + Actions.__take_action(config.get_lock_command()) @staticmethod def logout(config, event=None): #print("please log out of current session!") - print(config.get_logout_command()) + Actions.__take_action(config.get_logout_command()) @staticmethod def reboot(config, event=None): #print("please reboot.") - print(config.get_reboot_command()) + Actions.__take_action(config.get_reboot_command()) @staticmethod def shutdown(config, event=None): #print("please shut yourself down!") - print(config.get_shutdown_command()) + Actions.__take_action(config.get_shutdown_command()) class Config: def __init__(self): @@ -192,10 +201,10 @@ def get_gtk3_default_icon_theme(): print("Found gtk3 default theme:",name) return name -def Initialize_config(): +def Initialize_config(infile): # Read config config_in = configparser.ConfigParser() - config_in.read('logout-manager.conf') + config_in.read(infile) config = Config() try: ci = config_in['logout-manager'] |