neema_t
Posts: 26
Joined: Tue Nov 12, 2013 9:38 pm

Simulating keyboard events (?) in Python

Sun Aug 24, 2014 3:19 pm

Hello,

Long story short, I have a 'dumb' laptop keyboard matrix and I want to make it into a functional keyboard wired directly to my Pi via shift registers (for cycling the matrix inputs and sampling the outputs on fewer I/O pins). I have built a USB interface for it in the past using an Arduino Leonardo/Micro but for efficiency I'd like to read the shift registers with Python (or another language, I suppose, but so far I've only used Python on my Pi) and get it to emulate the keystrokes. Ideally I'd like it to function exactly the same as if it were a USB keyboard, i.e. in both the Pi's CLI and GUI with all the keys, modifiers and so on intact, if I can't do that then I'll stick with the Arduino interface.

So far I've found Pexpect but I really can't work it out, I've also tried os.system and subprocess.call but I can't figure out what to tell them to do in order to get the desired result, so far I can only think to tell them to echo a variable but that naturally won't 'type' the variable into the prompt. I've also found xsendkey and xsendkeycode but they're written in C, I think, and apparently only work in X; I want this more for use in the CLI since the project I'm using it for will have a PiTFT as the main display so X isn't something I want to use much, but of course I need the keyboard to work in both the CLI and GUI anyway.

I'm using a Model B with Raspbian, I assume that matters. Any and all pointers, tips and suggestions are appreciated, thanks for reading.

