I am writing an anti-RSI/typing break programme for Ubuntu Linux in python. I would like to be able to "lock the keyboard" so that all keypresses are ignored until I "unlock" it. I want to be able to force the user to take a typing break.
I would like some programmatic way to "turn off" the keyboard (near instantaneously) until my programme releases it later (which could be 0.1 sec → 10 sec later). While I have "turned off the keyboard", no key presses should be sent to any windows, window managers, etc. Preferably, the screen should still show the same content. The keyboard should be locked even if this programme is not at the forefont and does not have focus.
Some programmes are able to do this already (e.g. Work Rave)
How do I do this on Linux/X11? (Preferable in Python)
This can be done easily with a shell script using 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
This logic is easily rewritable in Python. If installing xinput is problematic, it might be a good idea to fetch the source of xinput and try to reimplement it in Python using a library like python-xlib.
Based on that, here's a code I came up with:
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?")
Tested on Linux Mint 12, X11, HP Laptop, Gnome. Not sure if any of that matters though :)
UPDATE Added an option to change the path, e.g. "serio0" or "serio1". And prints the description, for me serio0 gave me: i8042 KBD port
, most likely if you have "KBD" in it, it's right, continue, otherwise I give you no guarantee :)
The canonical way to do this is by grabbing the input. For this no window must be actually visible. A input only window usually does the trick. However you should give the user some sort of feedback, why his input no longer works. Doing this as a focus grab has the advantage that a crash of the program won't turn the system unresponsive.
BTW: I think forcibly interrupting the user, maybe in the middle of a critical operations is a huge No-Go! I never understood the purpose of those programs. The user will sit in front of the screen idling, maybe loosing his thoughts. Just my 2 cents.
This isn't my code by the way
This stops the user from the pressing the Win Key, Shift Key or Alt Key, but I'm sure you can apply other keys as well.
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
<DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall)> _
Private Shared Function CallNextHookEx( _
ByVal idHook As Integer, _
ByVal nCode As Integer, _
ByVal wParam As IntPtr, _
ByVal lParam As IntPtr) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall, SetLastError:=True)> _
Private Shared Function SetWindowsHookEx( _
ByVal idHook As Integer, _
ByVal HookProc As HookCallback, _
ByVal hInstance As IntPtr, _
ByVal wParam As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, CallingConvention:=CallingConvention.StdCall, SetLastError:=True)> _
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
Usage:
KeyboardJammer.Jam()