From 94a2b01ee3b85278361899ac959eb9f2aaf26119 Mon Sep 17 00:00:00 2001 From: Adrian Iain Lam Date: Mon, 18 Sep 2017 05:20:09 +0800 Subject: [PATCH] Add checks to run on different env e.g. GTK<3.18 or without xdotool - Scroll lock hidden if GTK < 3.18 - drop-down menu hidden (except quit) if xdotool not found / not executable - custom xdotool path can be specified Add version number to prepare for future .deb packaging plan Fix drop-down menu creation to not create unnecessary items (e.g. those not listed in ORDER) Move blocks of code around for better encapsulation --- README.md | 24 +++++++------ indicator-keyboard-led.py | 87 +++++++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e8f123c..c7f7e56 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ distributions. To install the rest, run: **Note**: [`gdk_keymap_get_scroll_lock_state ()`][gtkdoc-scroll] is only available since GTK+ 3.18. The earliest Ubuntu release that supports -GTK+ 3.18 is 16.04 (Xenial). This means that if you use an older version -you will have to ignore Scroll lock functionality (See [Examples](#examples)). +GTK+ 3.18 is 16.04 (Xenial). This means that if you use an older version, +scroll lock functionality will be disabled. [gtkdoc-scroll]: https://developer.gnome.org/gdk3/stable/gdk3-Keyboard-Handling.html#gdk-keymap-get-scroll-lock-state @@ -66,7 +66,7 @@ Order changed to Caps Num Scroll. `python3 indicator-keyboard-led.py --order CNS` ![indicator NC][sc5] -Hide Scroll lock. `python3 indicator-keyboard-led.py --order NC` +Hide Scroll lock, also the default appearance if Scroll lock is not supported. `python3 indicator-keyboard-led.py --order NC` ![indicator C short][sc6] Show Caps lock only, short appearance. @@ -84,7 +84,7 @@ Default appearance in a French locale. [sc6]: screenshots/sc6.png [sc7]: screenshots/sc7.png -## Known bugs +## Known bugs / Troubleshooting ### Pressing Scroll Lock does nothing @@ -119,15 +119,19 @@ modified. [Original source][origsrc] by dm+ on PCLinuxOS-Forums. [origsrc]: http://www.pclinuxos.com/forum/index.php/topic,125690.msg1052201.html?PHPSESSID=2qsv83lve6dgd0ivq14bfcjc30#msg1052201 [quotesrc]: http://askubuntu.com/a/597757/274080 -### X11Keymap has no attribute 'get_scroll_lock_state' +### Scroll lock does not appear on indicator -If running the script gives the error message +Your installed GTK+ version is probably older than 3.18, which +does not support `get_scroll_lock_state`. Please consider upgrading +your system if you really want Scroll lock functionality. - AttributeError: 'gtk.gdk.X11Keymap' object has no attribute 'get_scroll_lock_state' +If your installed GTK+ is 3.18+ then please file a bug report. -then your installed GTK+ version is probably older than 3.18, which -does not support `get_scroll_lock_state`. Either hide all Scroll Lock -functionality (See [Examples](#examples)), or upgrade your system. +### Drop-down menu only has "Quit", the clickable locks do not appear + +Please verify that xdotool is installed in your PATH with the executable +bit set. If you provided a custom path to xdotool please verify that it +is correct and is an executable regular file. ## Localization diff --git a/indicator-keyboard-led.py b/indicator-keyboard-led.py index 86aa543..7f78bc6 100755 --- a/indicator-keyboard-led.py +++ b/indicator-keyboard-led.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # indicator-keyboard-led - simulate keyboard lock keys LED -# Copyright (c) 2016 Adrian I Lam +# Copyright (c) 2017 Adrian I Lam # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -28,52 +28,67 @@ import signal import subprocess -from os import path +import os import argparse -import sys +import shutil import gi gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') gi.require_version('AppIndicator3', '0.1') from gi.repository import Gdk, Gtk, AppIndicator3 -SCRIPT_DIR = path.dirname(path.realpath(__file__)) +APP_NAME = 'indicator-keyboard-led' +APP_VERSION = '1.1' + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) import gettext -t = gettext.translation('default', path.join(SCRIPT_DIR, 'locale')) +t = gettext.translation('default', os.path.join(SCRIPT_DIR, 'locale')) _ = t.gettext class IndicatorKeyboardLED: - locks = { 'N': _('Num'), 'C': _('Caps'), 'S': _('Scroll') } - - def __init__(self, short=False, order='NCS'): + def __init__(self, short=False, order='NCS', xdotool=None): + self.validate_order(order) + self.indicator = AppIndicator3.Indicator.new( - 'indicator-keyboard-led', - path.join(SCRIPT_DIR, 'icon.svg'), + APP_NAME, os.path.join(SCRIPT_DIR, 'icon.svg'), AppIndicator3.IndicatorCategory.APPLICATION_STATUS) self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) if short: self.locks = { 'N': _('N'), 'C': _('C'), 'S': _('S') } + else: + self.locks = { 'N': _('Num'), 'C': _('Caps'), 'S': _('Scroll') } keymap = Gdk.Keymap.get_default() keymap.connect('state-changed', self.update_indicator, order) self.update_indicator(keymap, order) + self.create_menu(xdotool, order) + def create_menu(self, xdotool, order): menu = Gtk.Menu() - items = { - 'N': Gtk.MenuItem.new_with_label(_('Num Lock')), - 'C': Gtk.MenuItem.new_with_label(_('Caps Lock')), - 'S': Gtk.MenuItem.new_with_label(_('Scroll Lock')) - } - items['N'].connect('activate', self.send_keypress, 'Num_Lock') - items['C'].connect('activate', self.send_keypress, 'Caps_Lock') - items['S'].connect('activate', self.send_keypress, 'Scroll_Lock') + xdotool = xdotool or shutil.which('xdotool') + if xdotool and os.access(xdotool, os.X_OK) and os.path.isfile(xdotool): + def send_keypress(menuitem, keystroke): + subprocess.call([xdotool, 'key', keystroke]) + def new_menu_item(itemtype): + if itemtype == 'N': + item = Gtk.MenuItem.new_with_label(_('Num Lock')) + item.connect('activate', send_keypress, 'Num_Lock') + elif itemtype == 'C': + item = Gtk.MenuItem.new_with_label(_('Caps Lock')) + item.connect('activate', send_keypress, 'Caps_Lock') + elif itemtype == 'S': + item = Gtk.MenuItem.new_with_label(_('Scroll Lock')) + item.connect('activate', send_keypress, 'Scroll_Lock') + else: + raise ValueError('Invalid itemtype') + return item - for i in order: - menu.append(items[i]) + for i in order: + menu.append(new_menu_item(i)) + menu.append(Gtk.SeparatorMenuItem()) quit_item = Gtk.MenuItem.new_with_label(_('Quit')) - menu.append(Gtk.SeparatorMenuItem()) menu.append(quit_item) quit_item.connect('activate', Gtk.main_quit) @@ -94,17 +109,19 @@ class IndicatorKeyboardLED: labels += [('⚫' if state else '⚪') + self.locks[i]] self.indicator.set_label(' '.join(labels), '') - def send_keypress(self, menuitem, keystroke): - subprocess.call(['xdotool', 'key', keystroke]) + def validate_order(self, order): + order = order.upper() + for i in order: + if i not in ['N', 'C', 'S']: + raise ValueError('Illegal character in ORDER. ' + '(Choices: [N, C, S])') + if len(order) != len(set(order)): + raise ValueError('Repeated character in ORDER. ' + 'Please specify each lock at most once.') + if 'S' in order and Gtk.check_version(3, 18, 0) is not None: + # Silently drop Scroll lock if GTK <= 3.18 + order.remove('S') -def validate_order(args): - args.order = args.order.upper() - for i in args.order: - if i not in ['N', 'C', 'S']: - sys.exit('Illegal character in ORDER. (Choices: [N, C, S])') - if len(args.order) != len(set(args.order)): - sys.exit('Repeated character in ORDER. ' - 'Please specify each lock at most once.') if __name__ == '__main__': signal.signal(signal.SIGINT, lambda signum, frame: Gtk.main_quit()) @@ -117,8 +134,12 @@ if __name__ == '__main__': parser.add_argument('-o', '--order', required=False, default='NCS', help='specify the order of the locks displayed, e.g. CSN for ' '⚫Caps ⚫Scroll ⚫Num, or NC for ⚫Num ⚫Caps without Scroll lock') + parser.add_argument('-x', '--xdotool', required=False, + help='provide full path to xdotool executable, ' + 'e.g. "/usr/bin/xdotool"; ' + 'if not specified, search in PATH') args = parser.parse_args() - validate_order(args) - IndicatorKeyboardLED(short=args.short, order=args.order) + IndicatorKeyboardLED(short=args.short, order=args.order, + xdotool=args.xdotool) Gtk.main() -- 2.7.4