Update debian files
[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
43e5a6e7 5# Copyright (c) 2017-2023 Adrian I Lam <spam@adrianiainlam.tk> s/spam/me/
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:
43e5a6e7 13#
fc1c0791
AIL
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
43e5a6e7 16#
fc1c0791
AIL
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 40APP_NAME = 'indicator-keyboard-led'
43e5a6e7 41APP_VERSION = '1.2'
94a2b01e 42
43e5a6e7 43ICON_FOLDER = '/usr/share/icons/hicolor/scalable/apps/'
f462ecb9 44import gettext
85c1429b 45t = gettext.translation(APP_NAME, '/usr/share/locale')
f462ecb9
AIL
46_ = t.gettext
47
fc1c0791 48class IndicatorKeyboardLED:
43e5a6e7 49 def __init__(self, order='NCS', xdotool=None):
94a2b01e
AIL
50 self.validate_order(order)
51
43e5a6e7 52 self.indicators = {}
170ff396 53
43e5a6e7
AIL
54 count = 0
55 for i in order:
56 self.indicators[i] = AppIndicator3.Indicator.new(
57 '-'.join([APP_NAME, str(count), i]),
58 os.path.join(ICON_FOLDER, '-'.join([APP_NAME, i, 'off.svg'])),
59 AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
60 self.indicators[i].set_status(AppIndicator3.IndicatorStatus.ACTIVE)
61
62 count += 1
fc1c0791
AIL
63
64 keymap = Gdk.Keymap.get_default()
170ff396
AIL
65 keymap.connect('state-changed', self.update_indicator, order)
66 self.update_indicator(keymap, order)
94a2b01e 67 self.create_menu(xdotool, order)
fc1c0791 68
94a2b01e 69 def create_menu(self, xdotool, order):
94a2b01e 70 xdotool = xdotool or shutil.which('xdotool')
43e5a6e7
AIL
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
86
87 for i in order:
88 menu = Gtk.Menu()
89
90 if xdotool and os.access(xdotool, os.X_OK) and os.path.isfile(xdotool):
94a2b01e 91 menu.append(new_menu_item(i))
fc1c0791 92
43e5a6e7
AIL
93 quit_item = Gtk.MenuItem.new_with_label(_('Quit'))
94 menu.append(quit_item)
95 quit_item.connect('activate', Gtk.main_quit)
fc1c0791 96
43e5a6e7
AIL
97 self.indicators[i].set_menu(menu)
98 menu.show_all()
fc1c0791 99
170ff396 100 def update_indicator(self, keymap, order):
170ff396
AIL
101 for i in order:
102 if i == 'N':
103 state = keymap.get_num_lock_state()
104 elif i == 'C':
105 state = keymap.get_caps_lock_state()
106 elif i == 'S':
107 state = keymap.get_scroll_lock_state()
108 else:
109 raise ValueError('Invalid value in ORDER')
43e5a6e7
AIL
110
111 icon = os.path.join(ICON_FOLDER, '-'.join([APP_NAME, i, 'on.svg' if state else 'off.svg']))
112 self.indicators[i].set_icon(icon)
113
fc1c0791 114
94a2b01e
AIL
115 def validate_order(self, order):
116 order = order.upper()
117 for i in order:
118 if i not in ['N', 'C', 'S']:
119 raise ValueError('Illegal character in ORDER. '
120 '(Choices: [N, C, S])')
121 if len(order) != len(set(order)):
122 raise ValueError('Repeated character in ORDER. '
123 'Please specify each lock at most once.')
124 if 'S' in order and Gtk.check_version(3, 18, 0) is not None:
125 # Silently drop Scroll lock if GTK <= 3.18
126 order.remove('S')
fc1c0791 127
170ff396 128
fc1c0791
AIL
129if __name__ == '__main__':
130 signal.signal(signal.SIGINT, lambda signum, frame: Gtk.main_quit())
170ff396 131
fc1c0791 132 parser = argparse.ArgumentParser(
170ff396
AIL
133 description='indicator-keyboard-led - simulate keyboard lock keys LED',
134 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
170ff396
AIL
135 parser.add_argument('-o', '--order', required=False, default='NCS',
136 help='specify the order of the locks displayed, e.g. CSN for '
43e5a6e7 137 'Caps Scroll Num, or NC for Num Caps without Scroll lock')
94a2b01e
AIL
138 parser.add_argument('-x', '--xdotool', required=False,
139 help='provide full path to xdotool executable, '
140 'e.g. "/usr/bin/xdotool"; '
141 'if not specified, search in PATH')
fc1c0791 142 args = parser.parse_args()
170ff396 143
43e5a6e7 144 IndicatorKeyboardLED(order=args.order,
94a2b01e 145 xdotool=args.xdotool)
fc1c0791 146 Gtk.main()