Record module for python by pyrex

----pyrecord.pyx----
cdef extern from "X11/X.h":
    ctypedef unsigned long Time

    # declare the #define constants, don't have to provide their initial values
    enum:
        KeyPress=2
        KeyRelease
        ButtonPress
        ButtonRelease
        MotionNotify
        FocusIn
        FocusOut

cdef extern from "X11/Xproto.h":
    # only need to declare the members you need to access
    # and no nested ctypedef/cdef supported so far
    ctypedef struct S1:
        unsigned char type
        unsigned char detail

    ctypedef struct S2:
        unsigned short rootX
        unsigned short rootY

    ctypedef union U:
        S1 u
        S2 keyButtonPointer

    ctypedef struct xEvent:
        U u

                
cdef extern from "X11/Xlib.h":
    enum Bool:
        False=0
        True=1

    ctypedef void *XPointer
    ctypedef int Status

    # opaque struct which you only use its pointer type
    ctypedef struct Display

    Display *XOpenDisplay(char* display_name)
    int XCloseDisplay(Display *display)
    int XFlush(Display* display)
    int XFree (void *data)

    int (*XSynchronize(Display *display, Bool onoff)) ()
   
    ctypedef unsigned int XID
    ctypedef XID KeySym
    ctypedef unsigned char KeyCode

    KeyCode XKeysymToKeycode(Display* display, KeySym keysym)
    KeySym XStringToKeysym(char* string)
    KeySym XKeycodeToKeysym(Display *display, KeyCode keycode, int index)
    char *XKeysymToString(KeySym keysym)

cdef extern from "X11/extensions/record.h":
    enum:
        XRecordCurrentClients=1
        XRecordFutureClients
        XRecordAllClients

    enum:
        XRecordFromServer=0
        XRecordFromClient

    ctypedef struct XRecordRange8:
        unsigned char       first
        unsigned char       last

    ctypedef struct XRecordRange:
        XRecordRange8     device_events

    ctypedef struct XRecordInterceptData:
        Time                server_time
        int                 category
        unsigned char       *data

    ctypedef unsigned long XRecordClientSpec
    ctypedef unsigned long XRecordContext

    XRecordRange *XRecordAllocRange()
   
    XRecordContext XRecordCreateContext(Display*, int, XRecordClientSpec*, int, XRecordRange**, int)

    # typedef function pointer
    ctypedef void (*XRecordInterceptProc) (XPointer, XRecordInterceptData*)   
    Status XRecordEnableContextAsync(Display*, XRecordContext, XRecordInterceptProc, XPointer)

    void XRecordProcessReplies(Display*)

    Status XRecordDisableContext(Display*, XRecordContext)
    Status XRecordFreeContext(Display*, XRecordContext)
    void XRecordFreeData(XRecordInterceptData*)

cdef void _event_callback_wrapper (XPointer py_fn, XRecordInterceptData *hook):
    cdef xEvent *data

    # casting
    data = <xEvent*> hook.data
   
    if hook.category != XRecordFromServer:
        XRecordFreeData (hook)
        return

    type = data.u.u.type
    event_spec = {'type':type}
   
    if type in (KeyPress, KeyRelease):
        event_spec['keysym'] = XKeysymToString(XKeycodeToKeysym(g_disp, data.u.u.detail, 0))
       
    elif type in (ButtonPress, ButtonRelease):
        event_spec['button'] = data.u.u.detail
       
    elif type == MotionNotify:
        event_spec['rootX'] = data.u.keyButtonPointer.rootX
        event_spec['rootY'] = data.u.keyButtonPointer.rootY

    # delegate to python callback method
    (<object>py_fn) (event_spec)
   
cdef Display *g_disp
cdef class XRecord:
    cdef Display *data_disp
    cdef Display *ctrl_disp

    cdef XRecordRange *rr
    cdef XRecordClientSpec  rcs
    cdef XRecordContext   rc

    # __new__ is also effective for constructor
    def __init__(self):
        self.ctrl_disp = XOpenDisplay (NULL)
        self.data_disp = XOpenDisplay (NULL)

        global g_disp
        g_disp = self.ctrl_disp

        XSynchronize(self.ctrl_disp, True)

        self.rr = XRecordAllocRange ()

        # use '.' instead of '->' to access the data member
        self.rr.device_events.first = KeyPress
        self.rr.device_events.last = MotionNotify
        self.r
