Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why XGrabKey generates extra focus-out and focus-in events?

My current code(from http://diyism-myboard.googlecode.com/files/myboard.py):

disp=Display()
screen=disp.screen()
root=screen.root

def grab_key(key, mod):
    key_code=string_to_keycode(key)
    #3rd: bool owner_events, 4th: pointer_mode, 5th: keyboard_mode, X.GrabModeSync, X.GrabModeAsync
    root.grab_key(key_code, mod, 0, X.GrabModeAsync, X.GrabModeAsync)
    root.grab_key(key_code, mod|X.LockMask, 0, X.GrabModeAsync, X.GrabModeAsync) #caps lock
    root.grab_key(key_code, mod|X.Mod2Mask, 0, X.GrabModeAsync, X.GrabModeAsync) #num lock
    root.grab_key(key_code, mod|X.LockMask|X.Mod2Mask, 0, X.GrabModeAsync, X.GrabModeAsync)

def main():
    grab_key('Shift_L', X.NONE)
    grab_key('Shift_R', X.NONE)
    while 1:
          evt=root.display.next_event()
          if evt.type in [X.KeyPress, X.KeyRelease]: #ignore X.MappingNotify(=34)
             handle_event(evt)

if __name__ == '__main__':
   main()

When i press "shift" key, the focus lost, and when i release it, the focus come back.


I looked at global hotkeys back in the early 90s for Irix, ultrix and solaris, as it had been easy to do on my Acorn BBC computer. Eventually we decided on solving this in a non-portable way on a level below xlib with some proprietary code. Since our software installation needed as superuser priviliges anyway, we were able to insert the appropriate software hooks as daemons.

For Linux (nowadays) you should probably look for a software solution by processing the keyboard event on the os level. I would start with having a look here: http://code.google.com/p/logkeys/

A more generic solution would be to have a small PC board with USB in and USB out, which acts to the computer as a mouse and keyboard and translates the keyboard keys as necessary. But this would not be so flexible if you want to change the mapping often.


Finally, as you know linux means freedom, i modified xserver to get rid of grab-style focusout:

sudo apt-get build-dep xorg-server
apt-get source xorg-server
cd xorg-server-*
#modify or patch dix/events.c: comment off "DoFocusEvents(keybd, oldWin, grab->window, NotifyGrab);" in ActivateKeyboardGrab(), comment off "DoFocusEvents(keybd, grab->window, focusWin, NotifyUngrab);" in DeactivateKeyboardGrab()
sudo apt-get install devscripts
debuild -us -uc    #"-us -uc" to avoid the signature step
cd ..
sudo dpkg --install xserver-xorg-core_*.deb
#clear dependencies:
sudo apt-mark auto $(apt-cache showsrc xorg-server | grep Build-Depends | perl -p -e 's/(?:[\[(].+?[\])]|Build-Depends:|,|\|)//g')
sudo apt-get autoremove

And i also need to get rid of XGrabKeyboard in gtk context menu:

sudo apt-get build-dep gtk+2.0
apt-get source gtk+2.0
cd gtk+2.0-*
#modify or patch it: add "return TRUE;" in first line of popup_grab_on_window() of gtk/gtkmenu.c
dpkg-source --commit
debuild -us -uc  #"-us -uc" to avoid the signature step, maybe need: sudo apt-get install devscripts
cd ..
sudo dpkg --install libgtk2.0-0_*.deb
#clear dependencies:
sudo apt-mark auto $(apt-cache showsrc gtk+2.0 | grep Build-Depends | perl -p -e 's/(?:[\[(].+?[\])]|Build-Depends:|,|\|)//g')
sudo apt-get autoremove

Now myboard.py works well.

If you are using ubuntu raring-updates edition, you could give a try to:

https://code.google.com/p/diyism-myboard/downloads/detail?name=xserver-xorg-core_1.13.3-0ubuntu6.2_i386.deb

and:

https://code.google.com/p/diyism-myboard/downloads/detail?name=libgtk2.0-0_2.24.17-0ubuntu2_i386.deb


Looks like XQueryKeymap will sort you. See below for C++ source code I found:

/* compile with g++ keytest.cpp -LX11 -o keytest */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>

double gettime() {
 timeval tim;
 gettimeofday(&tim, NULL);
 double t1=tim.tv_sec+(tim.tv_usec/1000000.0);
 return t1;
}

int main() {
 Display *display_name;
 int depth,screen,connection;
 display_name = XOpenDisplay(NULL);
 screen = DefaultScreen(display_name);
 depth = DefaultDepth(display_name,screen);
 connection = ConnectionNumber(display_name);
 printf("Keylogger started\n\nInfo about X11 connection:\n");
 printf(" The display is::%s\n",XDisplayName((char*)display_name));
 printf(" Width::%d\tHeight::%d\n",
 DisplayWidth(display_name,screen),
 DisplayHeight(display_name,screen));
 printf(" Connection number is %d\n",connection);

 if(depth == 1)
  printf(" You live in prehistoric times\n");
 else
  printf(" You've got a coloured monitor with depth of %d\n",depth);

 printf("\n\nLogging started.\n\n");

 char keys_return[32];
 while(1) {
  XQueryKeymap(display_name,keys_return);
  for (int i=0; i<32; i++) {
   if (keys_return[i] != 0) {
    int pos = 0;
    int num = keys_return[i];
    printf("%.20f: ",gettime());
    while (pos < 8) {
     if ((num & 0x01) == 1) {
      printf("%d ",i*8+pos);
     }
     pos++; num /= 2;
    }
    printf("\n");
   }
  }
  usleep(30000);
 }
 XCloseDisplay(display_name);
}

Note, this isn't tested code, nor is it mine -- I merely found it on the Internet.


I've got an idea that I'm pretty sure would work, but I have to get to bed and can't test it myself, and it isn't pretty, since I don't think there's any way to do what you want in X. Here's the steps I have in mind. In short: disable the keyboard in X, read the events from the lower level api, and selectively feed them to X yourself. You have to disable the keyboard in X because otherwise, you could look at the event, but not stop it; you would read it alongside X, not intercept it.

So here it is broken out:

1) Run xinput -list to get the keyboard X is using

