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

Question:

Does anyone know an xlib function to trap a keypress event without losing the original focus? How to get rid of it?

(or “to use XGrabKey() without generating Grab-style focusout”?)

(or “How to get rid of NotifyGrab and NotifyUngrab focus events at system level?)

The XGrabKey will lose focus on key pressed and restore focus on key released.

And I want to trap the keypress without leak it to the original window (just as XGrabKey can do it).

References:

  1. …XGrabKey will steal focus…
    https://bugs.launchpad.net/gtkhotkey/+bug/390552/comments/8

  2. …The program receives control to do something in response to the key combination. Meanwhile, the program has been temporarily focused…
    During XGrabKey(board), discover which window had been focused

  3. …The XGrabKeyboard function actively grabs control of the keyboard and generates FocusIn and FocusOut events…
    http://www.x.org/archive/X11R6.8.0/doc/XGrabKeyboard.3.html#toc3

  4. …I can’t see a
    way to provide metacity’s current desktop changin behavior (changing
    and showing the popup dialog at the same time) without causing a
    Grab-type focus out on the window…
    https://mail.gnome.org/archives/wm-spec-list/2007-May/msg00000.html

  5. …Fullscreen mode should not exit on FocusOut events with NotifyGrab…
    https://bugzilla.mozilla.org/show_bug.cgi?id=578265

  6. grabbing keyboard doesnt allow changing focus …
    grabbing keyboard doesnt allow changing focus

  7. Focus Events Generated by Grabs (both the active grab of XGrabKeyboard and the passive grab of XGrabKey)
    http://www.x.org/releases/X11R7.6/doc/libX11/specs/libX11/libX11.html#Focus_Events_Generated_by_Grabs

  8. the XGrabKey source code: http://cgit.freedesktop.org/xorg/lib/libX11/tree/src/GrKey.c maybe we could modify this to get rid of focus-out events?

  9. there is “DoFocusEvents(keybd, oldWin, grab->window, NotifyGrab);” in ActivateKeyboardGrab():
    http://cgit.freedesktop.org/xorg/xserver/tree/dix/events.c

I’m writting a one-keystroke to keys-combination(and mouse movement) mapping software:https://code.google.com/p/diyism-myboard/

I have realized it in Windows with RegisterHotKey() and UnRegisterHotKey(): https://code.google.com/p/diyism-myboard/downloads/detail?name=MyBoard.pas

And i want to migrate it into Linux with XGrabKey() and XUngrabKey(): https://code.google.com/p/diyism-myboard/downloads/detail?name=myboard.py

I have created $10 bounty to resolve this problem. We need more backers to place bounties.
https://www.bountysource.com/issues/1072081-right-button-menu-flashes-while-jkli-keys-move-the-mouse-pointer

Asked By: diyism

||

Answers:

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.

Answered By: Anthon

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 startednnInfo about X11 connection:n");
 printf(" The display is::%sn",XDisplayName((char*)display_name));
 printf(" Width::%dtHeight::%dn",
 DisplayWidth(display_name,screen),
 DisplayHeight(display_name,screen));
 printf(" Connection number is %dn",connection);

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

 printf("nnLogging started.nn");

 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.

Answered By: hd1

My current code(from https://github.com/diyism/MyBoard/blob/master/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.

Answered By: diyism

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.

Answered By: Adam D. Ruppe

You could use XQueryKeymap to read the events, then you could use XTestKey to send a backspace key event and then the the key you want to be pressed. Better, you could register hotkeys for all the keyboard events and then generate the key event using XTestKey. BTW, KDE’s “Custom Shortcuts” control module allows using shortcuts to generate keypresses. Source code.

Answered By: Ramchandra Apte

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.

I’ve migrate it from googlecode to github: https://github.com/diyism/MyBoard

Answered By: diyism

For writing a key (shortcut) mapping software also have a look at libtermkey, a terminal key input library (written in C) that recognises XTerm-style mouse position/button reporting, special keys (such as arrow and function keys), including “modified” keys like Ctrl-Left.

There is, for example, POE::Wheel::TermKey, “an asynchronous perl wrapper around the libtermkey library, which provides an abstract way to read keypress events in terminal-based programs.”

Answered By: mmx

While I see that this question is old, it is still relevant and I have found a workaround that is more portable than modifying xorg-server.

Basically, XGrabKey does not send focus events when the grab window is the actively focused window.

If you watch the active window and reset your grabs when the focus changes to be on the new window, then the events will not be generated. I found watching for PropertyNotify
events and looking at the _NET_ACTIVE_WINDOW window property to work well to get that information.

I have not yet found any detrimental effects, or frankly any impact whatsoever, of settings the grab to be the currently active window.

Answered By: rbritton
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.