41

有谁知道 xlib 函数可以在不丢失原始焦点的情况下捕获按键事件?如何摆脱它?

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

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

XGrabKey 将失去对按下键的关注并恢复对释放键的关注。

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

参考:

  1. ...XGrabKey 将窃取焦点... https://bugs.launchpad.net/gtkhotkey/+bug/390552/comments/8

  2. ...程序接收控制以响应组合键执行某些操作。同时,程序暂时被聚焦... 在XGrabKey(board)期间,发现哪个窗口被聚焦

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

  4. ...我看不到一种方法来提供 metacity 当前的桌面更改行为(同时更改和显示弹出对话框)而不导致窗口上出现 Grab 类型的焦点... 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. 抓取键盘不允许改变焦点... 抓取键盘不允许改变焦点

  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

我正在写一个按键组合(和鼠标移动)映射软件: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

4

8 回答 8

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”键时,焦点丢失,当我松开它时,焦点又回来了。

于 2013-03-14T08:30:10.530 回答
11

早在 90 年代初期,我就查看了 Irix、ultrix 和 solaris 的全局热键,因为在我的 Acorn BBC 计算机上很容易做到这一点。最终,我们决定在低于 xlib 的级别上使用一些专有代码以不可移植的方式解决这个问题。由于无论如何我们的软件安装都需要超级用户权限,因此我们能够插入适当的软件挂钩作为守护进程。

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

更通用的解决方案是使用带有 USB 输入和 USB 输出的小型 PC 板,它对计算机充当鼠标和键盘,并在必要时转换键盘按键。但是,如果您想经常更改映射,这不会那么灵活。

于 2013-03-10T13:34:37.407 回答
7

最后,你知道 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

于 2013-11-19T12:22:17.317 回答
5

看起来 XQueryKeymap 会给你排序。有关我发现的 C++ 源代码,请参见下文:

/* 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);
}

请注意,这不是经过测试的代码,也不是我的——我只是在 Internet 上找到的。

于 2013-03-10T13:43:25.570 回答
3

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

所以在这里分解:

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

2) 运行xinput list-propsid 找到 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) Open /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
}

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

  • 读取并忽略 8 个字节。

  • 下一个字节应该是1,然后下一个是0。如果不是,跳过这个事件

  • 下一个字节是键码的小端,因为有 < 255 个键,这就足够了

  • 跳过下一个字节。

  • 读取下一个字节以查看是按下还是释放

  • 跳过接下来的三个字节

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

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

8) 确保将所有内容设置回退出时的状态,否则可能会将用户的键盘输入中断为 X!

我建议使用第二个 USB 键盘对此进行测试,您可以使用 /dev/input 和 xinput 彼此独立地禁用和收听键盘,因此如果您崩溃,您仍然可以使用第一个键盘并正常工作。(实际上,我认为故意用第二个键盘来做这件事会很酷,热键加倍!)

但是,是的,需要 root 并可能将键盘与 X “分离”一点都不漂亮,而且使用 SendKey 进行转发可能说起来容易做起来难,但我很确定这奏效并为您提供最大的灵活性.

于 2013-11-16T05:41:03.380 回答
1

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

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

于 2014-02-04T11:38:27.870 回答
0

您可以使用XQueryKeymap读取事件,然后您可以使用XTestKey发送退格键事件,然后发送您想要按下的键。更好的是,您可以为所有键盘事件注册热键,然后使用 XTestKey 生成键事件。顺便说一句,KDE 的“自定义快捷方式”控制模块允许使用快捷方式来生成按键。源代码

于 2013-11-16T07:50:39.360 回答
0

虽然我看到这个问题很老,但它仍然是相关的,我找到了一种比修改 xorg-server 更便携的解决方法。

基本上,当抓取窗口是主动聚焦窗口时,XGrabKey 不会发送焦点事件。

如果您观看活动窗口并在焦点更改为新窗口时重置您的抓取,则不会生成事件。我发现监视PropertyNotify 事件并查看_NET_ACTIVE_WINDOW窗口属性可以很好地获取该信息。

我还没有发现将抓取设置为当前活动窗口的任何不利影响,或者坦率地说有任何影响。

于 2021-09-17T00:46:06.107 回答