Why XGrabKey generates extra focus-out and focus-in events?
Asked Answered
D

8

42

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

Dyandyana answered 7/3, 2013 at 11:48 Comment(10)
You are writing a keylogger, right? ;)Warfeld
No, i'm writting a key mapping software with python-xlib: code.google.com/p/diyism-myboardDyandyana
If you want to remap your keyboard, you are doing it wrong. XGrabKey is not the tool for the job.Resonant
I once (1997, I checked) wrote a keylogger that grabbed key events. Don't know if it still compiles/works or is of any use to you, but I created a gist if you want to check it out: gist.github.com/robertklep/5124355Geehan
@Geehan Thanks, i found XSelectInput in your code, maybe it's better than XGrabKey, i'll give a try.Dyandyana
I'm thinking that a combination of XSelectInput with XNextEvent and XPutBackEvent might be useful, but have you looked at xmodmap?Geehan
@Geehan xmodmap only can map keys combination to one key, but can't map one keystroke to keys combination and mouse movement.Dyandyana
But perhaps its code can be of help to you :)Geehan
Not the same thing, xmodmap uses XChangeKeyboardMapping()Dyandyana
There really is a reason why e.g. wxPython has [RegisterHotKey][wiki.wxpython.org/RegisterHotKey] but only for Microsoft Windows not for Linux. It hooks directly into OS functionality, not into the "window manager". Have you looked at the X source for hooks to catch and reroute the events?Blink
D
7

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

Dyandyana answered 7/3, 2013 at 11:48 Comment(0)
B
11

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.

Blink answered 10/3, 2013 at 13:34 Comment(0)
D
11

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.

Dyandyana answered 14/3, 2013 at 8:30 Comment(2)
Maybe we could modify the XGrabKey source code to get rid of focus out events: cgit.freedesktop.org/xorg/lib/libX11/tree/src/GrKey.cDyandyana
myboard.py link broken, use this: web.archive.org/web/20160107234343/https://…Cymene
D
7

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

Dyandyana answered 7/3, 2013 at 11:48 Comment(0)
P
5

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.

Persecution answered 10/3, 2013 at 13:43 Comment(7)
compiled and tested ok for meBlink
Done, I also corrected the first line XQueryMap -> XQueryKeymap and had to put in some extra chars for the required 6 char edit change)Blink
Once it's caught, you're responsible for trapping it, @DyandyanaPersecution
XQueryKeymap can trap keystroke(not only catch keystroke)? How to trap it and without leak it to system?Dyandyana
It doesn't trap the event, it lets you look at the event queue without changing it.Blink
Like I said (above) you are responsible for trapping it, if you so choose.Persecution
Shouldn't this answer suggest some way to trap the key event, since that was what was originally asked for?Consol
B
3

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.

Banas answered 7/3, 2013 at 11:48 Comment(1)
Thanks, It's reasonable but some complex.Dyandyana
E
1

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.

Eleemosynary answered 7/3, 2013 at 11:48 Comment(4)
Maybe this method is the right way, but myboard.py is listening too many keys, maybe my "commenting off DoFocusEvents" is more effeicent. Myboard.py project has been moved to github.com/diyism/MyBoard and till now, I have used this project every day for 8 years.Dyandyana
Just so I understand you correctly, are you suggesting to not do root.grab_key() bust instead active_window.grab_key() whenever it changes? This actually sounds like a reasonable solution and apparently the only available oneSchist
I have now implemented this and it works flawlessly! These are all my changes I had to apply. github.com/phil294/ahk_x11/commit/53fa4c7Schist
...not on all systems though. On Solus (or even KDE in general?), the root grabbing is still preferable (issue on my repo). I'll be glad if someone should find a proper solution to all of this.Schist
A
1

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."

Anemochore answered 7/3, 2013 at 11:48 Comment(0)
C
0

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.

Clandestine answered 7/3, 2013 at 11:48 Comment(4)
XQueryKeymap can catch but can't trap events.Dyandyana
@Dyandyana But I said one can generate a backspace event. Are you the downvoter?Clandestine
@Apte, Backspace won't reverse have-triggered mouse movements.Dyandyana
@Dyandyana so you want the pointer movement event to received but don't want the pointer to be moved? I thought you were only remapping the keyboard.Clandestine

© 2022 - 2024 — McMap. All rights reserved.