summaryrefslogtreecommitdiff
path: root/openbox/keyboard.c
diff options
context:
space:
mode:
authorDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
committerDana Jansens <danakj@orodu.net>2003-03-16 21:11:39 +0000
commitf8a47de5ec444c452093371e3db16857eb39a490 (patch)
tree31db2567842d98232775f9980f7a8d2586c0ac71 /openbox/keyboard.c
parent8ba0586bcbdc7fe9648f1063812126d71a041670 (diff)
merge the C branch into HEAD
Diffstat (limited to 'openbox/keyboard.c')
-rw-r--r--openbox/keyboard.c696
1 files changed, 696 insertions, 0 deletions
diff --git a/openbox/keyboard.c b/openbox/keyboard.c
new file mode 100644
index 00000000..87cd5036
--- /dev/null
+++ b/openbox/keyboard.c
@@ -0,0 +1,696 @@
+#include "focus.h"
+#include "openbox.h"
+#include "keyboard.h"
+#include "clientwrap.h"
+
+#include <Python.h>
+#include <glib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif
+
+typedef struct KeyBindingTree {
+ guint state;
+ guint key;
+ GList *keylist;
+ PyObject *func;
+
+ /* the next binding in the tree at the same level */
+ struct KeyBindingTree *next_sibling;
+ /* the first child of this binding (next binding in a chained sequence).*/
+ struct KeyBindingTree *first_child;
+} KeyBindingTree;
+
+
+static KeyBindingTree *firstnode, *curpos;
+static guint reset_key, reset_state;
+static gboolean grabbed, user_grabbed;
+static PyObject *grab_func;
+
+/***************************************************************************
+
+ Define the type 'KeyboardData'
+
+ ***************************************************************************/
+
+typedef struct KeyboardData {
+ PyObject_HEAD
+ PyObject *keychain;
+ guint state;
+ guint keycode;
+ gboolean press;
+} KeyboardData;
+
+staticforward PyTypeObject KeyboardDataType;
+
+/***************************************************************************
+
+ Type methods/struct
+
+ ***************************************************************************/
+
+static PyObject *keybdata_new(PyObject *keychain, guint state,
+ guint keycode, gboolean press)
+{
+ KeyboardData *data = PyObject_New(KeyboardData, &KeyboardDataType);
+ data->keychain = keychain;
+ Py_INCREF(keychain);
+ data->state = state;
+ data->keycode = keycode;
+ data->press = press;
+ return (PyObject*) data;
+}
+
+static void keybdata_dealloc(KeyboardData *self)
+{
+ Py_DECREF(self->keychain);
+ PyObject_Del((PyObject*)self);
+}
+
+static PyObject *keybdata_getattr(KeyboardData *self, char *name)
+{
+ if (!strcmp(name, "keychain")) {
+ Py_INCREF(self->keychain);
+ return self->keychain;
+ } else if (!strcmp(name, "state"))
+ return PyInt_FromLong(self->state);
+ else if (!strcmp(name, "keycode"))
+ return PyInt_FromLong(self->keycode);
+ else if (!strcmp(name, "press"))
+ return PyInt_FromLong(!!self->press);
+
+ PyErr_Format(PyExc_AttributeError, "no such attribute '%s'", name);
+ return NULL;
+}
+
+static PyTypeObject KeyboardDataType = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "KeyboardData",
+ sizeof(KeyboardData),
+ 0,
+ (destructor) keybdata_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ (getattrfunc) keybdata_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 */
+};
+
+/***************************************************************************/
+
+guint keyboard_translate_modifier(char *str)
+{
+ if (!strcmp("Mod1", str)) return Mod1Mask;
+ else if (!strcmp("Mod2", str)) return Mod2Mask;
+ else if (!strcmp("Mod3", str)) return Mod3Mask;
+ else if (!strcmp("Mod4", str)) return Mod4Mask;
+ else if (!strcmp("Mod5", str)) return Mod5Mask;
+ else if (!strcmp("C", str)) return ControlMask;
+ else if (!strcmp("S", str)) return ShiftMask;
+ g_warning("Invalid modifier '%s' in binding.", str);
+ return 0;
+}
+
+static gboolean translate(char *str, guint *state, guint *keycode)
+{
+ char **parsed;
+ char *l;
+ int i;
+ gboolean ret = FALSE;
+ KeySym sym;
+
+ parsed = g_strsplit(str, "-", -1);
+
+ /* first, find the key (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 keycode */
+ sym = XStringToKeysym(l);
+ if (sym == NoSymbol) {
+ g_warning("Invalid key name '%s' in key binding.", l);
+ goto translation_fail;
+ }
+ *keycode = XKeysymToKeycode(ob_display, sym);
+ if (!keycode) {
+ g_warning("Key '%s' does not exist on the display.", l);
+ goto translation_fail;
+ }
+
+ ret = TRUE;
+
+translation_fail:
+ g_strfreev(parsed);
+ return ret;
+}
+
+static void destroytree(KeyBindingTree *tree)
+{
+ KeyBindingTree *c;
+
+ while (tree) {
+ destroytree(tree->next_sibling);
+ c = tree->first_child;
+ if (c == NULL) {
+ GList *it;
+ for (it = tree->keylist; it != NULL; it = it->next)
+ g_free(it->data);
+ g_list_free(tree->keylist);
+ Py_XDECREF(tree->func);
+ }
+ g_free(tree);
+ tree = c;
+ }
+}
+
+static KeyBindingTree *buildtree(GList *keylist)
+{
+ GList *it;
+ KeyBindingTree *ret = NULL, *p;
+
+ if (g_list_length(keylist) <= 0)
+ return NULL; /* nothing in the list.. */
+
+ for (it = g_list_last(keylist); it != NULL; it = it->prev) {
+ p = ret;
+ ret = g_new(KeyBindingTree, 1);
+ ret->next_sibling = NULL;
+ ret->func = NULL;
+ if (p == NULL) {
+ GList *it;
+
+ /* this is the first built node, the bottom node of the tree */
+ ret->keylist = g_list_copy(keylist); /* shallow copy */
+ for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */
+ it->data = g_strdup(it->data);
+ }
+ ret->first_child = p;
+ if (!translate(it->data, &ret->state, &ret->key)) {
+ destroytree(ret);
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+static void assimilate(KeyBindingTree *node)
+{
+ KeyBindingTree *a, *b, *tmp, *last;
+
+ if (firstnode == NULL) {
+ /* there are no nodes at this level yet */
+ firstnode = node;
+ } else {
+ a = firstnode;
+ last = a;
+ b = node;
+ while (a) {
+ last = a;
+ if (!(a->state == b->state && a->key == b->key)) {
+ a = a->next_sibling;
+ } else {
+ tmp = b;
+ b = b->first_child;
+ g_free(tmp);
+ a = a->first_child;
+ }
+ }
+ if (!(last->state == b->state && last->key == b->key))
+ last->next_sibling = b;
+ else {
+ last->first_child = b->first_child;
+ g_free(b);
+ }
+ }
+}
+
+static KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict)
+{
+ KeyBindingTree *a, *b;
+
+ *conflict = FALSE;
+
+ a = firstnode;
+ b = search;
+ while (a && b) {
+ if (!(a->state == b->state && a->key == b->key)) {
+ a = a->next_sibling;
+ } else {
+ if ((a->first_child == NULL) == (b->first_child == NULL)) {
+ if (a->first_child == NULL) {
+ /* found it! (return the actual node, not the search's) */
+ return a;
+ }
+ } else {
+ *conflict = TRUE;
+ return NULL; /* the chain status' don't match (conflict!) */
+ }
+ b = b->first_child;
+ a = a->first_child;
+ }
+ }
+ return NULL; // it just isn't in here
+}
+
+static void grab_keys(gboolean grab)
+{
+ if (!grab) {
+ XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root);
+ } else {
+ KeyBindingTree *p = firstnode;
+ while (p) {
+ XGrabKey(ob_display, p->key, p->state, ob_root, FALSE,
+ GrabModeAsync, GrabModeSync);
+ p = p->next_sibling;
+ }
+ }
+}
+
+static void reset_chains()
+{
+ /* XXX kill timer */
+ curpos = NULL;
+ if (grabbed) {
+ grabbed = FALSE;
+ g_message("reset chains. user_grabbed: %d", user_grabbed);
+ if (!user_grabbed)
+ XUngrabKeyboard(ob_display, CurrentTime);
+ }
+}
+
+void keyboard_event(XKeyEvent *e)
+{
+ PyObject *chain, *client, *args, *keybdata, *ret;
+ gboolean press = e->type == KeyPress;
+
+ if (focus_client) client = clientwrap_new(focus_client);
+ else client = Py_None;
+
+ if (user_grabbed) {
+ GString *str = g_string_sized_new(0);
+ KeySym sym;
+
+ /* build the 'chain' */
+ if (e->state & ControlMask)
+ g_string_append(str, "C-");
+ if (e->state & ShiftMask)
+ g_string_append(str, "S-");
+ if (e->state & Mod1Mask)
+ g_string_append(str, "Mod1-");
+ if (e->state & Mod2Mask)
+ g_string_append(str, "Mod2-");
+ if (e->state & Mod3Mask)
+ g_string_append(str, "Mod3-");
+ if (e->state & Mod4Mask)
+ g_string_append(str, "Mod4-");
+ if (e->state & Mod5Mask)
+ g_string_append(str, "Mod5-");
+
+ sym = XKeycodeToKeysym(ob_display, e->keycode, 0);
+ if (sym == NoSymbol)
+ g_string_append(str, "NoSymbol");
+ else {
+ char *name = XKeysymToString(sym);
+ if (name == NULL)
+ name = "Undefined";
+ g_string_append(str, name);
+ }
+
+ chain = PyTuple_New(1);
+ PyTuple_SET_ITEM(chain, 0, PyString_FromString(str->str));
+ g_string_free(str, TRUE);
+
+ keybdata = keybdata_new(chain, e->state, e->keycode, press);
+
+ args = Py_BuildValue("OO", keybdata, client);
+
+ ret = PyObject_CallObject(grab_func, args);
+ if (ret == NULL) PyErr_Print();
+ Py_XDECREF(ret);
+
+ Py_DECREF(args);
+ Py_DECREF(keybdata);
+ Py_DECREF(chain);
+ }
+
+ if (press) {
+ if (e->keycode == reset_key && e->state == reset_state) {
+ reset_chains();
+ XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
+ } else {
+ KeyBindingTree *p;
+ if (curpos == NULL)
+ p = firstnode;
+ else
+ p = curpos->first_child;
+ while (p) {
+ if (p->key == e->keycode && p->state == e->state) {
+ if (p->first_child != NULL) { /* part of a chain */
+ /* XXX TIMER */
+ if (!grabbed && !user_grabbed) {
+ /*grab should never fail because we should have a
+ sync grab at this point */
+ XGrabKeyboard(ob_display, ob_root, 0,
+ GrabModeAsync, GrabModeSync,
+ CurrentTime);
+ }
+ grabbed = TRUE;
+ curpos = p;
+ XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
+ } else {
+ GList *it;
+ int i;
+
+ chain = PyTuple_New(g_list_length(p->keylist));
+ for (i = 0, it = p->keylist; it != NULL;
+ it = it->next, ++i)
+ PyTuple_SET_ITEM(chain, i,
+ PyString_FromString(it->data));
+
+ keybdata = keybdata_new(chain, e->state, e->keycode,
+ press);
+
+ args = Py_BuildValue("OO", keybdata, client);
+
+ ret = PyObject_CallObject(p->func, args);
+ if (ret == NULL) PyErr_Print();
+ Py_XDECREF(ret);
+
+ Py_DECREF(args);
+ Py_DECREF(keybdata);
+ Py_DECREF(chain);
+
+ XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
+ reset_chains();
+ }
+ break;
+ }
+ p = p->next_sibling;
+ }
+ }
+ }
+
+ if (client != Py_None) { Py_DECREF(client); }
+}
+
+static void clearall()
+{
+ grab_keys(FALSE);
+ destroytree(firstnode);
+ firstnode = NULL;
+ grab_keys(TRUE);
+}
+
+static gboolean grab_keyboard(gboolean grab)
+{
+ gboolean ret = TRUE;
+
+ g_message("grab_keyboard(%s). grabbed: %d", (grab?"True":"False"),grabbed);
+
+ user_grabbed = grab;
+ if (!grabbed) {
+ if (grab)
+ ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync,
+ GrabModeAsync, CurrentTime) == GrabSuccess;
+ else
+ XUngrabKeyboard(ob_display, CurrentTime);
+ }
+ return ret;
+}
+
+/***************************************************************************
+
+ Define the type 'Keyboard'
+
+ ***************************************************************************/
+
+#define IS_KEYBOARD(v) ((v)->ob_type == &KeyboardType)
+#define CHECK_KEYBOARD(self, funcname) { \
+ if (!IS_KEYBOARD(self)) { \
+ PyErr_SetString(PyExc_TypeError, \
+ "descriptor '" funcname "' requires a 'Keyboard' " \
+ "object"); \
+ return NULL; \
+ } \
+}
+
+typedef struct Keyboard {
+ PyObject_HEAD
+} Keyboard;
+
+staticforward PyTypeObject KeyboardType;
+
+static PyObject *keyb_bind(Keyboard *self, PyObject *args)
+{
+ KeyBindingTree *tree = NULL, *t;
+ gboolean conflict;
+ PyObject *item, *tuple, *func;
+ GList *keylist = NULL, *it;
+ int i, s;
+
+ CHECK_KEYBOARD(self, "grab");
+ if (!PyArg_ParseTuple(args, "OO:grab", &tuple, &func))
+ return NULL;
+
+ if (!PyTuple_Check(tuple)) {
+ PyErr_SetString(PyExc_ValueError, "expected a tuple of strings");
+ goto binderror;
+ }
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_ValueError, "expected a callable object");
+ goto binderror;
+ }
+
+ s = PyTuple_GET_SIZE(tuple);
+ if (s <= 0) {
+ PyErr_SetString(PyExc_ValueError, "expected a tuple of strings");
+ goto binderror;
+ }
+
+ for (i = 0; i < s; ++i) {
+ item = PyTuple_GET_ITEM(tuple, i);
+ if (!PyString_Check(item)) {
+ PyErr_SetString(PyExc_ValueError, "expected a tuple of strings");
+ goto binderror;
+ }
+ keylist = g_list_append(keylist,
+ g_strdup(PyString_AsString(item)));
+ }
+
+ if (!(tree = buildtree(keylist))) {
+ PyErr_SetString(PyExc_ValueError, "invalid binding");
+ goto binderror;
+ }
+
+ t = find(tree, &conflict);
+ if (conflict) {
+ PyErr_SetString(PyExc_ValueError, "conflict with binding");
+ goto binderror;
+ }
+ if (t != NULL) {
+ /* already bound to something */
+ PyErr_SetString(PyExc_ValueError, "keychain is already bound");
+ goto binderror;
+ }
+
+ /* grab the server here to make sure no key pressed go missed */
+ XGrabServer(ob_display);
+ XSync(ob_display, FALSE);
+
+ grab_keys(FALSE);
+
+ /* set the function */
+ t = tree;
+ while (t->first_child) t = t->first_child;
+ t->func = func;
+ Py_INCREF(func);
+
+ /* assimilate this built tree into the main tree */
+ assimilate(tree); // assimilation destroys/uses the tree
+
+ grab_keys(TRUE);
+
+ XUngrabServer(ob_display);
+ XFlush(ob_display);
+
+ for (it = keylist; it != NULL; it = it->next)
+ g_free(it->data);
+ g_list_free(it);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+
+binderror:
+ if (tree != NULL) destroytree(tree);
+ for (it = keylist; it != NULL; it = it->next)
+ g_free(it->data);
+ g_list_free(it);
+ return NULL;
+}
+
+static PyObject *keyb_clearBinds(Keyboard *self, PyObject *args)
+{
+ CHECK_KEYBOARD(self, "clearBinds");
+ if (!PyArg_ParseTuple(args, ":clearBinds"))
+ return NULL;
+ clearall();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *keyb_grab(Keyboard *self, PyObject *args)
+{
+ PyObject *func;
+
+ CHECK_KEYBOARD(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_keyboard(TRUE)) {
+ PyErr_SetString(PyExc_RuntimeError, "failed to grab keyboard");
+ return NULL;
+ }
+ grab_func = func;
+ Py_INCREF(grab_func);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *keyb_ungrab(Keyboard *self, PyObject *args)
+{
+ CHECK_KEYBOARD(self, "ungrab");
+ if (!PyArg_ParseTuple(args, ":ungrab"))
+ return NULL;
+ grab_keyboard(FALSE);
+ Py_XDECREF(grab_func);
+ grab_func = NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#define METH(n, d) {#n, (PyCFunction)keyb_##n, METH_VARARGS, #d}
+
+static PyMethodDef KeyboardMethods[] = {
+ METH(bind,
+ "bind(keychain, func)\n\n"
+ "Binds a key-chain to a function. The keychain is a tuple of strings "
+ "which define a chain of key presses. Each member of the tuple has "
+ "the format [Modifier-]...[Key]. 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 Key can be any valid key definition. Key "
+ "definitions can be found by running 'xev', pressing the key while "
+ "its window is focused, and watching its output. Here are some "
+ "examples of valid keychains: ('a'), ('F7'), ('control-a', 'd'), "
+ "('control-mod1-x', 'control-mod4-g'), ('F1', 'space'). The func "
+ "must have a definition similar to 'def func(keydata, client)'. A "
+ "keychain cannot 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 entire keyboard, causing all possible keyboard events to "
+ "be passed 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 type until you restart Openbox. The func must have a "
+ "definition similar to 'def func(keydata)'. The keyboard cannot be "
+ "grabbed if it is already grabbed."),
+ METH(ungrab,
+ "ungrab()\n\n"
+ "Ungrabs the keyboard. The keyboard cannot be ungrabbed if it is not "
+ "grabbed."),
+ { NULL, NULL, 0, NULL }
+};
+
+/***************************************************************************
+
+ Type methods/struct
+
+ ***************************************************************************/
+
+static void keyb_dealloc(PyObject *self)
+{
+ PyObject_Del(self);
+}
+
+static PyTypeObject KeyboardType = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "Keyboard",
+ sizeof(Keyboard),
+ 0,
+ (destructor) keyb_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 keyboard_startup()
+{
+ PyObject *input, *inputdict, *ptr;
+ gboolean b;
+
+ curpos = firstnode = NULL;
+ grabbed = user_grabbed = FALSE;
+
+ b = translate("C-G", &reset_state, &reset_key);
+ g_assert(b);
+
+ KeyboardType.ob_type = &PyType_Type;
+ KeyboardType.tp_methods = KeyboardMethods;
+ PyType_Ready(&KeyboardType);
+ PyType_Ready(&KeyboardDataType);
+
+ /* 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 Keyboard instance to the input module */
+ ptr = (PyObject*) PyObject_New(Keyboard, &KeyboardType);
+ PyDict_SetItemString(inputdict, "Keyboard", ptr);
+ Py_DECREF(ptr);
+
+ Py_DECREF(input);
+}
+
+void keyboard_shutdown()
+{
+ if (grabbed || user_grabbed) {
+ grabbed = FALSE;
+ grab_keyboard(FALSE);
+ }
+ grab_keys(FALSE);
+ destroytree(firstnode);
+ firstnode = NULL;
+}
+