354 lines
7.8 KiB
C
354 lines
7.8 KiB
C
#include "focus.h"
|
|
#include "openbox.h"
|
|
#include "hooks.h"
|
|
#include "kbind.h"
|
|
|
|
#include <glib.h>
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
|
|
typedef struct KeyBindingTree {
|
|
guint state;
|
|
guint key;
|
|
GList *keylist;
|
|
|
|
/* 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;
|
|
|
|
guint kbind_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 = kbind_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);
|
|
}
|
|
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;
|
|
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 == a->key))
|
|
last->next_sibling = b;
|
|
else {
|
|
last->first_child = b->first_child;
|
|
g_free(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void reset_chains()
|
|
{
|
|
/* XXX kill timer */
|
|
curpos = NULL;
|
|
if (grabbed) {
|
|
grabbed = FALSE;
|
|
g_message("reset chains. user: %d", user_grabbed);
|
|
if (!user_grabbed)
|
|
XUngrabKeyboard(ob_display, CurrentTime);
|
|
}
|
|
}
|
|
|
|
void kbind_fire(guint state, guint key, gboolean press)
|
|
{
|
|
EventData *data;
|
|
struct Client *c = focus_client;
|
|
GQuark context = c != NULL ? g_quark_try_string("client")
|
|
: g_quark_try_string("root");
|
|
|
|
if (user_grabbed) {
|
|
data = eventdata_new_key(press ? Key_Press : Key_Release,
|
|
context, c, state, key, NULL);
|
|
g_assert(data != NULL);
|
|
hooks_fire_keyboard(data);
|
|
eventdata_free(data);
|
|
}
|
|
|
|
if (key == reset_key && 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 == key && p->state == 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 {
|
|
data = eventdata_new_key(press ? Key_Press : Key_Release,
|
|
context, c, state, key,
|
|
p->keylist);
|
|
g_assert(data != NULL);
|
|
hooks_fire(data);
|
|
eventdata_free(data);
|
|
|
|
XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
|
|
reset_chains();
|
|
}
|
|
break;
|
|
}
|
|
p = p->next_sibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
gboolean kbind_add(GList *keylist)
|
|
{
|
|
KeyBindingTree *tree, *t;
|
|
gboolean conflict;
|
|
|
|
if (!(tree = buildtree(keylist)))
|
|
return FALSE; /* invalid binding requested */
|
|
|
|
t = find(tree, &conflict);
|
|
if (conflict) {
|
|
/* conflicts with another binding */
|
|
destroytree(tree);
|
|
return FALSE;
|
|
}
|
|
|
|
if (t != NULL) {
|
|
/* already bound to something */
|
|
destroytree(tree);
|
|
} else {
|
|
/* grab the server here to make sure no key pressed go missed */
|
|
XGrabServer(ob_display);
|
|
XSync(ob_display, FALSE);
|
|
|
|
grab_keys(FALSE);
|
|
|
|
/* assimilate this built tree into the main tree */
|
|
assimilate(tree); // assimilation destroys/uses the tree
|
|
|
|
grab_keys(TRUE);
|
|
|
|
XUngrabServer(ob_display);
|
|
XFlush(ob_display);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void kbind_clearall()
|
|
{
|
|
grab_keys(FALSE);
|
|
destroytree(firstnode);
|
|
firstnode = NULL;
|
|
grab_keys(TRUE);
|
|
}
|
|
|
|
void kbind_startup()
|
|
{
|
|
gboolean b;
|
|
|
|
curpos = firstnode = NULL;
|
|
grabbed = user_grabbed = FALSE;
|
|
|
|
b = translate("C-G", &reset_state, &reset_key);
|
|
g_assert(b);
|
|
}
|
|
|
|
void kbind_shutdown()
|
|
{
|
|
if (grabbed || user_grabbed) {
|
|
grabbed = FALSE;
|
|
kbind_grab_keyboard(FALSE);
|
|
}
|
|
grab_keys(FALSE);
|
|
destroytree(firstnode);
|
|
firstnode = NULL;
|
|
}
|
|
|
|
gboolean kbind_grab_keyboard(gboolean grab)
|
|
{
|
|
gboolean ret = TRUE;
|
|
|
|
if (!grab)
|
|
g_message("grab_keyboard(false). grabbed: %d", 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;
|
|
}
|