You are not logged in.

#1 2017-05-28 12:33:17

lordnaikon
Member
Registered: 2014-09-30
Posts: 34

[SOLVED]c x11 xtst fake key presses from char/string

Hi!
I wanted to make a program to simulate key presses. I think i am mostly done but i have done something wrong i guess because it is not doing what i expect it to do. I have made a small example program to illustrate the issue. The main problem is that if i want to generate capital letters it does not work with strings like 'zZ'. It is generating only small letters 'zz'. Although symbols like '! $ & _ >' etc. work fine (that require shift on my German keyboard layout) and even multi byte ones like '?' (EDIT: i figured that this board is not showing this glyph its a bomb 'emoji' U1f4a3). What i am doing is this:

preamble:
So basically the main problem by emulating key presses is first the layout that changes from user to user and most importantly modifier keys. So if you go the naive route and get a keysym with XStringToKeysym() get a keycode from that keysym with XKeysymToKeycode() and fire that event its not working like most 'newcomers' would expect (like me). The problem here is, that multiple keysyms are mapped to the same keycode. Like the keysysm for 'a' and 'A' are mapped to the same keycode because they're on the same physikal button on your keyboard that is linked to that keycode. So if you go the route from above you end up with the same keycode although the keysyms are different but mapped to the same button/keycode. And there is usually no way around this because it is not clear how the 'A' came to existence in the first place. shift+a or caps+a or you have a fancy keyboard with an 'a' and 'A' button on it. The other problem is how do i emit key presses for buttons that are not even on the keyboard of that person running that application. Like what key is pressed on an english layout if i want to type a 'Ä' (german umlaut). This does not work because XKeysymToKeycode() will not return a proper keycode for this because there is no keysym mapping for it with that layout.

my approach:
What i am tying to do to circumvent this is finding a keycode that is not being used. You have 255-8 keycodes at your disposal but a regular keyboard has only ~110 keys on it so there is usually some space left. I am trying to find one of those keycodes that are unmapped on the current layout and use it to assign my own keysyms on it. Then i get a keysym from my char i got by iterating over my string and pass it to XStringToKeysym() which gives me the appropriate keysym. In case of ’?’ that is in most cases not mapped to any keyboard layout i know of. So i map it to the unused keycode and press it with XTestFakeKeyEvent() and repeat that for every char in the string. This works great with all fancy glyph one can think of but it does not work with simple letters and i really don't know why sad in my debugging sessions keysyms and keycodes seem to be correct its just that XTestFakeKeyEvent() does not do the right things in that case. Its possible that i fucked something up at the keymapping part but i am not really sure whats the problem here and i hope someone has a good idea and can help me find a way to a working solution.

I am just using this unicode notation because i don't want to deal with this in the example here. Just assume there is code producing this from an arbitrary string. 

be aware that the code below can ruin your keymapping in such a way that you're not able to type and use your keyboard anymore and need to restart your X-Server/PC ... i hope it does not in its current state (working fine here)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>

//gcc -g enigo2.c -lXtst -lX11

