Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C xtest emitting key presses for every Unicode character

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 'πŸ’£' . 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 :( 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 in the strings array because i don't want to deal with this in the example here. Just assume there is code producing this from an arbitrary input 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) just be aware if you fiddle with the code

#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);

  //my test string already transformed into unicode
  //ready to be consumed by XStringToKeysym
  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 = 33;
  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;
}
like image 870
dustin.b Avatar asked Oct 17 '22 11:10

dustin.b


1 Answers

When you send a single keysym for a given keycode to XChangeKeyboardMapping and it is a letter, it automatically fills correct upper and lower case equivalents for shift and capslock modifiers. That is, after

XChangeKeyboardMapping(dpy, scratch_keycode, 1, &keysym, 1);

the keycode map for scratch_keycode effectively changes (on my machine) to

tolower(keysym), toupper(keysym), tolower(keysym), toupper(keysym), tolower(keysym), toupper(keysym), 0, 0, 0, 0, ...

In order to inhibit this behaviour, send 2 identical keysyms per keycode:

KeySym keysym_list[2] = { sym, sym  };
XChangeKeyboardMapping(dpy, scratch_keycode, 2, keysym_list, 1);

This will fill both shifted and unshifted positions with the same keysym.

like image 153
n. 1.8e9-where's-my-share m. Avatar answered Oct 30 '22 00:10

n. 1.8e9-where's-my-share m.