summaryrefslogtreecommitdiff
path: root/openbox/pointer.c
diff options
context:
space:
mode:
Diffstat (limited to 'openbox/pointer.c')
-rw-r--r--openbox/pointer.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/openbox/pointer.c b/openbox/pointer.c
new file mode 100644
index 00000000..00720f3b
--- /dev/null
+++ b/openbox/pointer.c
@@ -0,0 +1,729 @@
+#include "pointer.h"
+#include "keyboard.h"
+#include "frame.h"
+#include "engine.h"
+#include "openbox.h"
+#include "hooks.h"
+
+#include <glib.h>
+#include <Python.h>
+#include <structmember.h> /* for PyMemberDef stuff */
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+typedef enum {
+ Action_Press,
+ Action_Release,
+ Action_Click,
+ Action_DoubleClick,
+ Action_Motion,
+ NUM_ACTIONS
+} Action;
+
+/* GData of GSList*s of PointerBinding*s. */
+static GData *bound_contexts;
+static gboolean grabbed;
+static int double_click_rate, drag_threshold;
+PyObject *grab_func;
+
+struct foreach_grab_temp {
+ Client *client;
+ gboolean grab;
+};
+
+typedef struct {
+ guint state;
+ guint button;
+ Action action;
+ char *name;
+ GSList *funcs[NUM_ACTIONS];
+} PointerBinding;
+
+/***************************************************************************
+
+ Define the type 'ButtonData'
+
+ ***************************************************************************/
+
+typedef struct PointerData {
+ PyObject_HEAD
+ Action action;
+ GQuark context;
+ char *button;
+ guint state;
+ guint buttonnum;
+ int posx, posy;
+ int pressposx, pressposy;
+ int pcareax, pcareay, pcareaw, pcareah;
+} PointerData;
+
+staticforward PyTypeObject PointerDataType;
+
+/***************************************************************************
+
+ Type methods/struct
+
+ ***************************************************************************/
+
+static PyObject *ptrdata_new(char *button, GQuark context, Action action,
+ guint state, guint buttonnum, int posx, int posy,
+ int pressposx, int pressposy, int pcareax,
+ int pcareay, int pcareaw, int pcareah)
+{
+ PointerData *self = PyObject_New(PointerData, &PointerDataType);
+ self->button = g_strdup(button);
+ self->context = context;
+ self->action = action;
+ self->state = state;
+ self->buttonnum = buttonnum;
+ self->posx = posx;
+ self->posy = posy;
+ self->pressposx = pressposx;
+ self->pressposy = pressposy;
+ self->pcareax = pcareax;
+ self->pcareay = pcareay;
+ self->pcareaw = pcareaw;
+ self->pcareah = pcareah;
+ return (PyObject*) self;
+}
+
+static void ptrdata_dealloc(PointerData *self)
+{
+ g_free(self->button);
+ PyObject_Del((PyObject*)self);
+}
+
+static PyObject *ptrdata_getattr(PointerData *self, char *name)
+{
+ if (!strcmp(name, "button"))
+ return PyString_FromString(self->button);
+ if (!strcmp(name, "action"))
+ return PyInt_FromLong(self->action);
+ if (!strcmp(name, "context"))
+ return PyString_FromString(g_quark_to_string(self->context));
+ if (!strcmp(name, "state"))
+ return PyInt_FromLong(self->state);
+ if (!strcmp(name, "buttonnum"))
+ return PyInt_FromLong(self->buttonnum);
+
+ if (self->action == Action_Motion) { /* the rest are only for motions */
+ if (!strcmp(name, "pos")) {
+ PyObject *pos = PyTuple_New(2);
+ PyTuple_SET_ITEM(pos, 0, PyInt_FromLong(self->posx));
+ PyTuple_SET_ITEM(pos, 1, PyInt_FromLong(self->posy));
+ return pos;
+ }
+ if (!strcmp(name, "presspos")) {
+ PyObject *presspos = PyTuple_New(2);
+ PyTuple_SET_ITEM(presspos, 0, PyInt_FromLong(self->pressposx));
+ PyTuple_SET_ITEM(presspos, 1, PyInt_FromLong(self->pressposy));
+ return presspos;
+ }
+ if (!strcmp(name, "pressclientarea")) {
+ if (self->pcareaw < 0) { /* < 0 indicates no client */
+ Py_INCREF(Py_None);
+ return Py_None;
+ } else {
+ PyObject *ca = PyTuple_New(4);
+ PyTuple_SET_ITEM(ca, 0, PyInt_FromLong(self->pcareax));
+ PyTuple_SET_ITEM(ca, 1, PyInt_FromLong(self->pcareay));
+ PyTuple_SET_ITEM(ca, 2, PyInt_FromLong(self->pcareaw));
+ PyTuple_SET_ITEM(ca, 3, PyInt_FromLong(self->pcareah));
+ return ca;
+ }
+ }
+ }
+
+ PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name);
+ return NULL;
+}
+
+static PyTypeObject PointerDataType = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "PointerData",
+ sizeof(PointerData),
+ 0,
+ (destructor) ptrdata_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ (getattrfunc) ptrdata_getattr, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+};
+
+/***************************************************************************/
+
+static gboolean translate(char *str, guint *state, guint *button)
+{
+ char **parsed;
+ char *l;
+ int i;
+ gboolean ret = FALSE;
+
+ parsed = g_strsplit(str, "-", -1);
+
+ /* first, find the button (last token) */
+ l = NULL;
+ for (i = 0; parsed[i] != NULL; ++i)
+ l = parsed[i];
+ if (l == NULL)
+ goto translation_fail;
+
+ /* figure out the mod mask */
+ *state = 0;
+ for (i = 0; parsed[i] != l; ++i) {
+ guint m = keyboard_translate_modifier(parsed[i]);
+ if (!m) goto translation_fail;
+ *state |= m;
+ }
+
+ /* figure out the button */
+ *button = atoi(l);
+ if (!*button) {
+ g_warning("Invalid button '%s' in pointer binding.", l);
+ goto translation_fail;
+ }
+
+ ret = TRUE;
+
+translation_fail:
+ g_strfreev(parsed);
+ return ret;
+}
+
+static void grab_button(Client *client, guint state, guint button,
+ GQuark context, gboolean grab)
+{
+ Window win;
+ int mode = GrabModeAsync;
+ unsigned int mask;
+
+ if (context == g_quark_try_string("frame")) {
+ win = client->frame->window;
+ mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
+ } else if (context == g_quark_try_string("client")) {
+ win = client->frame->plate;
+ mode = GrabModeSync; /* this is handled in pointer_event */
+ mask = ButtonPressMask; /* can't catch more than this with Sync mode
+ the release event is manufactured in
+ pointer_fire */
+ } else return;
+
+ if (grab)
+ XGrabButton(ob_display, button, state, win, FALSE, mask, mode,
+ GrabModeAsync, None, None);
+ else
+ XUngrabButton(ob_display, button, state, win);
+}
+
+static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
+{
+ struct foreach_grab_temp *d = user_data;
+ GSList *it;
+ for (it = data; it != NULL; it = it->next) {
+ PointerBinding *b = it->data;
+ grab_button(d->client, b->state, b->button, key, d->grab);
+ }
+}
+
+void pointer_grab_all(Client *client, gboolean grab)
+{
+ struct foreach_grab_temp bt;
+ bt.client = client;
+ bt.grab = grab;
+ g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
+}
+
+static void grab_all_clients(gboolean grab)
+{
+ GSList *it;
+
+ for (it = client_list; it != NULL; it = it->next)
+ pointer_grab_all(it->data, grab);
+}
+
+static gboolean grab_pointer(gboolean grab)
+{
+ gboolean ret = TRUE;
+ if (grab)
+ ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask |
+ ButtonReleaseMask |
+ ButtonMotionMask |
+ PointerMotionMask),
+ GrabModeAsync, GrabModeAsync, None, None,
+ CurrentTime) == GrabSuccess;
+ else
+ XUngrabPointer(ob_display, CurrentTime);
+ if (ret) grabbed = grab;
+ return ret;
+}
+
+static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
+{
+ GSList *it;
+ user_data = user_data;
+ for (it = data; it != NULL; it = it->next) {
+ int i;
+
+ PointerBinding *b = it->data;
+ for (i = 0; i < NUM_ACTIONS; ++i)
+ while (b->funcs[i] != NULL) {
+ Py_DECREF((PyObject*)b->funcs[i]->data);
+ b->funcs[i] = g_slist_delete_link(b->funcs[i], b->funcs[i]);
+ }
+ g_free(b->name);
+ g_free(b);
+ }
+ g_slist_free(data);
+}
+
+static void clearall()
+{
+ grab_all_clients(FALSE);
+ g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
+}
+
+static void fire_event(char *button, GQuark context, Action action,
+ guint state, guint buttonnum, int posx, int posy,
+ int pressposx, int pressposy, int pcareax,
+ int pcareay, int pcareaw, int pcareah,
+ PyObject *client, GSList *functions)
+{
+ PyObject *ptrdata, *args, *ret;
+ GSList *it;
+
+ ptrdata = ptrdata_new(button, context, action,
+ state, buttonnum, posx, posy, pressposx, pressposy,
+ pcareax, pcareay, pcareaw, pcareah);
+ args = Py_BuildValue("OO", ptrdata, client);
+
+ if (grabbed) {
+ ret = PyObject_CallObject(grab_func, args);
+ if (ret == NULL) PyErr_Print();
+ Py_XDECREF(ret);
+ } else {
+ for (it = functions; it != NULL; it = it->next) {
+ ret = PyObject_CallObject(it->data, args);
+ if (ret == NULL) PyErr_Print();
+ Py_XDECREF(ret);
+ }
+ }
+
+ Py_DECREF(args);
+ Py_DECREF(ptrdata);
+}
+
+void pointer_event(XEvent *e, Client *c)
+{
+ static guint button = 0, lastbutton = 0;
+ static Time time = 0;
+ static Rect carea;
+ static guint pressx, pressy;
+ GQuark contextq;
+ gboolean click = FALSE, dblclick = FALSE;
+ PyObject *client;
+ GString *str = g_string_sized_new(0);
+ guint state;
+ GSList *it = NULL;
+ PointerBinding *b = NULL;
+
+ contextq = engine_get_context(c, e->xany.window);
+
+ /* pick a button, figure out clicks/double clicks */
+ switch (e->type) {
+ case ButtonPress:
+ if (!button) {
+ button = e->xbutton.button;
+ if (c != NULL) carea = c->frame->area;
+ else carea.width = -1; /* indicates no client */
+ pressx = e->xbutton.x_root;
+ pressy = e->xbutton.y_root;
+ }
+ state = e->xbutton.state;
+ break;
+ case ButtonRelease:
+ state = e->xbutton.state;
+ break;
+ case MotionNotify:
+ state = e->xmotion.state;
+ break;
+ default:
+ g_assert_not_reached();
+ return;
+ }
+
+ if (!grabbed) {
+ for (it = g_datalist_id_get_data(&bound_contexts, contextq);
+ it != NULL; it = it->next) {
+ b = it->data;
+ if (b->state == state && b->button == button)
+ break;
+ }
+ /* if not grabbed and not bound, then nothing to do! */
+ if (it == NULL) return;
+ }
+
+ if (c) client = clientwrap_new(c);
+ else client = Py_None;
+
+ /* build the button string */
+ if (state & ControlMask) g_string_append(str, "C-");
+ if (state & ShiftMask) g_string_append(str, "S-");
+ if (state & Mod1Mask) g_string_append(str, "Mod1-");
+ if (state & Mod2Mask) g_string_append(str, "Mod2-");
+ if (state & Mod3Mask) g_string_append(str, "Mod3-");
+ if (state & Mod4Mask) g_string_append(str, "Mod4-");
+ if (state & Mod5Mask) g_string_append(str, "Mod5-");
+ g_string_append_printf(str, "%d", button);
+
+ /* figure out clicks/double clicks */
+ switch (e->type) {
+ case ButtonRelease:
+ if (button == e->xbutton.button) {
+ /* determine if this is a valid 'click'. Its not if the release is
+ not over the window, or if a drag occured. */
+ if (ABS(e->xbutton.x_root - pressx) < (unsigned)drag_threshold &&
+ ABS(e->xbutton.y_root - pressy) < (unsigned)drag_threshold &&
+ e->xbutton.x >= 0 && e->xbutton.y >= 0) {
+ int junk;
+ Window wjunk;
+ guint ujunk, w, h;
+ XGetGeometry(ob_display, e->xany.window, &wjunk, &junk, &junk,
+ &w, &h, &ujunk, &ujunk);
+ if (e->xbutton.x < (signed)w && e->xbutton.y < (signed)h)
+ click =TRUE;
+ }
+
+ /* determine if this is a valid 'double-click' */
+ if (click) {
+ if (lastbutton == button &&
+ e->xbutton.time - double_click_rate < time) {
+ dblclick = TRUE;
+ lastbutton = 0;
+ } else
+ lastbutton = button;
+ } else
+ lastbutton = 0;
+ time = e->xbutton.time;
+ pressx = pressy = 0;
+ button = 0;
+ carea.x = carea.y = carea.width = carea.height = 0;
+ }
+ break;
+ }
+
+ /* fire off the events */
+ switch (e->type) {
+ case ButtonPress:
+ fire_event(str->str, contextq, Action_Press,
+ state, button, 0, 0, 0, 0, 0, 0, 0, 0,
+ client, b == NULL ? NULL : b->funcs[Action_Press]);
+ break;
+ case ButtonRelease:
+ fire_event(str->str, contextq, Action_Release,
+ state, button, 0, 0, 0, 0, 0, 0, 0, 0,
+ client, b == NULL ? NULL : b->funcs[Action_Release]);
+ break;
+ case MotionNotify:
+ /* watch out for the drag threshold */
+ if (ABS(e->xmotion.x_root - pressx) < (unsigned)drag_threshold &&
+ ABS(e->xmotion.y_root - pressy) < (unsigned)drag_threshold)
+ break;
+ fire_event(str->str, contextq, Action_Motion,
+ state, button, e->xmotion.x_root,
+ e->xmotion.y_root, pressx, pressy,
+ carea.x, carea.y, carea.width, carea.height,
+ client, b == NULL ? NULL : b->funcs[Action_Motion]);
+ break;
+ }
+
+ if (click)
+ fire_event(str->str, contextq, Action_Click,
+ state, button, 0, 0, 0, 0, 0, 0, 0, 0,
+ client, b == NULL ? NULL : b->funcs[Action_Click]);
+ if (dblclick)
+ fire_event(str->str, contextq, Action_DoubleClick,
+ state, button, 0, 0, 0, 0, 0, 0, 0, 0,
+ client, b == NULL ? NULL : b->funcs[Action_DoubleClick]);
+
+ g_string_free(str, TRUE);
+ if (client != Py_None) { Py_DECREF(client); }
+
+ if (contextq == g_quark_try_string("client")) {
+ /* Replay the event, so it goes to the client*/
+ XAllowEvents(ob_display, ReplayPointer, CurrentTime);
+ /* generate a release event since we don't get real ones */
+ if (e->type == ButtonPress) {
+ e->type = ButtonRelease;
+ pointer_event(e, c);
+ }
+ }
+}
+
+/***************************************************************************
+
+ Define the type 'Pointer'
+
+ ***************************************************************************/
+
+#define IS_POINTER(v) ((v)->ob_type == &PointerType)
+#define CHECK_POINTER(self, funcname) { \
+ if (!IS_POINTER(self)) { \
+ PyErr_SetString(PyExc_TypeError, \
+ "descriptor '" funcname "' requires a 'Pointer' " \
+ "object"); \
+ return NULL; \
+ } \
+}
+
+typedef struct Pointer {
+ PyObject_HEAD
+ Action press;
+ Action release;
+ Action click;
+ Action doubleclick;
+ Action motion;
+} Pointer;
+
+staticforward PyTypeObject PointerType;
+
+static PyObject *ptr_bind(Pointer *self, PyObject *args)
+{
+ char *buttonstr;
+ char *contextstr;
+ guint state, button;
+ PointerBinding *b;
+ GSList *it;
+ GQuark context;
+ PyObject *func;
+ Action action;
+ int i;
+
+ CHECK_POINTER(self, "grab");
+ if (!PyArg_ParseTuple(args, "ssiO:grab",
+ &buttonstr, &contextstr, &action, &func))
+ return NULL;
+
+ if (!translate(buttonstr, &state, &button)) {
+ PyErr_SetString(PyExc_ValueError, "invalid button");
+ return NULL;
+ }
+
+ context = g_quark_try_string(contextstr);
+ if (!context) {
+ PyErr_SetString(PyExc_ValueError, "invalid context");
+ return NULL;
+ }
+
+ if (action < 0 || action >= NUM_ACTIONS) {
+ PyErr_SetString(PyExc_ValueError, "invalid action");
+ return NULL;
+ }
+
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_ValueError, "expected a callable object");
+ return NULL;
+ }
+
+ for (it = g_datalist_id_get_data(&bound_contexts, context);
+ it != NULL; it = it->next){
+ b = it->data;
+ if (b->state == state && b->button == button) {
+ /* already bound */
+ b->funcs[action] = g_slist_append(b->funcs[action], func);
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ }
+
+ grab_all_clients(FALSE);
+
+ /* add the binding */
+ b = g_new(PointerBinding, 1);
+ b->state = state;
+ b->button = button;
+ b->name = g_strdup(buttonstr);
+ for (i = 0; i < NUM_ACTIONS; ++i)
+ if (i != (signed)action) b->funcs[i] = NULL;
+ b->funcs[action] = g_slist_append(NULL, func);
+ g_datalist_id_set_data(&bound_contexts, context,
+ g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
+ grab_all_clients(TRUE);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *ptr_clearBinds(Pointer *self, PyObject *args)
+{
+ CHECK_POINTER(self, "clearBinds");
+ if (!PyArg_ParseTuple(args, ":clearBinds"))
+ return NULL;
+ clearall();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *ptr_grab(Pointer *self, PyObject *args)
+{
+ PyObject *func;
+
+ CHECK_POINTER(self, "grab");
+ if (!PyArg_ParseTuple(args, "O:grab", &func))
+ return NULL;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_ValueError, "expected a callable object");
+ return NULL;
+ }
+ if (!grab_pointer(TRUE)) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer");
+ return NULL;
+ }
+ grab_func = func;
+ Py_INCREF(grab_func);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *ptr_ungrab(Pointer *self, PyObject *args)
+{
+ CHECK_POINTER(self, "ungrab");
+ if (!PyArg_ParseTuple(args, ":ungrab"))
+ return NULL;
+ grab_pointer(FALSE);
+ Py_XDECREF(grab_func);
+ grab_func = NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d}
+
+static PyMethodDef PointerMethods[] = {
+ METH(bind,
+ "bind(button, context, func)\n\n"
+ "Binds a pointer button for a context to a function. See the "
+ "Terminology section for a decription and list of common contexts. "
+ "The button is a string which defines a modifier and button "
+ "combination with the format [Modifier-]...[Button]. Modifiers can "
+ "be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5', 'control', and 'shift'. "
+ "The keys on your keyboard that are bound to each of these modifiers "
+ "can be found by running 'xmodmap'. The button is the number of the "
+ "button. Button numbers can be found by running 'xev', pressing the "
+ "button with the pointer over its window, and watching its output. "
+ "Here are some examples of valid buttons: 'control-1', '2', "
+ "'mod1-shift-5'. The func must have a definition similar to "
+ "'def func(keydata, client)'. A button and context may be bound to "
+ "more than one function."),
+ METH(clearBinds,
+ "clearBinds()\n\n"
+ "Removes all bindings that were previously made by bind()."),
+ METH(grab,
+ "grab(func)\n\n"
+ "Grabs the pointer device, causing all possible pointer events to be "
+ "sent to the given function. CAUTION: Be sure when you grab() that "
+ "you also have an ungrab() that will execute, or you will not be "
+ "able to use the pointer device until you restart Openbox. The func "
+ "must have a definition similar to 'def func(keydata)'. The pointer "
+ "cannot be grabbed if it is already grabbed."),
+ METH(ungrab,
+ "ungrab()\n\n"
+ "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not "
+ "grabbed."),
+ { NULL, NULL, 0, NULL }
+};
+
+static PyMemberDef PointerMembers[] = {
+ {"Action_Press", T_INT, offsetof(Pointer, press), READONLY,
+ "a pointer button press"},
+ {"Action_Release", T_INT, offsetof(Pointer, release), READONLY,
+ "a pointer button release"},
+ {"Action_Click", T_INT, offsetof(Pointer, click), READONLY,
+ "a pointer button click (press-release)"},
+ {"Action_DoubleClick", T_INT, offsetof(Pointer, doubleclick), READONLY,
+ "a pointer button double-click"},
+ {"Action_Motion", T_INT, offsetof(Pointer, motion), READONLY,
+ "a pointer drag"},
+ {NULL}
+};
+
+/***************************************************************************
+
+ Type methods/struct
+
+ ***************************************************************************/
+
+static void ptr_dealloc(PyObject *self)
+{
+ PyObject_Del(self);
+}
+
+static PyTypeObject PointerType = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "Pointer",
+ sizeof(Pointer),
+ 0,
+ (destructor) ptr_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+};
+
+/**************************************************************************/
+
+void pointer_startup()
+{
+ PyObject *input, *inputdict;
+ Pointer *ptr;
+
+ grabbed = FALSE;
+ double_click_rate = 300;
+ drag_threshold = 3;
+ g_datalist_init(&bound_contexts);
+
+ PointerType.ob_type = &PyType_Type;
+ PointerType.tp_methods = PointerMethods;
+ PointerType.tp_members = PointerMembers;
+ PyType_Ready(&PointerType);
+ PyType_Ready(&PointerDataType);
+
+ /* get the input module/dict */
+ input = PyImport_ImportModule("input"); /* new */
+ g_assert(input != NULL);
+ inputdict = PyModule_GetDict(input); /* borrowed */
+ g_assert(inputdict != NULL);
+
+ /* add a Pointer instance to the input module */
+ ptr = PyObject_New(Pointer, &PointerType);
+ ptr->press = Action_Press;
+ ptr->release = Action_Release;
+ ptr->click = Action_Click;
+ ptr->doubleclick = Action_DoubleClick;
+ ptr->motion = Action_Motion;
+ PyDict_SetItemString(inputdict, "Pointer", (PyObject*) ptr);
+ Py_DECREF(ptr);
+
+ Py_DECREF(input);
+}
+
+void pointer_shutdown()
+{
+ if (grabbed)
+ grab_pointer(FALSE);
+ clearall();
+ g_datalist_clear(&bound_contexts);
+}
+