int main(int argc, char *argv[])
{
  Display *dpy;
  dpy = XOpenDisplay(NULL);

  const char *strings[] = {

      "U1f4a3",// ?

      "U007A", //z
      "U005A", //Z
      "U002f", //'/'
      "U005D", //]

      "U003a", //:
      "U002a", //*
      "U0020", //' '
      "U0079", //y

      "U0059", //Y
      "U0020", //' '
      "U0031", //1
      "U0021", //!

      "U0020", //' '
      "U0036", //6
      "U0026", //&
      "U0020", //' '

      "U0034", //4
      "U0024", //$
      "U0020", //' '
      "U002D", //-

      "U005F", //_
      "U0020", //' '
      "U003C", //<
      "U003E", //>

      "U0063", //c
      "U0043", //C
      "U006f", //o
      "U004f", //O

      "U00e4", //ä
      "U00c4", //Ä
      "U00fc", //ü
      "U00dc", //Ü
  };

  KeySym *keysyms = NULL;
  int keysyms_per_keycode = 0;
  int scratch_keycode = 0; // Scratch space for temporary keycode bindings
  int keycode_low, keycode_high;
  //get the range of keycodes usually from 8 - 255
  XDisplayKeycodes(dpy, &keycode_low, &keycode_high);
  //get all the mapped keysyms available
  keysyms = XGetKeyboardMapping(
    dpy, 
    keycode_low, 
    keycode_high - keycode_low, 
    &keysyms_per_keycode);

  //find unused keycode for unmapped keysyms so we can 
  //hook up our own keycode and map every keysym on it
  //so we just need to 'click' our once unmapped keycode
  int i;
  for (i = keycode_low; i <= keycode_high; i++)
  {
    int j = 0;
    int key_is_empty = 1;
    for (j = 0; j < keysyms_per_keycode; j++)
    {
      int symindex = (i - keycode_low) * keysyms_per_keycode + j;
      // test for debugging to looking at those value
      // KeySym sym_at_index = keysyms[symindex];
      // char *symname;
      // symname = XKeysymToString(keysyms[symindex]);

      if(keysyms[symindex] != 0) {
        key_is_empty = 0;
      } else {
        break;
      }
    }
    if(key_is_empty) {
      scratch_keycode = i;
      break;
    }
  }
  XFree(keysyms);
  XFlush(dpy);

  usleep(200 * 1000);

  int arraysize = (sizeof(strings) / (sizeof(char)*8));
  for (int i = 0; i < arraysize; i++)
  {

    //find the keysym for the given unicode char
    //map that keysym to our previous unmapped keycode
    //click that keycode/'button' with our keysym on it
    KeySym sym = XStringToKeysym(strings[i]);
    KeySym keysym_list[] = { sym };
    XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
    KeyCode code = scratch_keycode;

    usleep(90 * 1000);
    XTestFakeKeyEvent(dpy, code, True, 0);
    XFlush(dpy);

    usleep(90 * 1000);
    XTestFakeKeyEvent(dpy, code, False, 0);
    XFlush(dpy);
  }

  //revert scratch keycode
  {
    KeySym keysym_list[] = { 0 };
    XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
  }

  usleep(100 * 1000);

  XCloseDisplay(dpy);

  return 0;
}

Last edited by lordnaikon (2017-06-03 20:12:52)

Offline

#2 2017-05-28 16:04:27

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 19,791

Re: [SOLVED]c x11 xtst fake key presses from char/string

I have not fully analyzed it, but I note that you are storing 16 bit unicode values in an array of 8 bit char. 
That, and I did not understand what you are doing here:

 int arraysize = (sizeof(strings) / (sizeof(char)*8));

I assert that arraysize should be 4.   You have 4 groups of four two byte values (stored as two char sequentially in an array of char) plus one two byte value (?) at the beginning for a total of 34 bytes.

Then, you provide an address of

KeySym sym = XStringToKeysym(strings[i])

in a pointer where 0 <= i < 4
Which correspond to strings[0], strings[1], strings[2], and strings[3]

I think you want a pointer to strings[1], strings[9], strings[17], and strings[25]

Of course, I could be completely wrong as I don't fully understand the theory of how this is all supposed to work hmm


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#3 2017-05-28 16:19:33

lordnaikon
Member
Registered: 2014-09-30
Posts: 34

Re: [SOLVED]c x11 xtst fake key presses from char/string

Thanks for your reply.

Yeah sry, that was a last minute change and i really did not thought well about that. Please just assume we are feeding  XStringToKeysym() with the elements of the array one by one like literally

KeySym sym = XStringToKeysym("U005A"); //for upper case Z

