10 #include <structmember.h> /* for PyMemberDef stuff */
24 /* GData of GSList*s of PointerBinding*s. */
25 static GData *bound_contexts;
26 static gboolean grabbed;
27 static int double_click_rate, drag_threshold;
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;
336 contextq = engine_get_context(c, e->xany.window);
338 /* pick a button, figure out clicks/double clicks */
342 button = e->xbutton.button;
343 if (c != NULL) carea = c->frame->area;
344 else carea.width = -1; /* indicates no client */
345 pressx = e->xbutton.x_root;
346 pressy = e->xbutton.y_root;
348 state = e->xbutton.state;
351 state = e->xbutton.state;
354 state = e->xmotion.state;
357 g_assert_not_reached();
362 for (it = g_datalist_id_get_data(&bound_contexts, contextq);
363 it != NULL; it = it->next) {
365 if (b->state == state && b->button == button)
368 /* if not grabbed and not bound, then nothing to do! */
369 if (it == NULL) return;
372 if (c) client = clientwrap_new(c);
373 else client = Py_None;
375 /* build the button string */
376 if (state & ControlMask) g_string_append(str, "C-");
377 if (state & ShiftMask) g_string_append(str, "S-");
378 if (state & Mod1Mask) g_string_append(str, "Mod1-");
379 if (state & Mod2Mask) g_string_append(str, "Mod2-");
380 if (state & Mod3Mask) g_string_append(str, "Mod3-");
381 if (state & Mod4Mask) g_string_append(str, "Mod4-");
382 if (state & Mod5Mask) g_string_append(str, "Mod5-");
383 g_string_append_printf(str, "%d", button);
385 /* figure out clicks/double clicks */
388 if (button == e->xbutton.button) {
389 /* determine if this is a valid 'click'. Its not if the release is
390 not over the window, or if a drag occured. */
391 if (ABS(e->xbutton.x_root - pressx) < (unsigned)drag_threshold &&
392 ABS(e->xbutton.y_root - pressy) < (unsigned)drag_threshold &&
393 e->xbutton.x >= 0 && e->xbutton.y >= 0) {
397 XGetGeometry(ob_display, e->xany.window, &wjunk, &junk, &junk,
398 &w, &h, &ujunk, &ujunk);
399 if (e->xbutton.x < (signed)w && e->xbutton.y < (signed)h)
403 /* determine if this is a valid 'double-click' */
405 if (lastbutton == button &&
406 e->xbutton.time - double_click_rate < time) {
413 time = e->xbutton.time;
416 carea.x = carea.y = carea.width = carea.height = 0;
421 /* fire off the events */
424 fire_event(str->str, contextq, Action_Press,
425 state, button, 0, 0, 0, 0, 0, 0, 0, 0,
426 client, b == NULL ? NULL : b->funcs[Action_Press]);
429 fire_event(str->str, contextq, Action_Release,
430 state, button, 0, 0, 0, 0, 0, 0, 0, 0,
431 client, b == NULL ? NULL : b->funcs[Action_Release]);
434 /* watch out for the drag threshold */
435 if (ABS(e->xmotion.x_root - pressx) < (unsigned)drag_threshold &&
436 ABS(e->xmotion.y_root - pressy) < (unsigned)drag_threshold)
438 fire_event(str->str, contextq, Action_Motion,
439 state, button, e->xmotion.x_root,
440 e->xmotion.y_root, pressx, pressy,
441 carea.x, carea.y, carea.width, carea.height,
442 client, b == NULL ? NULL : b->funcs[Action_Motion]);
447 fire_event(str->str, contextq, Action_Click,
448 state, button, 0, 0, 0, 0, 0, 0, 0, 0,
449 client, b == NULL ? NULL : b->funcs[Action_Click]);
451 fire_event(str->str, contextq, Action_DoubleClick,
452 state, button, 0, 0, 0, 0, 0, 0, 0, 0,
453 client, b == NULL ? NULL : b->funcs[Action_DoubleClick]);
455 g_string_free(str, TRUE);
456 if (client != Py_None) { Py_DECREF(client); }
458 if (contextq == g_quark_try_string("client")) {
459 /* Replay the event, so it goes to the client*/
460 XAllowEvents(ob_display, ReplayPointer, CurrentTime);
461 /* generate a release event since we don't get real ones */
462 if (e->type == ButtonPress) {
463 e->type = ButtonRelease;
469 /***************************************************************************
471 Define the type 'Pointer'
473 ***************************************************************************/
475 #define IS_POINTER(v) ((v)->ob_type == &PointerType)
476 #define CHECK_POINTER(self, funcname) { \
477 if (!IS_POINTER(self)) { \
478 PyErr_SetString(PyExc_TypeError, \
479 "descriptor '" funcname "' requires a 'Pointer' " \
485 typedef struct Pointer {
494 staticforward PyTypeObject PointerType;
496 static PyObject *ptr_bind(Pointer *self, PyObject *args)
508 CHECK_POINTER(self, "grab");
509 if (!PyArg_ParseTuple(args, "ssiO:grab",
510 &buttonstr, &contextstr, &action, &func))
513 if (!translate(buttonstr, &state, &button)) {
514 PyErr_SetString(PyExc_ValueError, "invalid button");
518 context = g_quark_try_string(contextstr);
520 PyErr_SetString(PyExc_ValueError, "invalid context");
524 if (action < 0 || action >= NUM_ACTIONS) {
525 PyErr_SetString(PyExc_ValueError, "invalid action");
529 if (!PyCallable_Check(func)) {
530 PyErr_SetString(PyExc_ValueError, "expected a callable object");
534 for (it = g_datalist_id_get_data(&bound_contexts, context);
535 it != NULL; it = it->next){
537 if (b->state == state && b->button == button) {
539 b->funcs[action] = g_slist_append(b->funcs[action], func);
545 grab_all_clients(FALSE);
547 /* add the binding */
548 b = g_new(PointerBinding, 1);
551 b->name = g_strdup(buttonstr);
552 for (i = 0; i < NUM_ACTIONS; ++i)
553 if (i != (signed)action) b->funcs[i] = NULL;
554 b->funcs[action] = g_slist_append(NULL, func);
555 g_datalist_id_set_data(&bound_contexts, context,
556 g_slist_append(g_datalist_id_get_data(&bound_contexts, context), b));
557 grab_all_clients(TRUE);
563 static PyObject *ptr_clearBinds(Pointer *self, PyObject *args)
565 CHECK_POINTER(self, "clearBinds");
566 if (!PyArg_ParseTuple(args, ":clearBinds"))
573 static PyObject *ptr_grab(Pointer *self, PyObject *args)
577 CHECK_POINTER(self, "grab");
578 if (!PyArg_ParseTuple(args, "O:grab", &func))
580 if (!PyCallable_Check(func)) {
581 PyErr_SetString(PyExc_ValueError, "expected a callable object");
584 if (!grab_pointer(TRUE)) {
585 PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer");
589 Py_INCREF(grab_func);
594 static PyObject *ptr_ungrab(Pointer *self, PyObject *args)
596 CHECK_POINTER(self, "ungrab");
597 if (!PyArg_ParseTuple(args, ":ungrab"))
600 Py_XDECREF(grab_func);
606 #define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d}
608 static PyMethodDef PointerMethods[] = {
610 "bind(button, context, func)\n\n"
611 "Binds a pointer button for a context to a function. See the "
612 "Terminology section for a decription and list of common contexts. "
613 "The button is a string which defines a modifier and button "
614 "combination with the format [Modifier-]...[Button]. Modifiers can "
615 "be 'mod1', 'mod2', 'mod3', 'mod4', 'mod5', 'control', and 'shift'. "
616 "The keys on your keyboard that are bound to each of these modifiers "
617 "can be found by running 'xmodmap'. The button is the number of the "
618 "button. Button numbers can be found by running 'xev', pressing the "
619 "button with the pointer over its window, and watching its output. "
620 "Here are some examples of valid buttons: 'control-1', '2', "
621 "'mod1-shift-5'. The func must have a definition similar to "
622 "'def func(keydata, client)'. A button and context may be bound to "
623 "more than one function."),
626 "Removes all bindings that were previously made by bind()."),
629 "Grabs the pointer device, causing all possible pointer events to be "
630 "sent to the given function. CAUTION: Be sure when you grab() that "
631 "you also have an ungrab() that will execute, or you will not be "
632 "able to use the pointer device until you restart Openbox. The func "
633 "must have a definition similar to 'def func(keydata)'. The pointer "
634 "cannot be grabbed if it is already grabbed."),
637 "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not "
639 { NULL, NULL, 0, NULL }
642 static PyMemberDef PointerMembers[] = {
643 {"Action_Press", T_INT, offsetof(Pointer, press), READONLY,
644 "a pointer button press"},
645 {"Action_Release", T_INT, offsetof(Pointer, release), READONLY,
646 "a pointer button release"},
647 {"Action_Click", T_INT, offsetof(Pointer, click), READONLY,
648 "a pointer button click (press-release)"},
649 {"Action_DoubleClick", T_INT, offsetof(Pointer, doubleclick), READONLY,
650 "a pointer button double-click"},
651 {"Action_Motion", T_INT, offsetof(Pointer, motion), READONLY,
656 /***************************************************************************
660 ***************************************************************************/
662 static void ptr_dealloc(PyObject *self)
667 static PyTypeObject PointerType = {
668 PyObject_HEAD_INIT(NULL)
673 (destructor) ptr_dealloc, /*tp_dealloc*/
680 0, /*tp_as_sequence*/
685 /**************************************************************************/
687 void pointer_startup()
689 PyObject *input, *inputdict;
693 double_click_rate = 300;
695 g_datalist_init(&bound_contexts);
697 PointerType.ob_type = &PyType_Type;
698 PointerType.tp_methods = PointerMethods;
699 PointerType.tp_members = PointerMembers;
700 PyType_Ready(&PointerType);
701 PyType_Ready(&PointerDataType);
703 /* get the input module/dict */
704 input = PyImport_ImportModule("input"); /* new */
705 g_assert(input != NULL);
706 inputdict = PyModule_GetDict(input); /* borrowed */
707 g_assert(inputdict != NULL);
709 /* add a Pointer instance to the input module */
710 ptr = PyObject_New(Pointer, &PointerType);
711 ptr->press = Action_Press;
712 ptr->release = Action_Release;
713 ptr->click = Action_Click;
714 ptr->doubleclick = Action_DoubleClick;
715 ptr->motion = Action_Motion;
716 PyDict_SetItemString(inputdict, "Pointer", (PyObject*) ptr);
722 void pointer_shutdown()
727 g_datalist_clear(&bound_contexts);