presses work when clicks dont on the frame around the titlebar..
[dana/openbox.git] / openbox / pointer.c
1 #include "pointer.h"
2 #include "keyboard.h"
3 #include "frame.h"
4 #include "engine.h"
5 #include "openbox.h"
6 #include "hooks.h"
7 #include "configwrap.h"
8
9 #include <glib.h>
10 #include <Python.h>
11 #include <structmember.h> /* for PyMemberDef stuff */
12 #ifdef HAVE_STDLIB_H
13 #  include <stdlib.h>
14 #endif
15
16 typedef enum {
17     Action_Press,
18     Action_Release,
19     Action_Click,
20     Action_DoubleClick,
21     Action_Motion,
22     NUM_ACTIONS
23 } Action;
24
25 /* GData of GSList*s of PointerBinding*s. */
26 static GData *bound_contexts;
27 static gboolean grabbed;
28 PyObject *grab_func;
29
30 struct foreach_grab_temp {
31     Client *client;
32     gboolean grab;
33 };
34
35 typedef struct {
36     guint state;
37     guint button;
38     Action action;
39     char *name;
40     GSList *funcs[NUM_ACTIONS];
41 } PointerBinding;
42
43 /***************************************************************************
44  
45    Define the type 'ButtonData'
46
47  ***************************************************************************/
48
49 typedef struct PointerData {
50     PyObject_HEAD
51     Action action;
52     GQuark context;
53     char *button;
54     guint state;
55     guint buttonnum;
56     int posx, posy;
57     int pressposx, pressposy;
58     int pcareax, pcareay, pcareaw, pcareah;
59 } PointerData;
60
61 staticforward PyTypeObject PointerDataType;
62
63 /***************************************************************************
64  
65    Type methods/struct
66  
67  ***************************************************************************/
68
69 static PyObject *ptrdata_new(char *button, GQuark context, Action action,
70                              guint state, guint buttonnum, int posx, int posy,
71                              int pressposx, int pressposy, int pcareax,
72                              int pcareay, int pcareaw, int pcareah)
73 {
74     PointerData *self = PyObject_New(PointerData, &PointerDataType);
75     self->button = g_strdup(button);
76     self->context = context;
77     self->action = action;
78     self->state = state;
79     self->buttonnum = buttonnum;
80     self->posx = posx;
81     self->posy = posy;
82     self->pressposx = pressposx;
83     self->pressposy = pressposy;
84     self->pcareax = pcareax;
85     self->pcareay = pcareay;
86     self->pcareaw = pcareaw;
87     self->pcareah = pcareah;
88     return (PyObject*) self;
89 }
90
91 static void ptrdata_dealloc(PointerData *self)
92 {
93     g_free(self->button);
94     PyObject_Del((PyObject*)self);
95 }
96
97 static PyObject *ptrdata_getattr(PointerData *self, char *name)
98 {
99     if (!strcmp(name, "button"))
100         return PyString_FromString(self->button);
101     if (!strcmp(name, "action"))
102         return PyInt_FromLong(self->action);
103     if (!strcmp(name, "context"))
104         return PyString_FromString(g_quark_to_string(self->context));
105     if (!strcmp(name, "state"))
106         return PyInt_FromLong(self->state);
107     if (!strcmp(name, "buttonnum"))
108         return PyInt_FromLong(self->buttonnum);
109
110     if (self->action == Action_Motion) { /* the rest are only for motions */
111         if (!strcmp(name, "pos")) {
112             PyObject *pos = PyTuple_New(2);
113             PyTuple_SET_ITEM(pos, 0, PyInt_FromLong(self->posx));
114             PyTuple_SET_ITEM(pos, 1, PyInt_FromLong(self->posy));
115             return pos;
116         }
117         if (!strcmp(name, "presspos")) {
118             PyObject *presspos = PyTuple_New(2);
119             PyTuple_SET_ITEM(presspos, 0, PyInt_FromLong(self->pressposx));
120             PyTuple_SET_ITEM(presspos, 1, PyInt_FromLong(self->pressposy));
121             return presspos;
122         }
123         if (!strcmp(name, "pressclientarea")) {
124             if (self->pcareaw < 0) { /* < 0 indicates no client */
125                 Py_INCREF(Py_None);
126                 return Py_None;
127             } else {
128                 PyObject *ca = PyTuple_New(4);
129                 PyTuple_SET_ITEM(ca, 0, PyInt_FromLong(self->pcareax));
130                 PyTuple_SET_ITEM(ca, 1, PyInt_FromLong(self->pcareay));
131                 PyTuple_SET_ITEM(ca, 2, PyInt_FromLong(self->pcareaw));
132                 PyTuple_SET_ITEM(ca, 3, PyInt_FromLong(self->pcareah));
133                 return ca;
134             }
135         }
136     }
137
138     PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name);
139     return NULL;
140 }
141
142 static PyTypeObject PointerDataType = {
143     PyObject_HEAD_INIT(NULL)
144     0,
145     "PointerData",
146     sizeof(PointerData),
147     0,
148     (destructor) ptrdata_dealloc,   /*tp_dealloc*/
149     0,                              /*tp_print*/
150     (getattrfunc) ptrdata_getattr,  /*tp_getattr*/
151     0,                              /*tp_setattr*/
152     0,                              /*tp_compare*/
153     0,                              /*tp_repr*/
154     0,                              /*tp_as_number*/
155     0,                              /*tp_as_sequence*/
156     0,                              /*tp_as_mapping*/
157     0,                              /*tp_hash */
158 };
159
160 /***************************************************************************/
161
162 static gboolean translate(char *str, guint *state, guint *button)
163 {
164     char **parsed;
165     char *l;
166     int i;
167     gboolean ret = FALSE;
168
169     parsed = g_strsplit(str, "-", -1);
170     
171     /* first, find the button (last token) */
172     l = NULL;
173     for (i = 0; parsed[i] != NULL; ++i)
174         l = parsed[i];
175     if (l == NULL)
176         goto translation_fail;
177
178     /* figure out the mod mask */
179     *state = 0;
180     for (i = 0; parsed[i] != l; ++i) {
181         guint m = keyboard_translate_modifier(parsed[i]);
182         if (!m) goto translation_fail;
183         *state |= m;
184     }
185
186     /* figure out the button */
187     *button = atoi(l);
188     if (!*button) {
189         g_warning("Invalid button '%s' in pointer binding.", l);
190         goto translation_fail;
191     }
192
193     ret = TRUE;
194
195 translation_fail:
196     g_strfreev(parsed);
197     return ret;
198 }
199
200 static void grab_button(Client *client, guint state, guint button,
201                         GQuark context, gboolean grab)
202 {
203     Window win;
204     int mode = GrabModeAsync;
205     unsigned int mask;
206
207     if (context == g_quark_try_string("frame")) {
208         win = client->frame->window;
209         mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
210     } else if (context == g_quark_try_string("client")) {
211         win = client->frame->plate;
212         mode = GrabModeSync; /* this is handled in pointer_event */
213         mask = ButtonPressMask; /* can't catch more than this with Sync mode
214                                    the release event is manufactured in
215                                    pointer_fire */
216     } else return;
217
218     if (grab)
219         XGrabButton(ob_display, button, state, win, FALSE, mask, mode,
220                     GrabModeAsync, None, None);
221     else
222         XUngrabButton(ob_display, button, state, win);
223 }
224
225 static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
226 {
227     struct foreach_grab_temp *d = user_data;
228     GSList *it;
229     for (it = data; it != NULL; it = it->next) {
230         PointerBinding *b = it->data;
231         grab_button(d->client, b->state, b->button, key, d->grab);
232     }
233 }
234   
235 void pointer_grab_all(Client *client, gboolean grab)
236 {
237     struct foreach_grab_temp bt;
238     bt.client = client;
239     bt.grab = grab;
240     g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
241 }
242
243 static void grab_all_clients(gboolean grab)
244 {
245     GSList *it;
246
247     for (it = client_list; it != NULL; it = it->next)
248         pointer_grab_all(it->data, grab);
249 }
250
251 static gboolean grab_pointer(gboolean grab)
252 {
253     gboolean ret = TRUE;
254     if (grab)
255         ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask |
256                                                         ButtonReleaseMask |
257                                                         ButtonMotionMask |
258                                                         PointerMotionMask),
259                            GrabModeAsync, GrabModeAsync, None, None,
260                            CurrentTime) == GrabSuccess;
261     else
262         XUngrabPointer(ob_display, CurrentTime);
263     if (ret) grabbed = grab;
264     return ret;
265 }
266
267 static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
268 {
269     GSList *it;
270     user_data = user_data;
271     for (it = data; it != NULL; it = it->next) {
272         int i;
273
274         PointerBinding *b = it->data;
275         for (i = 0; i < NUM_ACTIONS; ++i)
276             while (b->funcs[i] != NULL) {
277                 Py_DECREF((PyObject*)b->funcs[i]->data);
278                 b->funcs[i] = g_slist_delete_link(b->funcs[i], b->funcs[i]);
279             }
280         g_free(b->name);
281         g_free(b);
282     }
283     g_slist_free(data);
284 }
285
286 static void clearall()
287 {
288     grab_all_clients(FALSE);
289     g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
290 }
291
292 static void fire_event(char *button, GQuark context, Action action,
293                        guint state, guint buttonnum, int posx, int posy,
294                        int pressposx, int pressposy, int pcareax,
295                        int pcareay, int pcareaw, int pcareah,
296                        PyObject *client, GSList *functions)
297 {
298     PyObject *ptrdata, *args, *ret;
299     GSList *it;
300
301     ptrdata = ptrdata_new(button, context, action,
302                           state, buttonnum, posx, posy, pressposx, pressposy,
303                           pcareax, pcareay, pcareaw, pcareah);
304     args = Py_BuildValue("OO", ptrdata, client);
305
306     if (grabbed) {
307         ret = PyObject_CallObject(grab_func, args);
308         if (ret == NULL) PyErr_Print();
309         Py_XDECREF(ret);
310     } else {
311         for (it = functions; it != NULL; it = it->next) {
312             ret = PyObject_CallObject(it->data, args);
313             if (ret == NULL) PyErr_Print();
314             Py_XDECREF(ret);
315         }
316     }
317
318     Py_DECREF(args);
319     Py_DECREF(ptrdata);
320 }
321
322 void pointer_event(XEvent *e, Client *c)
323 {
324     static guint button = 0, lastbutton = 0;
325     static Time time = 0;
326     static Rect carea;
327     static guint pressx, pressy;
328     GQuark contextq;
329     gboolean click = FALSE, dblclick = FALSE;
330     PyObject *client;
331     GString *str = g_string_sized_new(0);
332     guint state;
333     GSList *it = NULL;
334     PointerBinding *b = NULL;
335     guint drag_threshold;
336
337     drag_threshold = configwrap_get_int("input", "drag_threshold");
338
339     contextq = engine_get_context(c, e->xany.window);
340
341     /* pick a button, figure out clicks/double clicks */
342     switch (e->type) {
343     case ButtonPress:
344         if (!button) {
345             button = e->xbutton.button;
346             if (c != NULL) carea = c->frame->area;
347             else carea.width = -1; /* indicates no client */
348             pressx = e->xbutton.x_root;
349             pressy = e->xbutton.y_root;
350         }
351         state = e->xbutton.state;
352         break;
353     case ButtonRelease:
354         state = e->xbutton.state;
355         break;
356     case MotionNotify:
357         state = e->xmotion.state;
358         break;
359     default:
360         g_assert_not_reached();
361         return;
362     }
363
364     if (!grabbed) {
365         for (it = g_datalist_id_get_data(&bound_contexts, contextq);
366              it != NULL; it = it->next) {
367             b = it->data;
368             if (b->state == state && b->button == button)
369                 break;
370         }
371         /* if not grabbed and not bound, then nothing to do! */
372         if (it == NULL) return;
373     }
374
375     if (c) client = clientwrap_new(c);
376     else client = Py_None;
377
378     /* build the button string */
379     if (state & ControlMask) g_string_append(str, "C-");
380     if (state & ShiftMask)   g_string_append(str, "S-");
381     if (state & Mod1Mask)    g_string_append(str, "Mod1-");
382     if (state & Mod2Mask)    g_string_append(str, "Mod2-");
383     if (state & Mod3Mask)    g_string_append(str, "Mod3-");
384     if (state & Mod4Mask)    g_string_append(str, "Mod4-");
385     if (state & Mod5Mask)    g_string_append(str, "Mod5-");
386     g_string_append_printf(str, "%d", button);
387
388     /* figure out clicks/double clicks */
389     switch (e->type) {
390     case ButtonRelease:
391         if (button == e->xbutton.button) {
392             /* determine if this is a valid 'click'. Its not if the release is
393              not over the window, or if a drag occured. */
394             if (ABS(e->xbutton.x_root - pressx) < drag_threshold &&
395                 ABS(e->xbutton.y_root - pressy) < drag_threshold &&
396                 e->xbutton.x >= 0 && e->xbutton.y >= 0) {
397                 int junk;
398                 Window wjunk;
399                 guint ujunk, w, h;
400                 XGetGeometry(ob_display, e->xany.window, &wjunk, &junk, &junk,
401                              &w, &h, &ujunk, &ujunk);
402                 if (e->xbutton.x < (signed)w && e->xbutton.y < (signed)h)
403                     click =TRUE;
404             }
405
406             /* determine if this is a valid 'double-click' */
407             if (click) {
408                 if (lastbutton == button &&
409                     e->xbutton.time -
410                     configwrap_get_int("input", "double_click_rate") < time) {
411                     dblclick = TRUE;
412                     lastbutton = 0;
413                 } else
414                     lastbutton = button;
415             } else
416                 lastbutton = 0;
417             time = e->xbutton.time;
418             pressx = pressy = 0;
419             button = 0;
420             carea.x = carea.y = carea.width = carea.height = 0;
421         }
422         break;
423     }
424
425     /* fire off the events */
426     switch (e->type) {
427     case ButtonPress:
428         fire_event(str->str, contextq, Action_Press,
429                    state, button, 0, 0, 0, 0, 0, 0, 0, 0,
430                    client, b == NULL ? NULL : b->funcs[Action_Press]);
431         break;
432     case ButtonRelease:
433         fire_event(str->str, contextq, Action_Release,
434                    state, button, 0, 0, 0, 0, 0, 0, 0, 0,
435                    client, b == NULL ? NULL : b->funcs[Action_Release]);
436         break;
437     case MotionNotify:
438         /* watch out for the drag threshold */
439         if (ABS(e->xmotion.x_root - pressx) < drag_threshold &&
440             ABS(e->xmotion.y_root - pressy) < drag_threshold)
441             break;
442         fire_event(str->str, contextq, Action_Motion,
443                    state, button, e->xmotion.x_root,
444                    e->xmotion.y_root, pressx, pressy,
445                    carea.x, carea.y, carea.width, carea.height,
446                    client, b == NULL ? NULL : b->funcs[Action_Motion]);
447         break;
448     }
449
450     if (click)
451         fire_event(str->str, contextq, Action_Click,
452                    state, button, 0, 0, 0, 0, 0, 0, 0, 0,
453                    client, b == NULL ? NULL : b->funcs[Action_Click]);
454     if (dblclick)
455         fire_event(str->str, contextq, Action_DoubleClick,
456                    state, button, 0, 0, 0, 0, 0, 0, 0, 0,
457                    client, b == NULL ? NULL : b->funcs[Action_DoubleClick]);
458
459     g_string_free(str, TRUE);
460     if (client != Py_None) { Py_DECREF(client); }
461
462     if (contextq == g_quark_try_string("client")) {
463         /* Replay the event, so it goes to the client*/
464         XAllowEvents(ob_display, ReplayPointer, CurrentTime);
465         /* generate a release event since we don't get real ones */
466         if (e->type == ButtonPress) {
467             e->type = ButtonRelease;
468             pointer_event(e, c);
469         }
470     }
471 }
472
473 /***************************************************************************
474  
475    Define the type 'Pointer'
476
477  ***************************************************************************/
478
479 #define IS_POINTER(v)  ((v)->ob_type == &PointerType)
480 #define CHECK_POINTER(self, funcname) { \
481     if (!IS_POINTER(self)) { \
482         PyErr_SetString(PyExc_TypeError, \
483                         "descriptor '" funcname "' requires a 'Pointer' " \
484                         "object"); \
485         return NULL; \
486     } \
487 }
488
489 typedef struct Pointer {
490     PyObject_HEAD
491     Action press;
492     Action release;
493     Action click;
494     Action doubleclick;
495     Action motion;
496 } Pointer;
497
498 staticforward PyTypeObject PointerType;
499
500 static PyObject *ptr_bind(Pointer *self, PyObject *args)
501 {
502     char *buttonstr;
503     char *contextstr;
504     guint state, button;
505     PointerBinding *b;
506     GSList *it;
507     GQuark context;
508     PyObject *func;
509     Action action;
510     int i;
511
512     CHECK_POINTER(self, "grab");
513     if (!PyArg_ParseTuple(args, "ssiO:grab",
514                           &buttonstr, &contextstr, &action, &func))
515         return NULL;
516
517     if (!translate(buttonstr, &state, &button)) {
518         PyErr_SetString(PyExc_ValueError, "invalid button");
519         return NULL;
520     }
521
522     context = g_quark_try_string(contextstr);
523     if (!context) {
524         PyErr_SetString(PyExc_ValueError, "invalid context");
525         return NULL;
526     }
527
528     if (action < 0 || action >= NUM_ACTIONS) {
529         PyErr_SetString(PyExc_ValueError, "invalid action");
530         return NULL;
531     }
532
533     if (!PyCallable_Check(func)) {
534         PyErr_SetString(PyExc_ValueError, "expected a callable object");
535         return NULL;
536     }
537
538     for (it = g_datalist_id_get_data(&bound_contexts, context);
539          it != NULL; it = it->next){
540         b = it->data;
541         if (b->state == state && b->button == button) {
542             /* already bound */
543             b->funcs[action] = g_slist_append(b->funcs[action], func);
544             Py_INCREF(Py_None);
545             return Py_None;
546         }
547     }
548
549     grab_all_clients(FALSE);
550
551     /* add the binding */
552     b = g_new(PointerBinding, 1);
553     b->state = state;
554     b->button = button;
555     b->name = g_strdup(buttonstr);
556     for (i = 0; i < NUM_ACTIONS; ++i)
557         if (i != (signed)action) b->funcs[i] = NULL;
558     b->funcs[action] = g_slist_append(NULL, func);
559     g_datalist_id_set_data(&bound_contexts, context, 
560         g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
561     grab_all_clients(TRUE);
562
563     Py_INCREF(Py_None);
564     return Py_None;
565 }
566
567 static PyObject *ptr_clearBinds(Pointer *self, PyObject *args)
568 {
569     CHECK_POINTER(self, "clearBinds");
570     if (!PyArg_ParseTuple(args, ":clearBinds"))
571         return NULL;
572     clearall();
573     Py_INCREF(Py_None);
574     return Py_None;
575 }
576
577 static PyObject *ptr_grab(Pointer *self, PyObject *args)
578 {
579     PyObject *func;
580
581     CHECK_POINTER(self, "grab");
582     if (!PyArg_ParseTuple(args, "O:grab", &func))
583         return NULL;
584     if (!PyCallable_Check(func)) {
585         PyErr_SetString(PyExc_ValueError, "expected a callable object");
586         return NULL;
587     }
588     if (!grab_pointer(TRUE)) {
589         PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer");
590         return NULL;
591     }
592     grab_func = func;
593     Py_INCREF(grab_func);
594     Py_INCREF(Py_None);
595     return Py_None;
596 }
597
598 static PyObject *ptr_ungrab(Pointer *self, PyObject *args)
599 {
600     CHECK_POINTER(self, "ungrab");
601     if (!PyArg_ParseTuple(args, ":ungrab"))
602         return NULL;
603     grab_pointer(FALSE);
604     Py_XDECREF(grab_func);
605     grab_func = NULL;
606     Py_INCREF(Py_None);
607     return Py_None;
608 }
609
610 #define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d}
611
612 static PyMethodDef PointerMethods[] = {
613     METH(bind,
614          "bind(button, context, func)\n\n"
615          "Binds a pointer button for a context to a function. See the "
616          "Terminology section for a decription and list of common contexts. "
617          "The button is a string which defines a modifier and button "
618          "combination with the format [Modifier-]...[Button]. Modifiers can "
619          "be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5', 'control', and 'shift'. "
620          "The keys on your keyboard that are bound to each of these modifiers "
621          "can be found by running 'xmodmap'. The button is the number of the "
622          "button. Button numbers can be found by running 'xev', pressing the "
623          "button with the pointer over its window, and watching its output. "
624          "Here are some examples of valid buttons: 'control-1', '2', "
625          "'mod1-shift-5'. The func must have a definition similar to "
626          "'def func(keydata, client)'. A button and context may be bound to "
627          "more than one function."),
628     METH(clearBinds,
629          "clearBinds()\n\n"
630          "Removes all bindings that were previously made by bind()."),
631     METH(grab,
632          "grab(func)\n\n"
633          "Grabs the pointer device, causing all possible pointer events to be "
634          "sent to the given function. CAUTION: Be sure when you grab() that "
635          "you also have an ungrab() that will execute, or you will not be "
636          "able to use the pointer device until you restart Openbox. The func "
637          "must have a definition similar to 'def func(keydata)'. The pointer "
638          "cannot be grabbed if it is already grabbed."),
639     METH(ungrab,
640          "ungrab()\n\n"
641          "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not "
642          "grabbed."),
643     { NULL, NULL, 0, NULL }
644 };
645
646 static PyMemberDef PointerMembers[] = {
647     {"Action_Press", T_INT, offsetof(Pointer, press), READONLY,
648      "a pointer button press"},
649     {"Action_Release", T_INT, offsetof(Pointer, release), READONLY,
650      "a pointer button release"},
651     {"Action_Click", T_INT, offsetof(Pointer, click), READONLY,
652      "a pointer button click (press-release)"},
653     {"Action_DoubleClick", T_INT, offsetof(Pointer, doubleclick), READONLY,
654      "a pointer button double-click"},
655     {"Action_Motion", T_INT, offsetof(Pointer, motion), READONLY,
656      "a pointer drag"},
657     {NULL}
658 };
659
660 /***************************************************************************
661  
662    Type methods/struct
663  
664  ***************************************************************************/
665
666 static void ptr_dealloc(PyObject *self)
667 {
668     PyObject_Del(self);
669 }
670
671 static PyTypeObject PointerType = {
672     PyObject_HEAD_INIT(NULL)
673     0,
674     "Pointer",
675     sizeof(Pointer),
676     0,
677     (destructor) ptr_dealloc,       /*tp_dealloc*/
678     0,                              /*tp_print*/
679     0,                              /*tp_getattr*/
680     0,                              /*tp_setattr*/
681     0,                              /*tp_compare*/
682     0,                              /*tp_repr*/
683     0,                              /*tp_as_number*/
684     0,                              /*tp_as_sequence*/
685     0,                              /*tp_as_mapping*/
686     0,                              /*tp_hash */
687 };
688
689 /**************************************************************************/
690
691 void pointer_startup()
692 {
693     PyObject *input, *inputdict;
694     Pointer *ptr;
695
696     grabbed = FALSE;
697     configwrap_add_int("input", "double_click_rate", "Double-Click Rate",
698                        "An integer containing the number of milliseconds in "
699                        "which 2 clicks must be received to cause a "
700                        "double-click event.", 300);
701     configwrap_add_int("input", "drag_threshold", "Drag Threshold",
702                        "An integer containing the number of pixels a drag "
703                        "must go before motion events start getting generated. "
704                        "Once a drag has begun, the button release will not "
705                        "count as a click event.", 3);
706     g_datalist_init(&bound_contexts);
707
708     PointerType.ob_type = &PyType_Type;
709     PointerType.tp_methods = PointerMethods;
710     PointerType.tp_members = PointerMembers;
711     PyType_Ready(&PointerType);
712     PyType_Ready(&PointerDataType);
713
714     /* get the input module/dict */
715     input = PyImport_ImportModule("input"); /* new */
716     g_assert(input != NULL);
717     inputdict = PyModule_GetDict(input); /* borrowed */
718     g_assert(inputdict != NULL);
719
720     /* add a Pointer instance to the input module */
721     ptr = PyObject_New(Pointer, &PointerType);
722     ptr->press = Action_Press;
723     ptr->release = Action_Release;
724     ptr->click = Action_Click;
725     ptr->doubleclick = Action_DoubleClick;
726     ptr->motion = Action_Motion;
727     PyDict_SetItemString(inputdict, "Pointer", (PyObject*) ptr);
728     Py_DECREF(ptr);
729
730     Py_DECREF(input);
731 }
732
733 void pointer_shutdown()
734 {
735     if (grabbed)
736         grab_pointer(FALSE);
737     clearall();
738     g_datalist_clear(&bound_contexts);
739 }
740