cs = XRecordAllClients

        self.rc = XRecordCreateContext (self.ctrl_disp, 0, &(self.rcs), 1, &(self.rr), 1)
       
    def set_callback(self, cb):
        XRecordEnableContextAsync (self.data_disp, self.rc, _event_callback_wrapper, <void*>cb) #casting

    # __del__ does not work for pyrex extension type
    def __dealloc__(self):
        XRecordDisableContext (self.ctrl_disp, self.rc)
        XRecordFreeContext (self.ctrl_disp, self.rc)
        XFree (self.rr)

        XCloseDisplay (self.data_disp)
        XCloseDisplay (self.ctrl_disp)

    def replies(self):
        XRecordProcessReplies (self.data_disp)

----test.py----
from pyrecord import *

stop = 0
def test_cb(event):
    if event['type'] in (2, 3):
        print event['type'], event['keysym']
        if event['keysym'] == 'Escape':
            global stop
            stop = 1
    elif event['type'] in (4, 5):
        print event['type'], event['button']
    elif event['type'] == 6:
        print event['rootX'], event['rootY']

r=XRecord()
r.set_callback(test_cb)

while not stop:
    r.replies()

del r

X RECORD extension example

/*
 * To enable record extension in Xorg/XFree86, add the following  line in
 * Section "Module"
 *     Load         "record"
 */

#include <stdio.h>
#include <stdlib.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.
 */
Display *data_disp = NULL;
Display *ctrl_disp = NULL;

/* stop flag */
int stop = 0;

void event_callback (XPointer, XRecordInterceptData*);

int main (int argc, char **argv)
{
  ctrl_disp = XOpenDisplay (NULL);
  data_disp = XOpenDisplay (NULL);

  if (!ctrl_disp || !data_disp) {
    fprintf (stderr, "Error to open local display!\n");
    exit (1);
  }


  /*


   * we must set the ctrl_disp to sync mode, or, when we the enalbe


   * context in data_disp, there will be a fatal X error !!!


   */

  XSynchronize(ctrl_disp,True);

  int major, minor;
  if (!XRecordQueryVersion (ctrl_disp, &major, &minor)) {
    fprintf (stderr, "RECORD extension not supported on this X server!\n");
    exit (2);
  }
 
  printf ("RECORD extension for local server is version is %d.%d\n", major, minor);

  XRecordRange  *rr;
  XRecordClientSpec  rcs;
  XRecordContext   rc;

  rr = XRecordAllocRange ();
  if (!rr) {
    fprintf (stderr, "Could not alloc record range object!\n");
    exit (3);
  }

  rr->device_events.first = KeyPress;
  rr->device_events.last = MotionNotify;
  rcs = XRecordAllClients;

  rc = XRecordCreateContext (ctrl_disp, 0, &rcs, 1, &rr, 1);
  if (!rc) {
    fprintf (stderr, "Could not create a record context!\n");
    exit (4);
  }
 
  if (!XRecordEnableContextAsync (data_disp, rc, event_callback, NULL)) {
    fprintf (stderr, "Cound not enable the record context!\n");
    exit (5);
  }

  while (stop != 1) {
    XRecordProcessReplies (data_disp);
  }

  XRecordDisableContext (ctrl_disp, rc);
  XRecordFreeContext (ctrl_disp, rc);
  XFree (rr);
 
  XCloseDisplay (data_disp);
  XCloseDisplay (ctrl_disp);
  return 0;
}

void event_callback(XPointer priv, XRecordInterceptData *hook)
{
  /* FIXME: we need use XQueryPointer to get the first location */
  static int cur_x = 0;
  static int cur_y = 0;

  if (hook->category != XRecordFromServer) {
    XRecordFreeData (hook);
    return;
  }

  XRecordDatum *data = (XRecordDatum*) hook->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 = hook->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", XKeysymToString(XKeycodeToKeysym(ctrl_disp, keycode, 0)));
    break;
  case KeyRelease:
    printf ("KeyRelease: \t%s", XKeysymToString(XKeycodeToKeysym(ctrl_disp, keycode, 0)));
    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);

  XRecordFreeData (hook);
}