Edit code and rename files to prepare for .deb packaging
[indicator-keyboard-led.git] / indicator-keyboard-led.py
CommitLineData
fc1c0791
AIL
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# indicator-keyboard-led - simulate keyboard lock keys LED
94a2b01e 5# Copyright (c) 2017 Adrian I Lam <me@adrianiainlam.tk>
fc1c0791
AIL
6#
7# Permission is hereby granted, free of charge, to any person obtaining a
8# copy of this software and associated documentation files (the "Software"),
9# to deal in the Software without restriction, including without limitation
10# the rights to use, copy, modify, merge, publish, distribute, sublicense,
11# and/or sell copies of the Software, and to permit persons to whom the
12# Software is furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20# THE AUTHOR OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23# IN THE SOFTWARE.
24#
25# I would like to thank Tobias Schlitt <toby@php.net>, who wrote
26# indicator-chars <https://github.com/tobyS/indicator-chars> which I used
27# as a reference when writing this software.
28
29import signal
30import subprocess
94a2b01e 31import os
fc1c0791 32import argparse
94a2b01e 33import shutil
fc1c0791
AIL
34import gi
35gi.require_version('Gdk', '3.0')
36gi.require_version('Gtk', '3.0')
37gi.require_version('AppIndicator3', '0.1')
38from gi.repository import Gdk, Gtk, AppIndicator3
39
94a2b01e
AIL
40APP_NAME = 'indicator-keyboard-led'
41APP_VERSION = '1.1'
42
85c1429b 43ICON_LOCATION = '/usr/share/icons/hicolor/scalable/apps/' + APP_NAME + '.svg'
f462ecb9 44import gettext
85c1429b 45t = gettext.translation(APP_NAME, '/usr/share/locale')
f462ecb9
AIL
46_ = t.gettext
47
fc1c0791 48class IndicatorKeyboardLED:
94a2b01e
AIL
49 def __init__(self, short=False, order='NCS', xdotool=None):
50 self.validate_order(order)
51
fc1c0791 52 self.indicator = AppIndicator3.Indicator.new(
85c1429b 53 APP_NAME, ICON_LOCATION,
fc1c0791
AIL
54 AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
55 self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
170ff396 56
fc1c0791 57 if short:
f462ecb9 58 self.locks = { 'N': _('N'), 'C': _('C'), 'S': _('S') }
94a2b01e
AIL
59 else:
60 self.locks = { 'N': _('Num'), 'C': _('Caps'), 'S': _('Scroll') }
fc1c0791
AIL
61
62 keymap = Gdk.Keymap.get_default()
170ff396
AIL
63 keymap.connect('state-changed', self.update_indicator, order)
64 self.update_indicator(keymap, order)
94a2b01e 65 self.create_menu(xdotool, order)
fc1c0791 66
94a2b01e 67 def create_menu(self, xdotool, order):
fc1c0791 68 menu = Gtk.Menu()
94a2b01e
AIL
69 xdotool = xdotool or shutil.which('xdotool')
70 if xdotool and os.access(xdotool, os.X_OK) and os.path.isfile(xdotool):
71 def send_keypress(menuitem, keystroke):
72 subprocess.call([xdotool, 'key', keystroke])
73 def new_menu_item(itemtype):
74 if itemtype == 'N':
75 item = Gtk.MenuItem.new_with_label(_('Num Lock'))
76 item.connect('activate', send_keypress, 'Num_Lock')
77 elif itemtype == 'C':
78 item = Gtk.MenuItem.new_with_label(_('Caps Lock'))
79 item.connect('activate', send_keypress, 'Caps_Lock')
80 elif itemtype == 'S':
81 item = Gtk.MenuItem.new_with_label(_('Scroll Lock'))
82 item.connect('activate', send_keypress, 'Scroll_Lock')
83 else:
84 raise ValueError('Invalid itemtype')
85 return item
170ff396 86
94a2b01e
AIL
87 for i in order:
88 menu.append(new_menu_item(i))
89 menu.append(Gtk.SeparatorMenuItem())
fc1c0791 90
f462ecb9 91 quit_item = Gtk.MenuItem.new_with_label(_('Quit'))
170ff396
AIL
92 menu.append(quit_item)
93 quit_item.connect('activate', Gtk.main_quit)
fc1c0791
AIL
94
95 self.indicator.set_menu(menu)
96 menu.show_all()
97
170ff396
AIL
98 def update_indicator(self, keymap, order):
99 labels = []
100 for i in order:
101 if i == 'N':
102 state = keymap.get_num_lock_state()
103 elif i == 'C':
104 state = keymap.get_caps_lock_state()
105 elif i == 'S':
106 state = keymap.get_scroll_lock_state()
107 else:
108 raise ValueError('Invalid value in ORDER')
109 labels += [('⚫' if state else '⚪') + self.locks[i]]
110 self.indicator.set_label(' '.join(labels), '')
fc1c0791 111
94a2b01e
AIL
112 def validate_order(self, order):
113 order = order.upper()
114 for i in order:
115 if i not in ['N', 'C', 'S']:
116 raise ValueError('Illegal character in ORDER. '
117 '(Choices: [N, C, S])')
118 if len(order) != len(set(order)):
119 raise ValueError('Repeated character in ORDER. '
120 'Please specify each lock at most once.')
121 if 'S' in order and Gtk.check_version(3, 18, 0) is not None:
122 # Silently drop Scroll lock if GTK <= 3.18
123 order.remove('S')
fc1c0791 124
170ff396 125
fc1c0791
AIL
126if __name__ == '__main__':
127 signal.signal(signal.SIGINT, lambda signum, frame: Gtk.main_quit())
170ff396 128
fc1c0791 129 parser = argparse.ArgumentParser(
170ff396
AIL
130 description='indicator-keyboard-led - simulate keyboard lock keys LED',
131 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
f462ecb9
AIL
132 parser.add_argument('-s', '--short', required=False, action='store_true',
133 help='use short label, i.e. ⚫N ⚫C ⚫S instead of ⚫Num ⚫Caps ⚫Scroll')
170ff396
AIL
134 parser.add_argument('-o', '--order', required=False, default='NCS',
135 help='specify the order of the locks displayed, e.g. CSN for '
136 '⚫Caps ⚫Scroll ⚫Num, or NC for ⚫Num ⚫Caps without Scroll lock')
94a2b01e
AIL
137 parser.add_argument('-x', '--xdotool', required=False,
138 help='provide full path to xdotool executable, '
139 'e.g. "/usr/bin/xdotool"; '
140 'if not specified, search in PATH')
fc1c0791 141 args = parser.parse_args()
170ff396 142
94a2b01e
AIL
143 IndicatorKeyboardLED(short=args.short, order=args.order,
144 xdotool=args.xdotool)
fc1c0791 145 Gtk.main()