+/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
+
+ keyboard.c for the Openbox window manager
+ Copyright (c) 2006 Mikael Magnusson
+ Copyright (c) 2003 Ben Jansens
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ See the COPYING file for a copy of the GNU General Public License.
+*/
+
+#include "mainloop.h"
#include "focus.h"
+#include "screen.h"
+#include "frame.h"
#include "openbox.h"
+#include "event.h"
+#include "grab.h"
+#include "client.h"
+#include "action.h"
+#include "prop.h"
+#include "config.h"
+#include "keytree.h"
#include "keyboard.h"
-#include "clientwrap.h"
+#include "translate.h"
+#include "moveresize.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;
+KeyBindingTree *keyboard_firstnode;
+typedef struct {
+ guint state;
+ ObClient *client;
+ GSList *actions;
+ ObFrameContext context;
+} ObInteractiveState;
-static KeyBindingTree *firstnode, *curpos;
-static guint reset_key, reset_state;
-static gboolean grabbed, user_grabbed;
-static PyObject *grab_func;
+static GSList *interactive_states;
-/***************************************************************************
-
- Define the type 'KeyboardData'
+static KeyBindingTree *curpos;
- ***************************************************************************/
+static void grab_for_window(Window win, gboolean grab)
+{
+ KeyBindingTree *p;
+
+ ungrab_all_keys(win);
+
+ if (grab) {
+ p = curpos ? curpos->first_child : keyboard_firstnode;
+ while (p) {
+ grab_key(p->key, p->state, win, GrabModeAsync);
+ p = p->next_sibling;
+ }
+ if (curpos)
+ grab_key(config_keyboard_reset_keycode,
+ config_keyboard_reset_state,
+ win, GrabModeAsync);
+ }
+}
-typedef struct KeyboardData {
- PyObject_HEAD
- PyObject *keychain;
- guint state;
- guint keycode;
- gboolean press;
-} KeyboardData;
+void keyboard_grab_for_client(ObClient *c, gboolean grab)
+{
+ grab_for_window(c->window, grab);
+}
-staticforward PyTypeObject KeyboardDataType;
+static void grab_keys(gboolean grab)
+{
+ GList *it;
-/***************************************************************************
-
- Type methods/struct
-
- ***************************************************************************/
+ grab_for_window(screen_support_win, grab);
+ for (it = client_list; it; it = g_list_next(it))
+ grab_for_window(((ObClient*)it->data)->window, grab);
+}
-static PyObject *keybdata_new(PyObject *keychain, guint state,
- guint keycode, gboolean press)
+static gboolean chain_timeout(gpointer data)
{
- KeyboardData *data = PyObject_New(KeyboardData, &KeyboardDataType);
- data->keychain = keychain;
- Py_INCREF(keychain);
- data->state = state;
- data->keycode = keycode;
- data->press = press;
- return (PyObject*) data;
+ keyboard_reset_chains();
+
+ return FALSE; /* don't repeat */
}
-static void keybdata_dealloc(KeyboardData *self)
+void keyboard_reset_chains()
{
- Py_DECREF(self->keychain);
- PyObject_Del((PyObject*)self);
+ ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
+
+ if (curpos) {
+ grab_keys(FALSE);
+ curpos = NULL;
+ grab_keys(TRUE);
+ }
}
-static PyObject *keybdata_getattr(KeyboardData *self, char *name)
+void keyboard_unbind_all()
{
- 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;
+ tree_destroy(keyboard_firstnode);
+ keyboard_firstnode = NULL;
+ grab_keys(FALSE);
+ curpos = 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 */
-};
-
-/***************************************************************************/
-
-
-
-static gboolean grab_keyboard(gboolean grab)
+gboolean keyboard_bind(GList *keylist, ObAction *action)
{
- gboolean ret = TRUE;
+ KeyBindingTree *tree, *t;
+ gboolean conflict;
+ gboolean mods = TRUE;
+
+ g_assert(keylist != NULL);
+ g_assert(action != NULL);
+
+ if (!(tree = tree_build(keylist)))
+ return FALSE;
+
+ if ((t = tree_find(tree, &conflict)) != NULL) {
+ /* already bound to something, use the existing tree */
+ tree_destroy(tree);
+ tree = NULL;
+ } else
+ t = tree;
+
+ if (conflict) {
+ g_warning("conflict with binding");
+ tree_destroy(tree);
+ return FALSE;
+ }
- g_message("grab_keyboard(%s). grabbed: %d", (grab?"True":"False"),grabbed);
+ /* find if every key in this chain has modifiers, and also find the
+ bottom node of the tree */
+ while (t->first_child) {
+ if (!t->state)
+ mods = FALSE;
+ t = t->first_child;
+ }
- user_grabbed = grab;
- if (!grabbed) {
- if (grab)
- ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync,
- GrabModeAsync, CurrentTime) == GrabSuccess;
- else
- XUngrabKeyboard(ob_display, CurrentTime);
+ /* when there are no modifiers in the binding, then the action cannot
+ be interactive */
+ if (!mods && action->data.any.interactive) {
+ action->data.any.interactive = FALSE;
+ action->data.inter.final = TRUE;
}
- return ret;
+
+ /* set the action */
+ t->actions = g_slist_append(t->actions, action);
+ /* assimilate this built tree into the main tree. assimilation
+ destroys/uses the tree */
+ if (tree) tree_assimilate(tree);
+
+ return TRUE;
}
-/***************************************************************************
-
- Define the type 'Keyboard'
+gboolean keyboard_interactive_grab(guint state, ObClient *client,
+ ObAction *action)
+{
+ ObInteractiveState *s;
- ***************************************************************************/
+ g_assert(action->data.any.interactive);
-#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; \
- } \
-}
+ if (!interactive_states) {
+ if (!grab_keyboard(TRUE))
+ return FALSE;
+ if (!grab_pointer(TRUE, OB_CURSOR_NONE)) {
+ grab_keyboard(FALSE);
+ return FALSE;
+ }
+ }
-typedef struct Keyboard {
- PyObject_HEAD
-} Keyboard;
+ s = g_new(ObInteractiveState, 1);
-staticforward PyTypeObject KeyboardType;
+ s->state = state;
+ s->client = client;
+ s->actions = g_slist_append(NULL, action);
+ interactive_states = g_slist_append(interactive_states, s);
-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;
+ return TRUE;
}
-static PyObject *keyb_grab(Keyboard *self, PyObject *args)
+void keyboard_interactive_end(ObInteractiveState *s,
+ guint state, gboolean cancel)
{
- 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;
+ action_run_interactive(s->actions, s->client, state, cancel, TRUE);
+
+ g_slist_free(s->actions);
+ g_free(s);
+
+ interactive_states = g_slist_remove(interactive_states, s);
+
+ if (!interactive_states) {
+ grab_keyboard(FALSE);
+ grab_pointer(FALSE, OB_CURSOR_NONE);
+ keyboard_reset_chains();
}
- grab_func = func;
- Py_INCREF(grab_func);
- Py_INCREF(Py_None);
- return Py_None;
}
-static PyObject *keyb_ungrab(Keyboard *self, PyObject *args)
+void keyboard_interactive_end_client(ObClient *client, gpointer data)
{
- 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;
+ GSList *it, *next;
+
+ for (it = interactive_states; it; it = next) {
+ ObInteractiveState *s = it->data;
+
+ next = g_slist_next(it);
+
+ if (s->client == client)
+ s->client = NULL;
+ }
}
-#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)
+gboolean keyboard_process_interactive_grab(const XEvent *e, ObClient **client)
{
- PyObject_Del(self);
+ GSList *it, *next;
+ gboolean handled = FALSE;
+ gboolean done = FALSE;
+ gboolean cancel = FALSE;
+
+ for (it = interactive_states; it; it = next) {
+ ObInteractiveState *s = it->data;
+
+ next = g_slist_next(it);
+
+ if ((e->type == KeyRelease &&
+ !(s->state & e->xkey.state)))
+ done = TRUE;
+ else if (e->type == KeyPress) {
+ /*if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
+ done = TRUE;
+ else */if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
+ cancel = done = TRUE;
+ }
+ if (done) {
+ keyboard_interactive_end(s, e->xkey.state, cancel);
+
+ handled = TRUE;
+ } else
+ *client = s->client;
+ }
+
+ return handled;
}
-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()
+void keyboard_event(ObClient *client, const XEvent *e)
{
- PyObject *input, *inputdict, *ptr;
- gboolean b;
+ KeyBindingTree *p;
- curpos = firstnode = NULL;
- grabbed = user_grabbed = FALSE;
+ g_assert(e->type == KeyPress);
- b = translate("C-G", &reset_state, &reset_key);
- g_assert(b);
+ if (e->xkey.keycode == config_keyboard_reset_keycode &&
+ e->xkey.state == config_keyboard_reset_state)
+ {
+ keyboard_reset_chains();
+ return;
+ }
- KeyboardType.ob_type = &PyType_Type;
- KeyboardType.tp_methods = KeyboardMethods;
- PyType_Ready(&KeyboardType);
- PyType_Ready(&KeyboardDataType);
+ if (curpos == NULL)
+ p = keyboard_firstnode;
+ else
+ p = curpos->first_child;
+ while (p) {
+ if (p->key == e->xkey.keycode &&
+ p->state == e->xkey.state)
+ {
+ if (p->first_child != NULL) { /* part of a chain */
+ ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
+ /* 5 second timeout for chains */
+ ob_main_loop_timeout_add(ob_main_loop, 5 * G_USEC_PER_SEC,
+ chain_timeout, NULL, NULL);
+ grab_keys(FALSE);
+ curpos = p;
+ grab_keys(TRUE);
+ } else {
+
+ keyboard_reset_chains();
+
+ action_run_key(p->actions, client, e->xkey.state,
+ e->xkey.x_root, e->xkey.y_root);
+ }
+ break;
+ }
+ p = p->next_sibling;
+ }
+}
- /* get the input module/dict */
- input = PyImport_ImportModule("input"); /* new */
- g_assert(input != NULL);
- inputdict = PyModule_GetDict(input); /* borrowed */
- g_assert(inputdict != NULL);
+gboolean keyboard_interactively_grabbed()
+{
+ return !!interactive_states;
+}
- /* add a Keyboard instance to the input module */
- ptr = (PyObject*) PyObject_New(Keyboard, &KeyboardType);
- PyDict_SetItemString(inputdict, "Keyboard", ptr);
- Py_DECREF(ptr);
+void keyboard_startup(gboolean reconfig)
+{
+ grab_keys(TRUE);
- Py_DECREF(input);
+ if (!reconfig)
+ client_add_destructor(keyboard_interactive_end_client, NULL);
}
-void keyboard_shutdown()
+void keyboard_shutdown(gboolean reconfig)
{
- if (grabbed || user_grabbed) {
- grabbed = FALSE;
- grab_keyboard(FALSE);
- }
- grab_keys(FALSE);
- destroytree(firstnode);
- firstnode = NULL;
+ GSList *it;
+
+ if (!reconfig)
+ client_remove_destructor(keyboard_interactive_end_client);
+
+ for (it = interactive_states; it; it = g_slist_next(it))
+ g_free(it->data);
+ g_slist_free(interactive_states);
+ interactive_states = NULL;
+
+ ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
+
+ keyboard_unbind_all();
}