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