LockKeys 0.2
J'ai évoqué récemment mon besoin d'avoir une applet indicateur d'état du clavier (capslock et numlock) indépendante de l'environnement de bureau. Ne trouvant rien qui me satisfasse j'ai alors bidouillé un script en bash, mais avec dans un coin de ma tête l'idée que l'occasion était bien belle de me mettre enfin à Python. D'autant plus que j'utilise Battery Monitor (aka batterymon-clone) écrit en python2 et dont je me suis bien entendu largement inspiré.
Fonctionnement : une simple icône dans le systray avec un menu accessible par un clic droit
En option il est possible de jouer un son et/ou d'afficher une notification. Le jeux d'icônes et le fichier son doivent être installés pour l'instant dans /usr/local/share/lockkeys. Ils sont réunis dans cette archive.
Voici le code
#!/usr/bin/python2 # -*- coding: utf-8 -*- #Todo mettre les messages sous forme de variable dans fichier à inclure <- internationalisation import gtk import glib import os import sys import ConfigParser import ctypes try: import pynotify if not pynotify.init("LockKeys"): print("Il y a eu une erreur pendant l'initialisation du système de notification. Les notifications ne fonctionnerons pas.") pynotify = None except: print("Il semble que python-notify ne soit pas installé. Les notifications ne fonctionnerons pas.") pynotify = None NotifyAvailable = pynotify class XKeyboardState(ctypes.Structure): _fields_ = [("key_click_percent", ctypes.c_int), ("bell_percent", ctypes.c_int), ("bell_pitch", ctypes.c_uint), ("bell_duration", ctypes.c_uint), ("led_mask", ctypes.c_ulong), ("global_auto_repeat", ctypes.c_int), ("auto_repeats", ctypes.c_char * 32)] def initXGetKeyboardControl(): global dpy, keyboardState, XGetKeyboardControl libX11 = ctypes.CDLL("libX11.so.6") XOpenDisplay = libX11.XOpenDisplay XOpenDisplay.restype = ctypes.c_void_p XOpenDisplay.argtypes = [ctypes.c_char_p] XGetKeyboardControl = libX11.XGetKeyboardControl XGetKeyboardControl.restype = ctypes.c_int XGetKeyboardControl.argtypes = [ctypes.c_void_p, ctypes.POINTER(XKeyboardState)] dpy = XOpenDisplay(None) keyboardState = XKeyboardState() def runXGetKeyboardControl(): global dpy, keyboardState, XGetKeyboardControl XGetKeyboardControl(dpy, ctypes.byref(keyboardState)) return keyboardState.led_mask #écrit dans le fichier de config def WriteConfig (Section, Key, Value): Config.set(Section,Key,Value) with open(ConfigFile, 'w') as myfile: Config.write(myfile) #Notification (état du clavier) si pynotify != None def notify(message,duration): if pynotify: n = pynotify.Notification('LockKeys', message) n.set_timeout(duration) n.set_icon_from_pixbuf(gtk.Label().render_icon(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_LARGE_TOOLBAR)) n.show() class Systray(): def __init__(self): self.tray_object= gtk.StatusIcon() self.tray_object.connect("popup_menu", self.rightclick_menu) self.show_trayicon(1) ## fixed to one for now self._oldMask = -1 #int(runXGetKeyboardControl()) & 3 def show_trayicon(self,value): self.tray_object.set_visible(True) return def property_modified(self): # utilse runXGetKeyboardControl() pour connaître l'état des touches capslock et numlock # 0 -> aucun, 1 -> capslock, 2-> numlock, 3 -> les 2 mask=int(runXGetKeyboardControl()) & 3 if mask != self._oldMask: if sound_status == True and self._oldMask != -1: os.system(Sound) notify(msg[mask],2000) self._oldMask = mask # Todo : essayer d'abord usr/share/lockkeys/ voire ~/.local/lockkeys icon_path = '/usr/local/share/lockkeys/' + str(mask) + '.png' self.tray_object.set_from_file(icon_path) # défini le menu clic droit sur l'icône def rightclick_menu(self, button, widget, event): menu = gtk.Menu() about_menu = gtk.ImageMenuItem(gtk.STOCK_ABOUT) about_menu.connect('activate', self.about) exit_menu = gtk.ImageMenuItem(gtk.STOCK_CLOSE) exit_menu.connect('activate', self.close) menu.append(about_menu) menu.append(exit_menu) sep = gtk.SeparatorMenuItem() menu.append(sep) sound_menu = gtk.CheckMenuItem("Activer le son") sound_menu.set_active(sound_status) sound_menu.connect("activate", self.sound_toggle) menu.append(sound_menu) notify_menu = gtk.CheckMenuItem("Activer les notifications") notify_menu.set_active(notify_status) notify_menu.connect("activate", self.notify_toggle) menu.append(notify_menu) menu.show_all() menu.popup(None, None, None, 2, event) # activation / désactivation du son et enregistrement dans le fichier config def sound_toggle(self, widget): global sound_status if widget.active: sound_status=True else: sound_status=False WriteConfig ('helpers','sound',sound_status) # activation / désactivation des notifications et enregistrement dans le fichier config def notify_toggle(self, widget): global notify_status global pynotify if widget.active: notify_status=True pynotify=NotifyAvailable else: notify_status=False pynotify=None WriteConfig ('helpers','notification',notify_status) def close(self,button): sys.exit(0) def about(self, button): about_dg = gtk.AboutDialog() about_dg.set_name('Lockkeys') about_dg.set_version('0.2') about_dg.set_copyright('(C) 2014 Vincent Gay <vgay@vintherine.org>') about_dg.set_comments(("Simple icône dans la zone de notification pour indiquer l'état de CapsLock et NumLock")) about_dg.set_license('Ce script est distribuable sous licence gpl version 3 ou supérieure\\nhttp://www.gnu.org/licenses/gpl-3.0.fr.html') about_dg.set_website('http://blog.vintherine.org') about_dg.run() about_dg.destroy() class Manager: def __init__(self): self.listener = Systray() def __property_modified_handler(self): self.listener.property_modified() def update(self): self.__property_modified_handler() return True def main(): initXGetKeyboardControl() m = Manager() glib.timeout_add(200, m.update) gtk.main() ConfigFile=os.path.expanduser('~/.config/lockkeys.cfg') # aplay appartient au paquet alsa, est-ce la peine de vérifier ? Sound = 'aplay /usr/local/share/lockkeys/ding.wav > /dev/null 2>1&' sound_status=True pynotify = None Config = ConfigParser.ConfigParser() msg=[] msg.append('Capslock = off, Numlock = off') msg.append('Capslock = on, Numlock = off') msg.append('Capslock = off, Numlock = on') msg.append('Capslock = on, Numlock = on') #créer le fichier de config s'il n'existe pas if os.path.isfile(ConfigFile) == False: ini = open(ConfigFile,'w') Config.add_section('helpers') Config.set('helpers','sound',True) Config.set('helpers','notification',False) Config.write(ini) ini.close() # lire le fichier de configuration Config.read(ConfigFile) try: sound_status = Config.getboolean("helpers", "sound") except: WriteConfig ('helpers','sound',True) try: notify_status = Config.getboolean("helpers", "notification") except: WriteConfig ('helpers','notification',False) notify_status=False if notify_status: pynotify=NotifyAvailable main()
Si l'indentation n'est pas propre suite au copié collé merci de prendre le code sur pastebin
Dépendances :
- python2
- gtk2
- pygtk
- python2-notify (optionnel)
- aplay (installé par alsa donc en principe présent)
Il me reste toutefois un point me chiffonne avec cette solution : le
timeout qui gère l'appel à gtk. S'il est trop haut il y a un décalage entre
l'appui sur les touches et le changement d'icône. S'il est trop bas la charge
cpu, quoique supportable, excède 1%. Ce qui me paraît beaucoup pour une simple
applet. Avec un compromis à 400 ms la charge ressort à 0,7% (Intel Pentium
2020M 2.4Ghz double cœur).
edit : problème résolu grâce aux conseils de Benjarobin. Le code ci-dessus a été modifié en conséquence.
Voilou, Il reste encore du boulot pour rendre l'application présentable mais c'est mon premier script python : ça s'arrose
Chez moi ça fonctionne correctement mais si quelqu'un d'autre voulait bien tester ça serait sympa.