How do I 'lock the keyboard' to prevent an

2019-01-09 16:37发布

问题:

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)

回答1:

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.



回答2:

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 :)



回答3:

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.



回答4:

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()