2) Run xinput list-props id to find the Device Enabled property

3) Run xinput set-prop id prop 0 to disable the device in X:

xinput -list
xinput list-props 12 # 12 is the id of the keyboard in the list... (example # btw)
xinput set-prop 12 119 0 # 119 is the "Device Enabled" prop, we turn it off

I don't know how xinput works on the xlib level, I'd just call it out to the shell for simplicity of implementation.

4) Open /dev/input/eventX, where X is the keyboard device. I'd actually search for the name (given in xinput -list) under /dev/input/by-id and open it that way. This will likely require root at some point, since the permissions on these are generally pretty restrictive.

5) Read the keyboard input from there:

The format of the data from the input events is:

struct input_event {
    int tv_sec; // time of the event
    int tv_usec; // ditto
    ushort type; // == 1 for key event
    ushort code; // key code, not the same as X keysyms, you should check it experimentally. 42 is left shift on mine, 54 is right shift
    int value; // for keys, 1 == pressed, 0 == released, 2 == repeat
}

ints are 32 bit, ushorts are 16 bit. Since you're only interested in keyboard input, you could do this pretty simply:

  • read and ignore 8 bytes.

  • the next byte should be 1, then the next one is 0. if not, skip this event

  • Next byte is the little end of the key code, and since there's < 255 keys, that's good enough so

  • skip the next byte.

  • read the next byte to see if itis pressed or released

  • skip the next three bytes

6) When you get an event you're interested in trapping, handle it yourself. Otherwise, use XSendEvent to send it to X so it can be processed normally. Mapping the hardware code you get from /dev/input to the appropriate keysym might be a trick, but I'm fairly certain there's a function in xlib somewhere to help with this.

7) goto 5 and loop till you're done

8) Make sure you set everything back to how it was when you exit, or you could break the user's keyboard input to X!

I'd suggest testing this with a second usb keyboard, you can disable and listen to keyboards independently of each other with /dev/input and xinput, so if you crash, you still have the first keyboard in and working normally. (Actually, I think it'd be pretty cool to intentionally do it with a second keyboard, double the hotkeys!)

But yeah, needing root and potentially leaving the keyboard "detached" from X aren't pretty at all, and that forwarding-with-SendKey might be easier said than done, but I'm pretty sure this would work and give you maximum flexibility.