为什么XGrabKey会产生额外的焦点和焦点事件?

时间:2013-03-07 11:48:39

标签: python linux x11 xlib

有没有人知道一个xlib函数来捕获一个按键事件而不会丢失原始焦点?如何摆脱它?

(或“使用XGrabKey()而不生成抓取式焦点”?)

(或“如何在系统级别摆脱NotifyGrab和NotifyUngrab焦点事件?”

XGrabKey将失去对关键按下和恢复焦点的关注。

我想捕获按键而不将其泄漏到原始窗口(正如XGrabKey可以做到的那样)。

参考文献:

  1. ...... XGrabKey会抢镜头...... https://bugs.launchpad.net/gtkhotkey/+bug/390552/comments/8

  2. ...程序接受控制以响应键组合做某事。同时,该计划暂时集中...... During XGrabKey(board), discover which window had been focused

  3. ... XGrabKeyboard函数主动抓取键盘控制并生成FocusIn和FocusOut事件...... http://www.x.org/archive/X11R6.8.0/doc/XGrabKeyboard.3.html#toc3

  4. ......我看不到了 提供metacity当前桌面改变行为的方式(改变 并且在不导致的情况下显示弹出对话框 抓住窗户的焦点... https://mail.gnome.org/archives/wm-spec-list/2007-May/msg00000.html

  5. ...全屏模式不应退出使用NotifyGrab的FocusOut事件... https://bugzilla.mozilla.org/show_bug.cgi?id=578265

  6. 抓住键盘不允许改变焦点...... grabbing keyboard doesnt allow changing focus

  7. Grabs生成的焦点事件(XGrabKeyboard的主动抓取和XGrabKey的被动抓取) http://www.x.org/releases/X11R7.6/doc/libX11/specs/libX11/libX11.html#Focus_Events_Generated_by_Grabs

  8. XGrabKey源代码:http://cgit.freedesktop.org/xorg/lib/libX11/tree/src/GrKey.c也许我们可以修改它来摆脱焦点事件?

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

  10. 我正在为按键组合(和鼠标移动)地图软件写一次按键:https://code.google.com/p/diyism-myboard/

    我在Windows中使用RegisterHotKey()和UnRegisterHotKey()实现了它:https://code.google.com/p/diyism-myboard/downloads/detail?name=MyBoard.pas

    我想用XGrabKey()和XUngrabKey()将其迁移到Linux:https://code.google.com/p/diyism-myboard/downloads/detail?name=myboard.py

    我已经创造了10美元的赏金来解决这个问题。我们需要更多的支持者才能获得赏金。 https://www.bountysource.com/issues/1072081-right-button-menu-flashes-while-jkli-keys-move-the-mouse-pointer

7 个答案:

答案 0 :(得分:12)

我在90年代早期看过Irix,ultrix和solaris的全球热键,因为它在我的Acorn BBC电脑上很容易做到。最终,我们决定以低于xlib的级别以非便携方式使用一些专有代码来解决这个问题。 由于我们的软件安装需要作为超级用户权限,我们能够插入适当的软件挂钩作为守护进程。

对于Linux(现在),您应该通过在操作系统级别处理键盘事件来寻找软件解决方案。我先来看看这里:http://code.google.com/p/logkeys/

更通用的解决方案是使用带有USB输入和USB输出的小型PC板,它可以作为鼠标和键盘对计算机起作用,并根据需要翻译键盘键。但如果你想经常改变映射,这就不会那么灵活了。

答案 1 :(得分:12)

我当前的代码(来自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()

当我按下“shift”键时,焦点丢失,当我释放它时,焦点会回来。

答案 2 :(得分:5)

看起来XQueryKeymap会对你进行排序。请参阅下面的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);
}

注意,这不是经过测试的代码,也不是我的 - 我只是在互联网上找到它。

答案 3 :(得分:5)

最后,正如你所知道linux意味着自由,我修改了xserver以摆脱抓斗式焦点:

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

我还需要在gtk上下文菜单中删除XGrabKeyboard:

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

现在myboard.py效果很好。

如果您使用的是ubuntu raring-updates版本,可以试试:

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

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

答案 4 :(得分:3)

我有一个想法,我很确定会工作,但我必须睡觉,不能自己测试,它不漂亮,因为我不认为有任何方法在X中做你想做的事。这是我想到的步骤。简而言之:在X中禁用键盘,从较低级别的api读取事件,并自己选择性地将它们提供给X.您必须在X中禁用键盘,否则,您可以查看事件,但不能停止它;你会把它和X一起读,而不是拦截它。

所以在这里它被打破了:

1)运行xinput -list以使键盘X正在使用

2)运行xinput list-props id以查找Device Enabled属性

3)运行xinput set-prop id prop 0以禁用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

我不知道xinput在xlib级别上是如何工作的,为了简化实现,我只是将它调用到shell中。

4)打开/dev/input/eventX,其中X是键盘设备。我实际上是在/ dev / input / by-id下搜索名称(在xinput -list中给出)并以这种方式打开它。这可能在某些时候需要root,因为这些权限通常都是非常严格的限制。

5)从那里读取键盘输入:

输入事件的数据格式为:

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
}

整数是32位,ushorts是16位。由于您只对键盘输入感兴趣,因此您可以非常简单地执行此操作:

  • 读取并忽略8个字节。

  • 下一个字节应为1,然后下一个字节为0.如果不是,请跳过此事件

  • 下一个字节是密钥代码的小端,因为它是&lt; 255键,这是足够好的

  • 跳过下一个字节。

  • 读取下一个字节以查看是否按下或释放了

  • 跳过接下来的三个字节

6)当你得到一个你想要捕获的事件时,请自己处理。否则,使用XSendEvent将其发送到X,以便正常处理。将从/ dev / input获得的硬件代码映射到适当的keysym可能是一个技巧,但我相当确定xlib中有一个函数来帮助解决这个问题。

7)转到5并循环直到你完成

8)确保将所有内容都设置为退出时的状态,或者您可以将用户的键盘输入分解为X!

我建议使用第二个USB键盘进行测试,你可以使用/ dev / input和xinput相互独立地禁用和监听键盘,所以如果你崩溃了,你仍然可以使用第一个键盘并正常工作。 (实际上,我认为用第二个键盘故意做到这一点非常酷,加倍热键!)

但是,是的,需要root并且可能让键盘“脱离”X并不是很漂亮,并且使用SendKey转发可能说起来容易做起来难,但我很确定这个工作并为您提供最大的灵活性。

答案 5 :(得分:1)

对于编写一个键(快捷方式)映射软件,还要查看libtermkey,一个终端键输入库(用C编写),它识别XTerm风格的鼠标位置/按钮报告,特殊键(如箭头)和功能键),包括Ctrl-Left等“修改”键。

例如,POE::Wheel::TermKey是一个围绕libtermkey库的异步perl包装器,它提供了一种抽象方式来读取基于终端的程序中的按键事件。“

答案 6 :(得分:0)

您可以使用XQueryKeymap来阅读事件,然后您可以使用XTestKey发送退格键事件,然后使用您想要按下的键。更好的是,您可以register hotkeys获取所有键盘事件,然后使用XTestKey生成键事件。 BTW,KDE的“自定义快捷方式”控制模块允许使用快捷方式生成按键。 Source code