Edit: It looks like python-uinput (http://tjjr.fi/sw/python-uinput/) will do what I want, I'm not sure I completely understand it yet but I think it'll do the job once I figure it out. You need to install libudev-dev first, run 'modprobe uinput' after you install (and add 'uinput' to /etc/modules if you want it to run at boot) and then run the Python examples (and presumably any program using uinput) with superuser privileges to get it to work, none of which was made particularly clear in the documentation. I need to look into what it can do, mainly with regards to modifiers and 'invisible' keys, but it looks good.


Edit again: After digging a little deeper, I'm struggling with python-uinput. I can do this just fine:

Code: Select all

include uinput
device = uinput.Device([
    uinput.KEY_A #let's just use 'a' as an example
    )]
device.emit_click(uinput.KEY_A)
This will type 'a' on my PiTFT's terminal screen as expected.
The problem I have is that I can't work out how to replace the KEY_A in that last line with a variable, which is going to make it quite tricky to use it to interface a keyboard... Poking around in the __init__.py file (here: https://github.com/tuomasjjrasanen/pyth ... _init__.py) brings this up:

Code: Select all

 def emit_click(self, event, syn=True):
        """Emit click event. Only KEY and BTN events are accepted,
        otherwise ValueError is raised.
        `event` - event identifier, for example uinput.KEY_A
        `syn` - if True, Device.syn(self) is called before returning.
        """
        ev_type, ev_code = event
        if ev_type != 0x01:
            raise ValueError("event must be of type KEY or BTN")
        _libsuinput.suinput_emit_click(self.__uinput_fd, ev_code)
        if syn:
            self.syn()
As far as I can tell, if I don't want to use whatever syn is (and I don't, I don't think) then the self and syn arguments of emit_click can be ignored. So with the uinput.KEY_A argument, ev_type is uinput (which equals 1, apparently, I don't know why - presumably something in the __init__ file I haven't seen yet) and ev_code is KEY_A. I cannot work out for the life of me how to swap out that concrete definition for the key to be 'pressed' for a variable (which would eventually give way to a 2D list). I also don't understand 'self.__uinput_fd' nor the point of the device = uinput.Device part of the code, but it's getting late so I'll have a look at it tomorrow. I thought I'd post it in case someone happens to just know how I can do this.

Again, thanks for reading, I know this post is getting a little long...

User avatar
AndrewS
Posts: 3642
Joined: Sun Apr 22, 2012 4:50 pm
Location: Cambridge, UK

Re: Simulating keyboard events (?) in Python

Tue Aug 26, 2014 2:26 pm

neema_t wrote:So with the uinput.KEY_A argument, ev_type is uinput (which equals 1, apparently, I don't know why - presumably something in the __init__ file I haven't seen yet) and ev_code is KEY_A.
Nope, that's not how Python works ;-)
uinput.KEY_A is an object called 'KEY_A' inside the module called 'uinput', and can be directly assigned to a variable (e.g. my_keya = uinput.KEY_A)

Inside the emit_click function, "ev_type, ev_code = event" means that ev_type will get assigned uinput.KEY_A[0] and ev_code will get assigned uinput.KEY_A[1]


Never played with uinput myself so can't really offer more advice.

neema_t
Posts: 26
Joined: Tue Nov 12, 2013 9:38 pm

Re: Simulating keyboard events (?) in Python

Tue Aug 26, 2014 11:15 pm

AndrewS wrote:
neema_t wrote:So with the uinput.KEY_A argument, ev_type is uinput (which equals 1, apparently, I don't know why - presumably something in the __init__ file I haven't seen yet) and ev_code is KEY_A.
Nope, that's not how Python works ;-)
uinput.KEY_A is an object called 'KEY_A' inside the module called 'uinput', and can be directly assigned to a variable (e.g. my_keya = uinput.KEY_A)

Inside the emit_click function, "ev_type, ev_code = event" means that ev_type will get assigned uinput.KEY_A[0] and ev_code will get assigned uinput.KEY_A[1]


Never played with uinput myself so can't really offer more advice.
Thank you for the response, I don't know what I was doing before but I think you've just helped me take a fairly large step in that I can now use a variable as an argument (is that the right word?) for device.emit_click(), i.e. I can do

Code: Select all

key = [uinput.KEY_A]
device.emit_click(key[0])
And it doesn't return an error, so at the very least I could write a huge list with all the keys in it and do it that way, though that'll be pretty ugly and wasteful.

So the thing I want to crack now is if I print key[0] I get (1, 30). So basically uinput always == 1 while the KEY_x bit relates to the codes from /usr/include/linux/input.h. Therefore, if I could just sort of skip the whole uinput.KEY_x bit and just send the key code, I wouldn't have to have an array (NO! BAD! I mean list) like this: key = [uinput.KEY_A, uinput.KEY_B, uinput.KEY_C...] which seems really wasteful when I could - should? - be able to have key = [30, 32, 34...] and so on.

You know? It's probably quite clear that I don't know Python (or Linux!) at all, I barely know C either but I'm learning a lot with every Arduino/Raspberry Pi/MikroC project I take on. I appreciate the help though!


Edit: Actually, I think I pretty much need to be able to use those codes directly because I don't know how to do modifier keys otherwise. I can add certain keys, like backspace is ^? and the left cursor key is ^[[D, but shift, ctrl and alt I can't do.

User avatar
AndrewS
Posts: 3642
Joined: Sun Apr 22, 2012 4:50 pm
Location: Cambridge, UK

Re: Simulating keyboard events (?) in Python

Tue Aug 26, 2014 11:35 pm

Sounds like you could simply do something like:

Code: Select all

send_keycodes = [30, 32, 34] # etc.
for code in send_keycodes:
    device.emit_click((1, keycode))
    time.sleep(0.5)
(assuming of course that the ev_type is indeed always 1 for every keycode you need to send - which you should be able to verify by looking at the sourcecode for the uinput python module)

You need the 'extra' brackets inside the emit_click argument list to tell Python you're passing a single tuple argument (with two elements), and not two separate integer arguments.

User avatar
Douglas6
Posts: 5227
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, USA

Re: Simulating keyboard events (?) in Python

Wed Aug 27, 2014 12:28 am

I'm not sure why you are resisting putting the uinput.KEY_x objects into a look-up table. If using a matrix keyboard, you'll need to look them up by e.g. row and column anyways; there's no way you're going to calculate the proper key report from your matrix position. And forget about ASCII values (assuming that's what you meant by 30, 32, 34, etc.) ASCII just does not figure in here.

neema_t
Posts: 26
Joined: Tue Nov 12, 2013 9:38 pm

Re: Simulating keyboard events (?) in Python

Wed Aug 27, 2014 9:43 am

AndrewS wrote:Sounds like you could simply do something like:

Code: Select all

send_keycodes = [30, 32, 34] # etc.
for code in send_keycodes:
    device.emit_click((1, keycode))
    time.sleep(0.5)
(assuming of course that the ev_type is indeed always 1 for every keycode you need to send - which you should be able to verify by looking at the sourcecode for the uinput python module)

You need the 'extra' brackets inside the emit_click argument list to tell Python you're passing a single tuple argument (with two elements), and not two separate integer arguments.
I think that worked, I'm not in front of my Pi right now but it didn't return an error when I typed it out in Terminal, the extra brackets may have been all I was missing... Thanks!

Douglas6 wrote:I'm not sure why you are resisting putting the uinput.KEY_x objects into a look-up table. If using a matrix keyboard, you'll need to look them up by e.g. row and column anyways; there's no way you're going to calculate the proper key report from your matrix position. And forget about ASCII values (assuming that's what you meant by 30, 32, 34, etc.) ASCII just does not figure in here.
If a lookup table is the same as a 2D list, that was the plan from the start as that's how I did it before. And I don't think it's ASCII codes I'm sending since to get the virtual keyboard to type 'a' I send 30, when in ASCII 'a' is 97. The codes correspond to what's in etc/include/linux/input.h, but I don't know what those codes are.

User avatar
AndrewS
Posts: 3642
Joined: Sun Apr 22, 2012 4:50 pm
Location: Cambridge, UK

Re: Simulating keyboard events (?) in Python

Wed Aug 27, 2014 3:14 pm

In Python, a lookup table is typically implemented with a dictionary, which is something slightly different to a 2D list ;)

User avatar
Douglas6
Posts: 5227
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, USA

Re: Simulating keyboard events (?) in Python

Wed Aug 27, 2014 9:42 pm

I was using 'look-up' table in a more general sense; could be a '2D' data structure, which in Python would be a list of lists, possibly. I would populate that data structure with uinput.KEY_x objects, which have a nice readable name, rather than with some un-meaningful integer such as '1' or '30', is all.

neema_t
Posts: 26
Joined: Tue Nov 12, 2013 9:38 pm

Re: Simulating keyboard events (?) in Python

Wed Aug 27, 2014 11:50 pm

Douglas6 wrote:I was using 'look-up' table in a more general sense; could be a '2D' data structure, which in Python would be a list of lists, possibly. I would populate that data structure with uinput.KEY_x objects, which have a nice readable name, rather than with some un-meaningful integer such as '1' or '30', is all.
I see, I thought that was probably what you meant. I just think it's wasteful to type 'uinput.KEY_x', when I could keep re-declaring the tuple like this:

Code: Select all

variables = [30, 46, etc]
tuple = (1, variables[x])
device.emit_click(tuple)
Which works. Sure, it's harder to keep track of which number means what but if it makes the program any more efficient then that's good, right? Especially since it'll be running all the time.

Also, I found I could use the codes in input.h instead of being limited - I think - by what's in python-uinput's __init__.py file. For instance, there was no mapping in __init__.py for backspace, but if I used the code 14 (defined in input.h) I could send a backspace keystroke. I say 'think' because input.h also lists everything as KEY_something, so maybe if I were to use those, even though they're not in __init__.py, they might work. I haven't tried that yet though.

User avatar
AndrewS
Posts: 3642
Joined: Sun Apr 22, 2012 4:50 pm
Location: Cambridge, UK

Re: Simulating keyboard events (?) in Python

Thu Aug 28, 2014 12:46 am

neema_t wrote:I see, I thought that was probably what you meant. I just think it's wasteful to type 'uinput.KEY_x', when I could keep re-declaring the tuple like this:

Code: Select all

variables = [30, 46, etc]
tuple = (1, variables[x])
device.emit_click(tuple)
Which works. Sure, it's harder to keep track of which number means what but if it makes the program any more efficient then that's good, right? Especially since it'll be running all the time.
If you're looking for efficiency, it'd be better to do:

Code: Select all

variables = [uinput.KEY_A, uinput.KEY_C, etc]
device.emit_click(variables[x])

User avatar
Douglas6
Posts: 5227
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, USA

Re: Simulating keyboard events (?) in Python

Thu Aug 28, 2014 1:40 am

And regardless of insignificant efficiencies, much more readable and maintainable.

Return to “Python”