デスクトップをクリックするたびにカーソルの下に画像を表示しようとしています。これを実現するためにxcbを使用することにしました。ルートウィンドウからポインタをキャプチャする必要があると考えました(これを行うためのより良い方法はわかりません)。クリックした場所に関係なく、常に画像を表示したいからです。画像を表示するアプリケーションは、私の通常のワークフローに干渉しないはずです。
これまでのところ、ポインタをキャプチャする方法は次のとおりです。
#include <X11/Xutil.h>
#include <X11/Xlib-xcb.h>
#include <X11/Xutil.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
void setup(xcb_connection_t *connection) {
xcb_generic_error_t *err;
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
xcb_void_cookie_t grab_cookie = xcb_grab_button(connection, True, screen->root, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_BUTTON_INDEX_1, XCB_MOD_MASK_ANY);
xcb_generic_error_t *error = xcb_request_check(connection, grab_cookie);
if (error != NULL) {
xcb_disconnect(connection);
perror("could not subscribe to events on a window, bailing out");
exit(1);
}
free(error);
xcb_flush(connection);
}
int main(int argc, char *argv[]) {
xcb_generic_event_t *e;
Display *dpy = XOpenDisplay(NULL);
xcb_connection_t *connection = XGetXCBConnection(dpy);
setup(connection);
while ((e = xcb_wait_for_event(connection))) {
switch(e->response_type & ~0x80) {
case XCB_BUTTON_PRESS:
printf("Click.\n");
break;
default:
break;
}
free(e);
}
xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME);
}
カーソルをつかむコード行は、setup
メソッドにあります。
xcb_void_cookie_t grab_cookie = xcb_grab_button(connection, True, screen->root, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_BUTTON_INDEX_1, XCB_MOD_MASK_ANY);
「自動修正」man xcb_grab_button
でわかる限り、関数の5番目の引数としてXCB_GRAB_MODE_ASYNC
を指定しても、ポインターイベントは影響を受けません(マニュアルには別のことが書かれていますが、一般的にはかなりです)貧しいので、それが間違っていなかったら私は非常に驚きます)。ただし、これは当てはまりません。クリックすると、クリックがアプリケーションに飲み込まれ、Firefoxがそれに反応しません。
クリックイベントが食べられないようにカーソルをつかむにはどうすればよいですか?そのような機能がXに存在しない場合、クリックイベントを単に「再送信」することをお勧めしますか、それともより良いオプションがありますか?この情報が何かを変更した場合に備えて、私のウィンドウマネージャーはi3です。
私は謎を解きました。どうやらXCB_GRAB_MODE_ASYNC
は私が思っていたようには機能しません。xcb_grab_pointer
マニュアルには、ポインタイベント処理が正常に続行されると書かれています。これは、他のクライアントに伝播されるイベントを参照すると思いましたが、そうではありません。イベントはまだ私のアプリケーションによってのみ聞こえます。イベントを伝播するには、受信後にイベントを再生する必要があります。
xcb_generic_event_t *e;
xcb_generic_error_t *err;
xcb_connection_t *connection = xcb_connect(NULL, NULL);
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
xcb_void_cookie_t grab_cookie = xcb_grab_button_checked(connection, True, screen->root, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_BUTTON_INDEX_1, XCB_MOD_MASK_ANY);
xcb_generic_error_t *error = xcb_request_check(connection, grab_cookie);
if (error != NULL) {
xcb_disconnect(connection);
perror("could not subscribe to events on a window, bailing out");
exit(1);
}
free(error);
do {
xcb_allow_events(connection, XCB_ALLOW_REPLAY_POINTER, XCB_CURRENT_TIME);
e = xcb_poll_for_event(connection);
if(!e) {
continue;
}
switch(e->response_type & EVENT_MASK) {
case XCB_BUTTON_RELEASE:
case XCB_BUTTON_PRESS:
printf("Hello.\n");
break;
default:
break;
}
free(e);
} while(1);
xcb_ungrab_pointer(connection, XCB_TIME_CURRENT_TIME);
Do-whileループの前に、index1ボタンのポインターの押下と解放が取得されます。 5番目の引数はXCB_GRAB_MODE_SYNC
である必要があります。これにより、Xサーバーはxcb_allow_events
が呼び出されるまでイベントをキューに入れます(Xlibマニュアルのman XGrabPointer
に詳細情報が記載されていますが、詳細、XCB用ではないことを考えると)。 while
ループ内のxcb_allow_events
への後続の呼び出しは、ポインターイベント処理を解凍(フリーズ解除)します。 XCB_ALLOW_REPLAY_POINTER
パラメータを渡すと、ボタンイベントが再生されます。
理由はよくわかりませんが、ループ内でxcb_poll_for_event
を使用することが重要です。