diff options
Diffstat (limited to 'openbox/pointer.c')
| -rw-r--r-- | openbox/pointer.c | 729 |
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); +} + |
