Search code examples
x11recordxlibxcb

XCB + record x11 extension example


Can you provide me example code (on any language) which use record extension via XCB? There are many examples of how to do it with Xlib (https://gist.github.com/whym/402801) but I can't understand how to translate this part of Xlib code to XCB:

# Create a recording context; we only want key and mouse events
ctx = record_dpy.record_create_context(
    0,
    [record.AllClients],
    [{
        'core_requests': (0, 0),
        'core_replies': (0, 0),
        'ext_requests': (0, 0, 0, 0),
        'ext_replies': (0, 0, 0, 0),
        'delivered_events': (0, 0),
        'device_events': (X.KeyPress, X.KeyPress),
        'errors': (0, 0),
        'client_started': False,
        'client_died': False,
    }]
)

XCB docs for xcb_record_create_context(...) is totally mess. Any working code will be helpful.


Solution

  • The short version: You don't want to work with the RECORD extension. It's messy with Xlib and it's plain ugly with xcb.

    Anyway, some example using Xlib and libXtst: https://github.com/nibrahim/showkeys/blob/master/tests/record-example.c

    The same code (roughly) translated to xcb (notice that this has to parse a blob of data itself that normally libXtst helps with, no idea if this really is possible in practice, but the spec says that this is possible in theory):

    #include <stdio.h>
    #include <stdlib.h>
    #include <xcb/xcb.h>
    #include <xcb/record.h>
    #include <X11/Xlibint.h>
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    #include <X11/cursorfont.h>
    #include <X11/keysymdef.h>
    #include <X11/keysym.h>
    #include <X11/extensions/record.h>
    #include <X11/extensions/XTest.h>
    
    /* for this struct, refer to libxnee */
    typedef union {
      unsigned char    type ;
      xEvent           event ;
      xResourceReq     req   ;
      xGenericReply    reply ;
      xError           error ;
      xConnSetupPrefix setup;
    } XRecordDatum;
    
    /*
     * FIXME: We need define a private struct for callback function,
     * to store cur_x, cur_y, data_disp, ctrl_disp etc.
     */
    static xcb_connection_t *data_disp = NULL;
    static xcb_connection_t *ctrl_disp = NULL;
    
    /* stop flag */
    int stop = 0;
    
    size_t event_callback(xcb_record_enable_context_reply_t *reply, uint8_t *data_);
    
    int main ()
    {
      ctrl_disp = xcb_connect (NULL, NULL);
      data_disp = xcb_connect (NULL, NULL);
    
      if (xcb_connection_has_error(ctrl_disp) || xcb_connection_has_error(data_disp)) {
        fprintf (stderr, "Error to open local display!\n");
        exit (1);
      }
    
      const xcb_query_extension_reply_t *query_ext = xcb_get_extension_data(ctrl_disp, &xcb_record_id);
      if (!query_ext) {
        fprintf (stderr, "RECORD extension not supported on this X server!\n");
        exit (2);
      }
    
      xcb_record_query_version_reply_t *version_reply = xcb_record_query_version_reply(ctrl_disp,
          xcb_record_query_version(ctrl_disp, XCB_RECORD_MAJOR_VERSION, XCB_RECORD_MINOR_VERSION), NULL);
      if (!version_reply) {
        fprintf (stderr, "This should not happen: Can't get RECORD version\n");
        exit (2);
      }
    
      printf ("RECORD extension for local server is version is %d.%d\n", version_reply->major_version, version_reply->minor_version);
      free(version_reply);
    
      xcb_record_range_t rr;
      xcb_record_client_spec_t rcs;
      xcb_record_context_t rc = xcb_generate_id(ctrl_disp);
    
      memset(&rr, 0, sizeof(rr));
      rr.device_events.first = XCB_KEY_PRESS;
      rr.device_events.last = XCB_MOTION_NOTIFY;
      rcs = XCB_RECORD_CS_ALL_CLIENTS;
    
      xcb_void_cookie_t create_cookie = xcb_record_create_context_checked (ctrl_disp, rc, 0, 1, 1, &rcs, &rr);
      xcb_generic_error_t *error = xcb_request_check(ctrl_disp, create_cookie);
      if (error) {
        fprintf (stderr, "Could not create a record context!\n");
        free(error);
        exit (4);
      }
    
      /* The above xcb_request_check() makes sure the server already handled the
       * CreateContext request, thus this isn't needed anymore:
       * XSync(ctrl_disp, 0);
       */
    
      xcb_record_enable_context_cookie_t cookie = xcb_record_enable_context(data_disp, rc);
    
      while (!stop) {
        xcb_record_enable_context_reply_t *reply = xcb_record_enable_context_reply(data_disp, cookie, NULL);
        if (!reply)
          break;
        if (reply->client_swapped) {
          fprintf (stderr, "I am too lazy to implement byteswapping\n");
          exit(42);
        }
    
        if (reply->category == 0 /* XRecordFromServer */) {
          size_t offset = 0;
          uint8_t *data = xcb_record_enable_context_data(reply);
          while (offset < reply->length<<2) {
            offset += event_callback(reply, &data[offset]);
          }
        }
        free(reply);
      }
    
      xcb_record_disable_context (ctrl_disp, rc);
      xcb_record_free_context (ctrl_disp, rc);
      xcb_flush (ctrl_disp);
    
      xcb_disconnect (data_disp);
      xcb_disconnect (ctrl_disp);
      return 0;
    }
    
    size_t event_callback(xcb_record_enable_context_reply_t *reply, uint8_t *data_)
    {
      /* FIXME: we need use XQueryPointer to get the first location */
      static int cur_x = 0;
      static int cur_y = 0;
    
      XRecordDatum *data = (XRecordDatum*) data_;
    
      int event_type = data->type;
    
      BYTE btncode, keycode;
      btncode = keycode = data->event.u.u.detail;
    
      int rootx = data->event.u.keyButtonPointer.rootX;
      int rooty = data->event.u.keyButtonPointer.rootY;
      int time = reply->server_time;
    
      switch (event_type) {
      case KeyPress:
        /* if escape is pressed, stop the loop and clean up, then exit */
        if (keycode == 9) stop = 1;
    
        /* Note: you should not use data_disp to do normal X operations !!!*/
        /*printf ("KeyPress: \t%s\n", XKeysymToString(XKeycodeToKeysym(ctrl_disp, keycode, 0)));*/
        printf ("KeyPress: \t%d\n", keycode);
        break;
      case KeyRelease:
        /*printf ("KeyRelease: \t%s\n", XKeysymToString(XKeycodeToKeysym(ctrl_disp, keycode, 0)));*/
        printf ("KeyRelease: \t%d\n", keycode);
        break;
      case ButtonPress:
        /* printf ("ButtonPress: /t%d, rootX=%d, rootY=%d", btncode, cur_x, cur_y); */
        break;
      case ButtonRelease:
        /* printf ("ButtonRelease: /t%d, rootX=%d, rootY=%d", btncode, cur_x, cur_y); */
        break;
      case MotionNotify:
        /* printf ("MouseMove: /trootX=%d, rootY=%d",rootx, rooty); */
        cur_x = rootx;
        cur_y = rooty;
        break;
      case CreateNotify:
        break;
      case DestroyNotify:
        break;
      case NoExpose:
        break;
      case Expose:
        break;
      default:
        break;
      }
    
      printf (", time=%d\n", time);
    
      if (data_[0] == 0)
        /* reply */
        return ((*(uint32_t*) &data_[4]) + 8) << 2;
      /* Error or event TODO: What about XGE events? */
      return 32;
    }