¿Cómo ‘locking el teclado’ para evitar que se envíen más pulsaciones de teclas en X11 / Linux / Gnome?

Estoy escribiendo un progtwig anti-RSI / typing break para Ubuntu Linux en python. Me gustaría poder “bloquear el teclado” para que todas las pulsaciones de teclas se ignoren hasta que lo “desbloquee”. Quiero poder forzar al usuario a tomar un descanso de escritura.

Me gustaría alguna forma programática de “apagar” el teclado (casi instantáneamente) hasta que mi progtwig lo libere más tarde (lo que podría ser de 0,1 segundos → 10 segundos más tarde). Si bien “apagué el teclado”, no debe enviarse ninguna pulsación de tecla a ninguna ventana, administrador de ventanas, etc. Preferiblemente, la pantalla debería mostrar el mismo contenido. El teclado debe estar bloqueado incluso si este progtwig no está en el fondo y no tiene el foco.

Algunos progtwigs ya pueden hacer esto (por ejemplo, Work Rave)

¿Cómo hago esto en Linux / X11? (Preferible en Python)

Esto se puede hacer fácilmente con un script de shell usando xinput:

#!/bin/sh do_it() { # need error checking there. We should also restrict which device gets # deactivated, by checking other properties. keyboard_ids="$(xinput list | sed -rn 's/.*id=([0-9]+).*slave\s+keyboard.*/\1/p')" for keyboard_id in $keyboard_ids; do # 121 is "Device Active". # use xinput watch-props $device_id to see some properties. xinput set-int-prop $keyboard_id 121 8 $1; done; } # you maybe don't want to exit in case of failure there. do_it 0 ; sleep 5; do_it 1 

Esta lógica es fácilmente regrabable en Python. Si la instalación de xinput es problemática, podría ser una buena idea obtener la fuente de xinput e intentar reimplementarla en Python utilizando una biblioteca como python-xlib.

Basado en eso , aquí hay un código que se me ocurrió:

 class KeyboardLocker: def __init__(self, serio=0): self._on = False self.serio = serio def on(self): return self._on def write_value(self,path, value): with open(path, "a") as f: f.write(value) def toggle(self): if self.on(): self.turn_off() else: self.turn_on() def description(self): path = '/sys/devices/platform/i8042/serio%d/description' % (self.serio,) with open(path, "r") as f: description = f.read() return description def turn_on(self): try: self.write_value('/sys/devices/platform/i8042/serio%d/bind_mode' % (self.serio,), 'auto') except IOError, e: self._on = False raise else: self._on = True return self.on() def turn_off(self): try: self.write_value('/sys/devices/platform/i8042/serio%d/bind_mode' % (self.serio,), 'manual') self.write_value('/sys/devices/platform/i8042/serio%d/drvctl' % (self.serio,), 'psmouse') except IOError, e: self._on = True raise else: self._on = False return self.on() if __name__ == "__main__": kl = KeyboardLocker(serio=0) device = kl.description() print "We got a lock on", device proceed = raw_input("Do you want to proceed? (y/n)").lower().startswith("y") import sys if not proceed: sys.exit(1) kl.turn_off() import time wait = 5 print "Sleeping few seconds...", wait time.sleep(wait) print "Voila!" kl.turn_on() raw_input("Does it work now?") 

Probado en Linux Mint 12, X11, HP Laptop, Gnome. No estoy seguro si algo de eso importa 🙂

ACTUALIZACIÓN Se agregó una opción para cambiar la ruta, por ejemplo, “serio0” o “serio1”. E imprime la descripción, para mí, me lo dio serio0: i8042 KBD port , lo más probable si tiene “KBD” en él, está bien, continúe, de lo contrario no le doy ninguna garantía 🙂

La forma canónica de hacer esto es agarrar la entrada. Para esto ninguna ventana debe ser realmente visible. Una ventana de entrada solamente hace el truco. Sin embargo, debe dar al usuario algún tipo de retroalimentación, por qué su entrada ya no funciona. Hacer esto como una captura de enfoque tiene la ventaja de que una falla del progtwig no hará que el sistema deje de responder.

Por cierto: creo que interrumpir a la fuerza al usuario, tal vez en medio de una operación crítica es un gran No-Go! Nunca entendí el propósito de esos progtwigs. El usuario se sentará frente a la pantalla sin hacer nada, tal vez perdiendo sus pensamientos. Sólo mis 2 centavos.

Este no es mi código por cierto

Esto evita que el usuario presione la tecla Win, la tecla Shift o la tecla Alt, pero estoy seguro de que también puede aplicar otras teclas.

 Public Class KeyboardJammer Private Delegate Function HookCallback(ByVal nCode As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer Private Shared HookDelegate As HookCallback Private Shared HookId As Integer Private Const Wh_Keyboard_LL As Integer = 13 Private Const Vk_Tab As Integer = 9 Private Const Vk_Escape As Integer = 27 Private Const Vk_LWinKey As Integer = 91 Private Const Vk_RWinkKey As Integer = 92 Private Shared Function KeyBoardHookProc(ByVal nCode As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer 'All keyboard events will be sent here.' 'Dont process just pass along.' If nCode < 0 Then Return CallNextHookEx(HookId, nCode, wParam, lParam) End If 'Extract the keyboard structure from the lparam' 'This will contain the virtual key and any flags.' 'This is using the my.computer.keyboard to get the' 'flags instead' Dim KeyboardSruct As KBDLLHOOKSTRUCT = Marshal.PtrToStructure(lParam, GetType(KBDLLHOOKSTRUCT)) If KeyboardSruct.vkCode = Vk_Tab And My.Computer.Keyboard.AltKeyDown Then 'Alt Tab' Return 1 ElseIf KeyboardSruct.vkCode = Vk_Escape And My.Computer.Keyboard.CtrlKeyDown Then 'Control Escape' Return 1 ElseIf KeyboardSruct.vkCode = Vk_LWinKey Or KeyboardSruct.vkCode = Vk_RWinkKey Then If KeyboardSruct.vkCode = Vk_Tab Then 'Winkey Tab' Return 1 Else 'Winkey' Return 1 End If ElseIf KeyboardSruct.vkCode = Vk_Escape And My.Computer.Keyboard.AltKeyDown Then 'Alt Escape' Return 1 End If 'Send the message along' Return CallNextHookEx(HookId, nCode, wParam, lParam) End Function Public Shared Sub Jam() 'Add the low level keyboard hook' If HookId = 0 Then HookDelegate = AddressOf KeyBoardHookProc HookId = SetWindowsHookEx(Wh_Keyboard_LL, HookDelegate, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly.GetModules()(0)), 0) If HookId = 0 Then 'error' End If End If End Sub Public Shared Sub UnJam() 'Remove the hook' UnhookWindowsHookEx(HookId) End Sub  _ Private Shared Function CallNextHookEx( _ ByVal idHook As Integer, _ ByVal nCode As Integer, _ ByVal wParam As IntPtr, _ ByVal lParam As IntPtr) As Integer End Function  _ Private Shared Function SetWindowsHookEx( _ ByVal idHook As Integer, _ ByVal HookProc As HookCallback, _ ByVal hInstance As IntPtr, _ ByVal wParam As Integer) As Integer End Function  _ Private Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Integer End Function Private Structure KBDLLHOOKSTRUCT Public vkCode As Integer Public scanCode As Integer Public flags As Integer Public time As Integer Public dwExtraInfo As IntPtr End Structure End Class 

Uso:

 KeyboardJammer.Jam()