for every entry in the strings array. You can even comment out the strings array and throw away the loop, it does not matter, and put the elements in by hand one by one. But yes this is a little bit quirky sry about that but i did not want to complicate the code even more by transforming the test string "?zZ/]:* yY 1! 6& 4$ -_ <>cCoOäÄüÜ" (? here is a glyph not shown on the forum its this one https://en.wikipedia.org/wiki/File:Emoji_u1f4a3.svg) into the representation we see in the strings array, i just want to feed those into the XStringToKeysym() thats all.

EDIT:
i made a more lean example but its still basically the same but the output is not as telling as the first one

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>

//gcc -g enigo3.c -lXtst -lX11

int main(int argc, char *argv[])
{
    Display *dpy;
    dpy = XOpenDisplay(NULL);

    KeySym *keysyms = NULL;
    int keysyms_per_keycode = 0;
    int scratch_keycode = 0; // Scratch space for temporary keycode bindings
    int keycode_low, keycode_high;
    //get the range of keycodes usually from 8 - 255
    XDisplayKeycodes(dpy, &keycode_low, &keycode_high);
    //get all the mapped keysyms available
    keysyms = XGetKeyboardMapping(
        dpy,
        keycode_low,
        keycode_high - keycode_low,
        &keysyms_per_keycode);

    //find unused keycode for unmapped keysyms so we can
    //hook up our own keycode and map every keysym on it
    //so we just need to 'click' our once unmapped keycode
    int i;
    for (i = keycode_low; i <= keycode_high; i++)
    {
        int j = 0;
        int key_is_empty = 1;
        for (j = 0; j < keysyms_per_keycode; j++)
        {
            int symindex = (i - keycode_low) * keysyms_per_keycode + j;
            // test for debugging to looking at those value
            // KeySym sym_at_index = keysyms[symindex];
            // char *symname;
            // symname = XKeysymToString(keysyms[symindex]);

            if (keysyms[symindex] != 0)
            {
                key_is_empty = 0;
            }
            else
            {
                break;
            }
        }
        if (key_is_empty)
        {
            scratch_keycode = i;
            break;
        }
    }
    XFree(keysyms);
    XFlush(dpy);

    usleep(200 * 1000);

    //find the keysym for the given unicode char
    //map that keysym to our previous unmapped keycode
    //click that keycode/'button' with our keysym on it

    //feed various glyphs into it 
    //"U007A" z "U002a" * "U0021" ! "U0026" & "U00e4" ä "U00c4" Ä "U1f4a3" Bombglyph etc.
    KeySym sym = XStringToKeysym("U005A"); //uppercase Z
    KeySym keysym_list[] = {sym};
    XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
    KeyCode code = scratch_keycode;

    usleep(90 * 1000);
    XTestFakeKeyEvent(dpy, code, True, 0);
    XFlush(dpy);

    usleep(90 * 1000);
    XTestFakeKeyEvent(dpy, code, False, 0);
    XFlush(dpy);

    //revert scratch keycode
    {
        KeySym keysym_list[] = {0};
        XChangeKeyboardMapping(dpy, scratch_keycode, 1, keysym_list, 1);
    }

    usleep(100 * 1000);

    XCloseDisplay(dpy);

    return 0;
}

Last edited by lordnaikon (2017-05-28 16:57:14)

Offline

#4 2017-06-03 20:12:22

lordnaikon
Member
Registered: 2014-09-30
Posts: 34

Re: [SOLVED]c x11 xtst fake key presses from char/string

I've found the problem and it was really confusing for me to say the least. Turns out that XChangeKeyboardMapping() is doing a little "extra" work from what i was originally thinking it would do (based on what i read in the documentation). After setting the keyboard mapping the way i would like it to be XChangeKeyboardMapping() is adding additional keysyms to that keycode. In fact if i set an empty keycode to a keysym that represents a uppercase "Z" XChangeKeyboardMapping() adds an additional lowercase "z" for whatever reason (most likely to prevent errors or unwanted behavior  by only setting the upper or lowercase keysym)

However to bypass that behavior we need to fill in the space where the lowercase "z" would be  in order to type a uppercase "Z", simply insert two uppercase "Z" did the trick and XChangeKeyboardMapping() is thinking the keycode mapping is filled "correctly".

just changing the line

KeySym keysym_list[] = {sym};

to

KeySym keysym_list[] = {sym, sym};

is doing the trick.

Offline

Board footer

Powered by FluxBB