7

I am trying to be notified about any pointer motion. Since I don't want to run as the window manager, I need to set XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION on all windows which I do both on startup and when I get a create notify event.

This seems to work fine in general and I receive motion notify events on all windows. However, somehow, this isn't true for Google Chrome windows. I checked the event mask by explicitly querying it afterwards and it is correctly set. I also don't see anything unusual in the propagation mask.

What could cause Google Chrome to not report motion notify events? AFAIK, the X protocol doesn't allow that except for active pointer grabs which Chrome surely doesn't have.

Here is how I register myself on all existing windows. I call register_events on the root window and whenever I receive a create notify event as well:

static void register_events(xcb_window_t window) {
    xcb_void_cookie_t cookie = xcb_change_window_attributes_checked(connection,                                         
        window, XCB_CW_EVENT_MASK, (uint32_t[]) { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_LEAVE_WINDOW });
    xcb_generic_error_t *error = xcb_request_check(connection, cookie);
    if (error != NULL) {
        xcb_disconnect(connection);
        errx(EXIT_FAILURE, "could not subscribe to events on a window, bailing out");
    }   
}

static void register_existing_windows(void) {
    xcb_query_tree_reply_t *reply;
    if ((reply = xcb_query_tree_reply(connection, xcb_query_tree(connection, root), 0)) == NULL) {
        return;
    }   

    int len = xcb_query_tree_children_length(reply);
    xcb_window_t *children = xcb_query_tree_children(reply);
    for (int i = 0; i < len; i++) {
        register_events(children[i]);
    }   

    xcb_flush(connection);
    free(reply);
}
Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100
  • 1
    What do you get from `xev` if you attach it to a Chome window? – Andrew Henle May 07 '15 at 10:25
  • @AndrewHenle Attaching it and moving my mouse there, moving it around, letting it rest and leaving the window again only gives me Enter/LeaveNotify, KeymapNotify and FocusIn/Out (http://pastebin.com/XQ3ZkVhW) – Ingo Bürk May 07 '15 at 11:09
  • I should also point out that the same observation can be made with Chromium on a different machine. – Ingo Bürk May 07 '15 at 11:20
  • 1
    You're seeing **zero** events from Chrome windows then? Even mouse clicks are getting swallowed? What does `xprop` and `xwininfo -all` show for those windows? – Andrew Henle May 07 '15 at 12:10
  • @AndrewHenle For a click I receive EnterNotify, KeymapNotify, PropertyNotify, ConfigureNotify and then LeaveNotify (yes, all for a simple click on a focused Chrome window). Pressing keys triggers the corresponding events. [xprop output](http://pastebin.com/Cu1WLxmW), [xwininfo on reparenting container](http://pastebin.com/A3mFqp3h), [xwininfo on actual container](http://pastebin.com/7y89fCr8) – Ingo Bürk May 07 '15 at 13:27
  • I've noticed that on arandr I also don't receive motion notify events – but unlike Chrome, I can see them on there with xev. – Ingo Bürk May 07 '15 at 15:59
  • Been busy - but this is weird. Like you posted earlier, I don't see how Chrome is swallowing the events without a don't-propagate mask stopping the event being sent. See http://menehune.opt.wfu.edu/Kokua/Irix_6.5.21_doc_cd/usr/share/Insight/library/SGI_bookshelves/SGI_Developer/books/XLib_PG/sgi_html/ch08.html#S2-1002-8-9 Is it possible Chrome is setting the don't-propagate mask later? – Andrew Henle May 08 '15 at 22:20
  • If any window in the tree selects the event, I will not receive it if I only subscribe to the top level window. Seems right to me? If all events propagated unless being stopped from doing so in the do not propagate mask, selecting the event on the root window would suffice, but it doesn't. – Ingo Bürk May 09 '15 at 22:15
  • Actually no, it still doesn't make sense, since I'm a different client. Maybe chrome does set a do not propagate mask somewhere in the tree. I haven't checked it yet and won't get around to doing it for now. Maybe after my vacation :) – Ingo Bürk May 09 '15 at 22:21

3 Answers3

8

The Chrome windows appear to be comprised of quite the tree of nested child windows. It appears you'll need to walk the tree of windows and monitor them all. This code picks up pointer motion events across the entirety of my Chrome windows:

#include <stdio.h>
#include <stdlib.h>
#include <xcb/xcb.h>
#include <X11/Xlib.h>

static void register_events(xcb_connection_t *conn,
                            xcb_window_t window) {
  xcb_void_cookie_t cookie =
    xcb_change_window_attributes_checked(conn,
                                         window, XCB_CW_EVENT_MASK,
                                         (uint32_t[]) {
                                           XCB_EVENT_MASK_POINTER_MOTION });
  xcb_generic_error_t *error = xcb_request_check(conn, cookie);
  if (error != NULL) {
    xcb_disconnect(conn);
    exit(-1);
  }
}

static void register_existing_windows(xcb_connection_t *conn,
                                      xcb_window_t root) {
  int i, len;
  xcb_window_t *children;
  xcb_query_tree_reply_t *reply;
  if ((reply = xcb_query_tree_reply(conn,
                                    xcb_query_tree(conn, root), 0))
      == NULL)
    {
      return;
    }

  len = xcb_query_tree_children_length(reply);
  children = xcb_query_tree_children(reply);
  for (i = 0; i < len; i++) {
    register_events(conn, children[i]);
    register_existing_windows(conn, children[i]);
  }

  xcb_flush(conn);
}

void main(void) {
  int i=0;

  /* Open the connection to the X server */
  xcb_connection_t *conn = xcb_connect (NULL, NULL);

  /* Get the first screen */
  xcb_screen_t *screen = xcb_setup_roots_iterator (xcb_get_setup (conn)).data;

  register_existing_windows(conn, screen->root);

  while(1) {
    xcb_generic_event_t *evt;
    evt = xcb_wait_for_event(conn);
    printf("%i\n", i++);
  }
}

(That's just intended as proof of concept, and not very nice.)

Jay Kominek
  • 8,674
  • 1
  • 34
  • 51
  • I had a hunch that it could be a nesting kind of issue, but only looked "one level deep". Didn't think I'd have to go *all* recursive. I'll give this a shot and get back to you. Thanks for the answer! – Ingo Bürk May 07 '15 at 18:46
  • Yeah, it seems this was the problem. I was thinking too complicated about propagation masks. I checked before fixing it now that if I opened a *new* Chrome window I got all events becaue I receive create notify events even for all subwindows. Just this initial part was flawed. Thanks, this was driving me crazy! – Ingo Bürk May 07 '15 at 18:59
  • 1
    Ahh, I see it's been solved. Welcome to the crazy world of XWindows. – Andrew Henle May 08 '15 at 22:21
  • 1
    I changed the accepted answer to my own because using XInput is orders of magnitude better, but of course that wasn't exactly my original question. So I hope it's clear that your answer was valid and very helpful, but I'd just like to steer visitors into an even better direction. – Ingo Bürk Aug 30 '15 at 21:37
2

While @Jay Kominek's answer was helpful and valid, I've come to realize now that using the Xinput extension provides a much better approach as it won't interfere with applications whatsoever.

Simply selecting on the entire tree causes all kinds of issues, e.g., hover doesn't work in Chrome anymore.

Ingo Bürk
  • 19,263
  • 6
  • 66
  • 100
0

xcb provides xcb_grab_pointer to capture pointer event without registe on specific window.

#include <stdlib.h>
#include <stdio.h>

#include <xcb/xcb.h>

void
print_modifiers (uint32_t mask)
{
  const char **mod, *mods[] = {
    "Shift", "Lock", "Ctrl", "Alt",
    "Mod2", "Mod3", "Mod4", "Mod5",
    "Button1", "Button2", "Button3", "Button4", "Button5"
  };
  printf ("Modifier mask: ");
  for (mod = mods ; mask; mask >>= 1, mod++)
    if (mask & 1)
      printf(*mod);
  putchar ('\n');
}

int
main ()
{
  xcb_connection_t    *c;
  xcb_screen_t        *screen;
  xcb_window_t         win;
  xcb_generic_event_t *e;
  uint32_t             mask = 0;

  /* Open the connection to the X server */
  c = xcb_connect (NULL, NULL);

  /* Get the first screen */
  screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;

  mask = XCB_EVENT_MASK_BUTTON_PRESS   |
              XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION;

  xcb_grab_pointer(c, false, screen->root, mask, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, XCB_CURRENT_TIME);

  xcb_flush (c);

  while ((e = xcb_wait_for_event (c))) {
    switch (e->response_type & ~0x80) {
    case XCB_BUTTON_PRESS: {
      xcb_button_press_event_t *ev = (xcb_button_press_event_t *)e;
      print_modifiers(ev->state);

      switch (ev->detail) {
      case 4:
        printf ("Wheel Button up in window %ld, at coordinates (%d,%d)\n",
                ev->event, ev->event_x, ev->event_y);
        break;
      case 5:
        printf ("Wheel Button down in window %ld, at coordinates (%d,%d)\n",
                ev->event, ev->event_x, ev->event_y);
        break;
      default:
        printf ("Button %d pressed in window %ld, at coordinates (%d,%d)\n",
                ev->detail, ev->event, ev->event_x, ev->event_y);
      }
      break;
    }
    case XCB_BUTTON_RELEASE: {
      xcb_button_release_event_t *ev = (xcb_button_release_event_t *)e;
      print_modifiers(ev->state);

      printf ("Button %d released in window %ld, at coordinates (%d,%d)\n",
              ev->detail, ev->event, ev->event_x, ev->event_y);
      break;
    }
    case XCB_MOTION_NOTIFY: {
      xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t *)e;

      printf ("Mouse moved in window %ld, at coordinates (%d,%d)\n",
              ev->event, ev->event_x, ev->event_y);
      break;
    }
    default:
      /* Unknown event type, ignore it */
      printf("Unknown event: %d\n", e->response_type);
      break;
    }
    /* Free the Generic Event */
    free (e);
  }

  return 0;
}