7 #include "configwrap.h"
11 #include <structmember.h> /* for PyMemberDef stuff */
25 /* GData of GSList*s of PointerBinding*s. */
26 static GData *bound_contexts;
27 static gboolean grabbed;
30 struct foreach_grab_temp {
40 GSList *funcs[NUM_ACTIONS];
43 /***************************************************************************
45 Define the type 'ButtonData'
47 ***************************************************************************/
49 typedef struct PointerData {
57 int pressposx, pressposy;
58 int pcareax, pcareay, pcareaw, pcareah;
61 staticforward PyTypeObject PointerDataType;
63 /***************************************************************************
67 ***************************************************************************/
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)
74 PointerData *self = PyObject_New(PointerData, &PointerDataType);
75 self->button = g_strdup(button);
76 self->context = context;
77 self->action = action;
79 self->buttonnum = buttonnum;
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;
91 static void ptrdata_dealloc(PointerData *self)
94 PyObject_Del((PyObject*)self);
97 static PyObject *ptrdata_getattr(PointerData *self, char *name)
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);
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));
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));
123 if (!strcmp(name, "pressclientarea")) {
124 if (self->pcareaw < 0) { /* < 0 indicates no client */
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));
138 PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name);
142 static PyTypeObject PointerDataType = {
143 PyObject_HEAD_INIT(NULL)
148 (destructor) ptrdata_dealloc, /*tp_dealloc*/
150 (getattrfunc) ptrdata_getattr, /*tp_getattr*/
155 0, /*tp_as_sequence*/
160 /***************************************************************************/
162 static gboolean translate(char *str, guint *state, guint *button)
167 gboolean ret = FALSE;
169 parsed = g_strsplit(str, "-", -1);
171 /* first, find the button (last token) */
173 for (i = 0; parsed[i] != NULL; ++i)
176 goto translation_fail;
178 /* figure out the mod mask */
180 for (i = 0; parsed[i] != l; ++i) {
181 guint m = keyboard_translate_modifier(parsed[i]);
182 if (!m) goto translation_fail;
186 /* figure out the button */
189 g_warning("Invalid button '%s' in pointer binding.", l);
190 goto translation_fail;
200 static void grab_button(Client *client, guint state, guint button,
201 GQuark context, gboolean grab)
204 int mode = GrabModeAsync;
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
219 XGrabButton(ob_display, button, state, win, FALSE, mask, mode,
220 GrabModeAsync, None, None);
222 XUngrabButton(ob_display, button, state, win);
225 static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
227 struct foreach_grab_temp *d = user_data;
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);
235 void pointer_grab_all(Client *client, gboolean grab)
237 struct foreach_grab_temp bt;
240 g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
243 static void grab_all_clients(gboolean grab)
247 for (it = client_list; it != NULL; it = it->next)
248 pointer_grab_all(it->data, grab);
251 static gboolean grab_pointer(gboolean grab)
255 ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask |
259 GrabModeAsync, GrabModeAsync, None, None,
260 CurrentTime) == GrabSuccess;
262 XUngrabPointer(ob_display, CurrentTime);
263 if (ret) grabbed = grab;
267 static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
270 user_data = user_data;
271 for (it = data; it != NULL; it = it->next) {
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]);
286 static void clearall()
288 grab_all_clients(FALSE);
289 g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
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)
298 PyObject *ptrdata, *args, *ret;
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);
307 ret = PyObject_CallObject(grab_func, args);
308 if (ret == NULL) PyErr_Print();
311 for (it = functions; it != NULL; it = it->next) {
312 ret = PyObject_CallObject(it->data, args);
313 if (ret == NULL) PyErr_Print();
322 void pointer_event(XEvent *e, Client *c)
324 static guint button = 0, lastbutton = 0;
325 static Time time = 0;
327 static guint pressx, pressy;
329 gboolean click = FALSE, dblclick = FALSE;
331 GString *str = g_string_sized_new(0);
334 PointerBinding *b = NULL;
335 guint drag_threshold;
337 drag_threshold = configwrap_get_int("input", "drag_threshold");
339 contextq = engine_get_context(c, e->xany.window);
341 /* pick a button, figure out clicks/double clicks */
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;
351 state = e->xbutton.state;
354 state = e->xbutton.state;
357 state = e->xmotion.state;
360 g_assert_not_reached();
365 for (it = g_datalist_id_get_data(&bound_contexts, contextq);
366 it != NULL; it = it->next) {
368 if (b->state == state && b->button == button)
371 /* if not grabbed and not bound, then nothing to do! */
372 if (it == NULL) return;
375 if (c) client = clientwrap_new(c);
376 else client = Py_None;
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);
388 /* figure out clicks/double clicks */
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) {
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)
406 /* determine if this is a valid 'double-click' */
408 if (lastbutton == button &&
410 configwrap_get_int("input", "double_click_rate") < time) {
417 time = e->xbutton.time;
420 carea.x = carea.y = carea.width = carea.height = 0;
425 /* fire off the events */
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]);
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]);
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)
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]);
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]);
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]);
459 g_string_free(str, TRUE);
460 if (client != Py_None) { Py_DECREF(client); }
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;
473 /***************************************************************************
475 Define the type 'Pointer'
477 ***************************************************************************/
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' " \
489 typedef struct Pointer {
498 staticforward PyTypeObject PointerType;
500 static PyObject *ptr_bind(Pointer *self, PyObject *args)
512 CHECK_POINTER(self, "grab");
513 if (!PyArg_ParseTuple(args, "ssiO:grab",
514 &buttonstr, &contextstr, &action, &func))
517 if (!translate(buttonstr, &state, &button)) {
518 PyErr_SetString(PyExc_ValueError, "invalid button");
522 context = g_quark_try_string(contextstr);
524 PyErr_SetString(PyExc_ValueError, "invalid context");
528 if (action < 0 || action >= NUM_ACTIONS) {
529 PyErr_SetString(PyExc_ValueError, "invalid action");
533 if (!PyCallable_Check(func)) {
534 PyErr_SetString(PyExc_ValueError, "expected a callable object");
538 for (it = g_datalist_id_get_data(&bound_contexts, context);
539 it != NULL; it = it->next){
541 if (b->state == state && b->button == button) {
543 b->funcs[action] = g_slist_append(b->funcs[action], func);
549 grab_all_clients(FALSE);
551 /* add the binding */
552 b = g_new(PointerBinding, 1);
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);
567 static PyObject *ptr_clearBinds(Pointer *self, PyObject *args)
569 CHECK_POINTER(self, "clearBinds");
570 if (!PyArg_ParseTuple(args, ":clearBinds"))
577 static PyObject *ptr_grab(Pointer *self, PyObject *args)
581 CHECK_POINTER(self, "grab");
582 if (!PyArg_ParseTuple(args, "O:grab", &func))
584 if (!PyCallable_Check(func)) {
585 PyErr_SetString(PyExc_ValueError, "expected a callable object");
588 if (!grab_pointer(TRUE)) {
589 PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer");
593 Py_INCREF(grab_func);
598 static PyObject *ptr_ungrab(Pointer *self, PyObject *args)
600 CHECK_POINTER(self, "ungrab");
601 if (!PyArg_ParseTuple(args, ":ungrab"))
604 Py_XDECREF(grab_func);
610 #define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d}
612 static PyMethodDef PointerMethods[] = {
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."),
630 "Removes all bindings that were previously made by bind()."),
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."),
641 "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not "
643 { NULL, NULL, 0, NULL }
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,
660 /***************************************************************************
664 ***************************************************************************/
666 static void ptr_dealloc(PyObject *self)
671 static PyTypeObject PointerType = {
672 PyObject_HEAD_INIT(NULL)
677 (destructor) ptr_dealloc, /*tp_dealloc*/
684 0, /*tp_as_sequence*/
689 /**************************************************************************/
691 void pointer_startup()
693 PyObject *input, *inputdict;
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);
708 PointerType.ob_type = &PyType_Type;
709 PointerType.tp_methods = PointerMethods;
710 PointerType.tp_members = PointerMembers;
711 PyType_Ready(&PointerType);
712 PyType_Ready(&PointerDataType);
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);
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);
733 void pointer_shutdown()
738 g_datalist_clear(&bound_contexts);