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 ')
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.
Original post of Vincent Gay.Votez pour ce billet sur Planet Libre.