]> Dogcows Code - chaz/openbox/blob - openbox/pointer.c
merge the C branch into HEAD
[chaz/openbox] / openbox / pointer.c
1 #include "pointer.h"
2 #include "keyboard.h"
3 #include "frame.h"
4 #include "engine.h"
5 #include "openbox.h"
6 #include "hooks.h"
7
8 #include <glib.h>
9 #include <Python.h>
10 #include <structmember.h> /* for PyMemberDef stuff */
11 #ifdef HAVE_STDLIB_H
12 # include <stdlib.h>
13 #endif
14
15 typedef enum {
16 Action_Press,
17 Action_Release,
18 Action_Click,
19 Action_DoubleClick,
20 Action_Motion,
21 NUM_ACTIONS
22 } Action;
23
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;
28 PyObject *grab_func;
29
30 struct foreach_grab_temp {
31 Client *client;
32 gboolean grab;
33 };
34
35 typedef struct {
36 guint state;
37 guint button;
38 Action action;
39 char *name;
40 GSList *funcs[NUM_ACTIONS];
41 } PointerBinding;
42
43 /***************************************************************************
44
45 Define the type 'ButtonData'
46
47 ***************************************************************************/
48
49 typedef struct PointerData {
50 PyObject_HEAD
51 Action action;
52 GQuark context;
53 char *button;
54 guint state;
55 guint buttonnum;
56 int posx, posy;
57 int pressposx, pressposy;
58 int pcareax, pcareay, pcareaw, pcareah;
59 } PointerData;
60
61 staticforward PyTypeObject PointerDataType;
62
63 /***************************************************************************
64
65 Type methods/struct
66
67 ***************************************************************************/
68
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)
73 {
74 PointerData *self = PyObject_New(PointerData, &PointerDataType);
75 self->button = g_strdup(button);
76 self->context = context;
77 self->action = action;
78 self->state = state;
79 self->buttonnum = buttonnum;
80 self->posx = posx;
81 self->posy = posy;
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;
89 }
90
91 static void ptrdata_dealloc(PointerData *self)
92 {
93 g_free(self->button);
94 PyObject_Del((PyObject*)self);
95 }
96
97 static PyObject *ptrdata_getattr(PointerData *self, char *name)
98 {
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);
109
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));
115 return pos;
116 }
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));
121 return presspos;
122 }
123 if (!strcmp(name, "pressclientarea")) {
124 if (self->pcareaw < 0) { /* < 0 indicates no client */
125 Py_INCREF(Py_None);
126 return Py_None;
127 } else {
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));
133 return ca;
134 }
135 }
136 }
137
138 PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name);
139 return NULL;
140 }
141
142 static PyTypeObject PointerDataType = {
143 PyObject_HEAD_INIT(NULL)
144 0,
145 "PointerData",
146 sizeof(PointerData),
147 0,
148 (destructor) ptrdata_dealloc, /*tp_dealloc*/
149 0, /*tp_print*/
150 (getattrfunc) ptrdata_getattr, /*tp_getattr*/
151 0, /*tp_setattr*/
152 0, /*tp_compare*/
153 0, /*tp_repr*/
154 0, /*tp_as_number*/
155 0, /*tp_as_sequence*/
156 0, /*tp_as_mapping*/
157 0, /*tp_hash */
158 };
159
160 /***************************************************************************/
161
162 static gboolean translate(char *str, guint *state, guint *button)
163 {
164 char **parsed;
165 char *l;
166 int i;
167 gboolean ret = FALSE;
168
169 parsed = g_strsplit(str, "-", -1);
170
171 /* first, find the button (last token) */
172 l = NULL;
173 for (i = 0; parsed[i] != NULL; ++i)
174 l = parsed[i];
175 if (l == NULL)
176 goto translation_fail;
177
178 /* figure out the mod mask */
179 *state = 0;
180 for (i = 0; parsed[i] != l; ++i) {
181 guint m = keyboard_translate_modifier(parsed[i]);
182 if (!m) goto translation_fail;
183 *state |= m;
184 }
185
186 /* figure out the button */
187 *button = atoi(l);
188 if (!*button) {
189 g_warning("Invalid button '%s' in pointer binding.", l);
190 goto translation_fail;
191 }
192
193 ret = TRUE;
194
195 translation_fail:
196 g_strfreev(parsed);
197 return ret;
198 }
199
200 static void grab_button(Client *client, guint state, guint button,
201 GQuark context, gboolean grab)
202 {
203 Window win;
204 int mode = GrabModeAsync;
205 unsigned int mask;
206
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
215 pointer_fire */
216 } else return;
217
218 if (grab)
219 XGrabButton(ob_display, button, state, win, FALSE, mask, mode,
220 GrabModeAsync, None, None);
221 else
222 XUngrabButton(ob_display, button, state, win);
223 }
224
225 static void foreach_grab(GQuark key, gpointer data, gpointer user_data)
226 {
227 struct foreach_grab_temp *d = user_data;
228 GSList *it;
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);
232 }
233 }
234
235 void pointer_grab_all(Client *client, gboolean grab)
236 {
237 struct foreach_grab_temp bt;
238 bt.client = client;
239 bt.grab = grab;
240 g_datalist_foreach(&bound_contexts, foreach_grab, &bt);
241 }
242
243 static void grab_all_clients(gboolean grab)
244 {
245 GSList *it;
246
247 for (it = client_list; it != NULL; it = it->next)
248 pointer_grab_all(it->data, grab);
249 }
250
251 static gboolean grab_pointer(gboolean grab)
252 {
253 gboolean ret = TRUE;
254 if (grab)
255 ret = XGrabPointer(ob_display, ob_root, FALSE, (ButtonPressMask |
256 ButtonReleaseMask |
257 ButtonMotionMask |
258 PointerMotionMask),
259 GrabModeAsync, GrabModeAsync, None, None,
260 CurrentTime) == GrabSuccess;
261 else
262 XUngrabPointer(ob_display, CurrentTime);
263 if (ret) grabbed = grab;
264 return ret;
265 }
266
267 static void foreach_clear(GQuark key, gpointer data, gpointer user_data)
268 {
269 GSList *it;
270 user_data = user_data;
271 for (it = data; it != NULL; it = it->next) {
272 int i;
273
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]);
279 }
280 g_free(b->name);
281 g_free(b);
282 }
283 g_slist_free(data);
284 }
285
286 static void clearall()
287 {
288 grab_all_clients(FALSE);
289 g_datalist_foreach(&bound_contexts, foreach_clear, NULL);
290 }
291
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)
297 {
298 PyObject *ptrdata, *args, *ret;
299 GSList *it;
300
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);
305
306 if (grabbed) {
307 ret = PyObject_CallObject(grab_func, args);
308 if (ret == NULL) PyErr_Print();
309 Py_XDECREF(ret);
310 } else {
311 for (it = functions; it != NULL; it = it->next) {
312 ret = PyObject_CallObject(it->data, args);
313 if (ret == NULL) PyErr_Print();
314 Py_XDECREF(ret);
315 }
316 }
317
318 Py_DECREF(args);
319 Py_DECREF(ptrdata);
320 }
321
322 void pointer_event(XEvent *e, Client *c)
323 {
324 static guint button = 0, lastbutton = 0;
325 static Time time = 0;
326 static Rect carea;
327 static guint pressx, pressy;
328 GQuark contextq;
329 gboolean click = FALSE, dblclick = FALSE;
330 PyObject *client;
331 GString *str = g_string_sized_new(0);
332 guint state;
333 GSList *it = NULL;
334 PointerBinding *b = NULL;
335
336 contextq = engine_get_context(c, e->xany.window);
337
338 /* pick a button, figure out clicks/double clicks */
339 switch (e->type) {
340 case ButtonPress:
341 if (!button) {
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;
347 }
348 state = e->xbutton.state;
349 break;
350 case ButtonRelease:
351 state = e->xbutton.state;
352 break;
353 case MotionNotify:
354 state = e->xmotion.state;
355 break;
356 default:
357 g_assert_not_reached();
358 return;
359 }
360
361 if (!grabbed) {
362 for (it = g_datalist_id_get_data(&bound_contexts, contextq);
363 it != NULL; it = it->next) {
364 b = it->data;
365 if (b->state == state && b->button == button)
366 break;
367 }
368 /* if not grabbed and not bound, then nothing to do! */
369 if (it == NULL) return;
370 }
371
372 if (c) client = clientwrap_new(c);
373 else client = Py_None;
374
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);
384
385 /* figure out clicks/double clicks */
386 switch (e->type) {
387 case ButtonRelease:
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) {
394 int junk;
395 Window wjunk;
396 guint ujunk, w, h;
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)
400 click =TRUE;
401 }
402
403 /* determine if this is a valid 'double-click' */
404 if (click) {
405 if (lastbutton == button &&
406 e->xbutton.time - double_click_rate < time) {
407 dblclick = TRUE;
408 lastbutton = 0;
409 } else
410 lastbutton = button;
411 } else
412 lastbutton = 0;
413 time = e->xbutton.time;
414 pressx = pressy = 0;
415 button = 0;
416 carea.x = carea.y = carea.width = carea.height = 0;
417 }
418 break;
419 }
420
421 /* fire off the events */
422 switch (e->type) {
423 case ButtonPress:
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]);
427 break;
428 case ButtonRelease:
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]);
432 break;
433 case MotionNotify:
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)
437 break;
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]);
443 break;
444 }
445
446 if (click)
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]);
450 if (dblclick)
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]);
454
455 g_string_free(str, TRUE);
456 if (client != Py_None) { Py_DECREF(client); }
457
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;
464 pointer_event(e, c);
465 }
466 }
467 }
468
469 /***************************************************************************
470
471 Define the type 'Pointer'
472
473 ***************************************************************************/
474
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' " \
480 "object"); \
481 return NULL; \
482 } \
483 }
484
485 typedef struct Pointer {
486 PyObject_HEAD
487 Action press;
488 Action release;
489 Action click;
490 Action doubleclick;
491 Action motion;
492 } Pointer;
493
494 staticforward PyTypeObject PointerType;
495
496 static PyObject *ptr_bind(Pointer *self, PyObject *args)
497 {
498 char *buttonstr;
499 char *contextstr;
500 guint state, button;
501 PointerBinding *b;
502 GSList *it;
503 GQuark context;
504 PyObject *func;
505 Action action;
506 int i;
507
508 CHECK_POINTER(self, "grab");
509 if (!PyArg_ParseTuple(args, "ssiO:grab",
510 &buttonstr, &contextstr, &action, &func))
511 return NULL;
512
513 if (!translate(buttonstr, &state, &button)) {
514 PyErr_SetString(PyExc_ValueError, "invalid button");
515 return NULL;
516 }
517
518 context = g_quark_try_string(contextstr);
519 if (!context) {
520 PyErr_SetString(PyExc_ValueError, "invalid context");
521 return NULL;
522 }
523
524 if (action < 0 || action >= NUM_ACTIONS) {
525 PyErr_SetString(PyExc_ValueError, "invalid action");
526 return NULL;
527 }
528
529 if (!PyCallable_Check(func)) {
530 PyErr_SetString(PyExc_ValueError, "expected a callable object");
531 return NULL;
532 }
533
534 for (it = g_datalist_id_get_data(&bound_contexts, context);
535 it != NULL; it = it->next){
536 b = it->data;
537 if (b->state == state && b->button == button) {
538 /* already bound */
539 b->funcs[action] = g_slist_append(b->funcs[action], func);
540 Py_INCREF(Py_None);
541 return Py_None;
542 }
543 }
544
545 grab_all_clients(FALSE);
546
547 /* add the binding */
548 b = g_new(PointerBinding, 1);
549 b->state = state;
550 b->button = button;
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);
558
559 Py_INCREF(Py_None);
560 return Py_None;
561 }
562
563 static PyObject *ptr_clearBinds(Pointer *self, PyObject *args)
564 {
565 CHECK_POINTER(self, "clearBinds");
566 if (!PyArg_ParseTuple(args, ":clearBinds"))
567 return NULL;
568 clearall();
569 Py_INCREF(Py_None);
570 return Py_None;
571 }
572
573 static PyObject *ptr_grab(Pointer *self, PyObject *args)
574 {
575 PyObject *func;
576
577 CHECK_POINTER(self, "grab");
578 if (!PyArg_ParseTuple(args, "O:grab", &func))
579 return NULL;
580 if (!PyCallable_Check(func)) {
581 PyErr_SetString(PyExc_ValueError, "expected a callable object");
582 return NULL;
583 }
584 if (!grab_pointer(TRUE)) {
585 PyErr_SetString(PyExc_RuntimeError, "failed to grab pointer");
586 return NULL;
587 }
588 grab_func = func;
589 Py_INCREF(grab_func);
590 Py_INCREF(Py_None);
591 return Py_None;
592 }
593
594 static PyObject *ptr_ungrab(Pointer *self, PyObject *args)
595 {
596 CHECK_POINTER(self, "ungrab");
597 if (!PyArg_ParseTuple(args, ":ungrab"))
598 return NULL;
599 grab_pointer(FALSE);
600 Py_XDECREF(grab_func);
601 grab_func = NULL;
602 Py_INCREF(Py_None);
603 return Py_None;
604 }
605
606 #define METH(n, d) {#n, (PyCFunction)ptr_##n, METH_VARARGS, #d}
607
608 static PyMethodDef PointerMethods[] = {
609 METH(bind,
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."),
624 METH(clearBinds,
625 "clearBinds()\n\n"
626 "Removes all bindings that were previously made by bind()."),
627 METH(grab,
628 "grab(func)\n\n"
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."),
635 METH(ungrab,
636 "ungrab()\n\n"
637 "Ungrabs the pointer. The pointer cannot be ungrabbed if it is not "
638 "grabbed."),
639 { NULL, NULL, 0, NULL }
640 };
641
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,
652 "a pointer drag"},
653 {NULL}
654 };
655
656 /***************************************************************************
657
658 Type methods/struct
659
660 ***************************************************************************/
661
662 static void ptr_dealloc(PyObject *self)
663 {
664 PyObject_Del(self);
665 }
666
667 static PyTypeObject PointerType = {
668 PyObject_HEAD_INIT(NULL)
669 0,
670 "Pointer",
671 sizeof(Pointer),
672 0,
673 (destructor) ptr_dealloc, /*tp_dealloc*/
674 0, /*tp_print*/
675 0, /*tp_getattr*/
676 0, /*tp_setattr*/
677 0, /*tp_compare*/
678 0, /*tp_repr*/
679 0, /*tp_as_number*/
680 0, /*tp_as_sequence*/
681 0, /*tp_as_mapping*/
682 0, /*tp_hash */
683 };
684
685 /**************************************************************************/
686
687 void pointer_startup()
688 {
689 PyObject *input, *inputdict;
690 Pointer *ptr;
691
692 grabbed = FALSE;
693 double_click_rate = 300;
694 drag_threshold = 3;
695 g_datalist_init(&bound_contexts);
696
697 PointerType.ob_type = &PyType_Type;
698 PointerType.tp_methods = PointerMethods;
699 PointerType.tp_members = PointerMembers;
700 PyType_Ready(&PointerType);
701 PyType_Ready(&PointerDataType);
702
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);
708
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);
717 Py_DECREF(ptr);
718
719 Py_DECREF(input);
720 }
721
722 void pointer_shutdown()
723 {
724 if (grabbed)
725 grab_pointer(FALSE);
726 clearall();
727 g_datalist_clear(&bound_contexts);
728 }
729
This page took 0.065102 seconds and 4 git commands to generate.