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
);