web-dev-qa-db-ja.com

xcb:イベントを伝播しない非同期ポインターグラブ

デスクトップをクリックするたびにカーソルの下に画像を表示しようとしています。これを実現するために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です。

4
PawkyPenguin

私は謎を解きました。どうやら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を使用することが重要です。

3
PawkyPenguin