merge the C branch into HEAD
[mikachu/openbox.git] / c / kbind.c
1 #include "focus.h"
2 #include "openbox.h"
3 #include "hooks.h"
4 #include "kbind.h"
5
6 #include <glib.h>
7 #ifdef HAVE_STRING_H
8 #  include <string.h>
9 #endif
10
11 typedef struct KeyBindingTree {
12     guint state;
13     guint key;
14     GList *keylist;
15
16     /* the next binding in the tree at the same level */
17     struct KeyBindingTree *next_sibling; 
18     /* the first child of this binding (next binding in a chained sequence).*/
19     struct KeyBindingTree *first_child;
20 } KeyBindingTree;
21
22
23 static KeyBindingTree *firstnode, *curpos;
24 static guint reset_key, reset_state;
25 static gboolean grabbed, user_grabbed;
26
27 guint kbind_translate_modifier(char *str)
28 {
29     if (!strcmp("Mod1", str)) return Mod1Mask;
30     else if (!strcmp("Mod2", str)) return Mod2Mask;
31     else if (!strcmp("Mod3", str)) return Mod3Mask;
32     else if (!strcmp("Mod4", str)) return Mod4Mask;
33     else if (!strcmp("Mod5", str)) return Mod5Mask;
34     else if (!strcmp("C", str)) return ControlMask;
35     else if (!strcmp("S", str)) return ShiftMask;
36     g_warning("Invalid modifier '%s' in binding.", str);
37     return 0;
38 }
39
40 static gboolean translate(char *str, guint *state, guint *keycode)
41 {
42     char **parsed;
43     char *l;
44     int i;
45     gboolean ret = FALSE;
46     KeySym sym;
47
48     parsed = g_strsplit(str, "-", -1);
49     
50     /* first, find the key (last token) */
51     l = NULL;
52     for (i = 0; parsed[i] != NULL; ++i)
53         l = parsed[i];
54     if (l == NULL)
55         goto translation_fail;
56
57     /* figure out the mod mask */
58     *state = 0;
59     for (i = 0; parsed[i] != l; ++i) {
60         guint m = kbind_translate_modifier(parsed[i]);
61         if (!m) goto translation_fail;
62         *state |= m;
63     }
64
65     /* figure out the keycode */
66     sym = XStringToKeysym(l);
67     if (sym == NoSymbol) {
68         g_warning("Invalid key name '%s' in key binding.", l);
69         goto translation_fail;
70     }
71     *keycode = XKeysymToKeycode(ob_display, sym);
72     if (!keycode) {
73         g_warning("Key '%s' does not exist on the display.", l); 
74         goto translation_fail;
75     }
76
77     ret = TRUE;
78
79 translation_fail:
80     g_strfreev(parsed);
81     return ret;
82 }
83
84 static void destroytree(KeyBindingTree *tree)
85 {
86     KeyBindingTree *c;
87
88     while (tree) {
89         destroytree(tree->next_sibling);
90         c = tree->first_child;
91         if (c == NULL) {
92             GList *it;
93             for (it = tree->keylist; it != NULL; it = it->next)
94                 g_free(it->data);
95             g_list_free(tree->keylist);
96         }
97         g_free(tree);
98         tree = c;
99     }
100 }
101
102 static KeyBindingTree *buildtree(GList *keylist)
103 {
104     GList *it;
105     KeyBindingTree *ret = NULL, *p;
106
107     if (g_list_length(keylist) <= 0)
108         return NULL; /* nothing in the list.. */
109
110     for (it = g_list_last(keylist); it != NULL; it = it->prev) {
111         p = ret;
112         ret = g_new(KeyBindingTree, 1);
113         ret->next_sibling = NULL;
114         if (p == NULL) {
115             GList *it;
116
117             /* this is the first built node, the bottom node of the tree */
118             ret->keylist = g_list_copy(keylist); /* shallow copy */
119             for (it = ret->keylist; it != NULL; it = it->next) /* deep copy */
120                 it->data = g_strdup(it->data);
121         }
122         ret->first_child = p;
123         if (!translate(it->data, &ret->state, &ret->key)) {
124             destroytree(ret);
125             return NULL;
126         }
127     }
128     return ret;
129 }
130
131 static void assimilate(KeyBindingTree *node)
132 {
133     KeyBindingTree *a, *b, *tmp, *last;
134
135     if (firstnode == NULL) {
136         /* there are no nodes at this level yet */
137         firstnode = node;
138     } else {
139         a = firstnode;
140         last = a;
141         b = node;
142         while (a) {
143             last = a;
144             if (!(a->state == b->state && a->key == b->key)) {
145                 a = a->next_sibling;
146             } else {
147                 tmp = b;
148                 b = b->first_child;
149                 g_free(tmp);
150                 a = a->first_child;
151             }
152         }
153         if (!(last->state == b->state && last->key == a->key))
154             last->next_sibling = b;
155         else {
156             last->first_child = b->first_child;
157             g_free(b);
158         }
159     }
160 }
161
162 KeyBindingTree *find(KeyBindingTree *search, gboolean *conflict)
163 {
164     KeyBindingTree *a, *b;
165
166     *conflict = FALSE;
167
168     a = firstnode;
169     b = search;
170     while (a && b) {
171         if (!(a->state == b->state && a->key == b->key)) {
172             a = a->next_sibling;
173         } else {
174             if ((a->first_child == NULL) == (b->first_child == NULL)) {
175                 if (a->first_child == NULL) {
176                     /* found it! (return the actual node, not the search's) */
177                     return a;
178                 }
179             } else {
180                 *conflict = TRUE;
181                 return NULL; /* the chain status' don't match (conflict!) */
182             }
183             b = b->first_child;
184             a = a->first_child;
185         }
186     }
187     return NULL; // it just isn't in here
188 }
189
190 static void grab_keys(gboolean grab)
191 {
192     if (!grab) {
193         XUngrabKey(ob_display, AnyKey, AnyModifier, ob_root);
194     } else {
195         KeyBindingTree *p = firstnode;
196         while (p) {
197             XGrabKey(ob_display, p->key, p->state, ob_root, FALSE,
198                      GrabModeAsync, GrabModeSync);
199             p = p->next_sibling;
200         }
201     }
202 }
203
204 void reset_chains()
205 {
206     /* XXX kill timer */
207     curpos = NULL;
208     if (grabbed) {
209         grabbed = FALSE;
210         g_message("reset chains. user: %d", user_grabbed);
211         if (!user_grabbed)
212             XUngrabKeyboard(ob_display, CurrentTime);
213     }
214 }
215
216 void kbind_fire(guint state, guint key, gboolean press)
217 {
218     EventData *data;
219     struct Client *c = focus_client;
220     GQuark context = c != NULL ? g_quark_try_string("client")
221                                : g_quark_try_string("root");
222
223     if (user_grabbed) {
224         data = eventdata_new_key(press ? Key_Press : Key_Release,
225                                  context, c, state, key, NULL);
226         g_assert(data != NULL);
227         hooks_fire_keyboard(data);
228         eventdata_free(data);
229     }
230
231     if (key == reset_key && state == reset_state) {
232         reset_chains();
233         XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
234     } else {
235         KeyBindingTree *p;
236         if (curpos == NULL)
237             p = firstnode;
238         else
239             p = curpos->first_child;
240         while (p) {
241             if (p->key == key && p->state == state) {
242                 if (p->first_child != NULL) { /* part of a chain */
243                     /* XXX TIMER */
244                     if (!grabbed && !user_grabbed) {
245                         /*grab should never fail because we should have a sync
246                           grab at this point */
247                         XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, 
248                                       GrabModeSync, CurrentTime);
249                     }
250                     grabbed = TRUE;
251                     curpos = p;
252                     XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
253                 } else {
254                     data = eventdata_new_key(press ? Key_Press : Key_Release,
255                                              context, c, state, key,
256                                              p->keylist);
257                     g_assert(data != NULL);
258                     hooks_fire(data);
259                     eventdata_free(data);
260
261                     XAllowEvents(ob_display, AsyncKeyboard, CurrentTime);
262                     reset_chains();
263                 }
264                 break;
265             }
266             p = p->next_sibling;
267         }
268     }
269 }
270
271 gboolean kbind_add(GList *keylist)
272 {
273     KeyBindingTree *tree, *t;
274     gboolean conflict;
275
276     if (!(tree = buildtree(keylist)))
277         return FALSE; /* invalid binding requested */
278
279     t = find(tree, &conflict);
280     if (conflict) {
281         /* conflicts with another binding */
282         destroytree(tree);
283         return FALSE;
284     }
285
286     if (t != NULL) {
287         /* already bound to something */
288         destroytree(tree);
289     } else {
290         /* grab the server here to make sure no key pressed go missed */
291         XGrabServer(ob_display);
292         XSync(ob_display, FALSE);
293
294         grab_keys(FALSE);
295
296         /* assimilate this built tree into the main tree */
297         assimilate(tree); // assimilation destroys/uses the tree
298
299         grab_keys(TRUE); 
300
301         XUngrabServer(ob_display);
302         XFlush(ob_display);
303     }
304  
305     return TRUE;
306 }
307
308 void kbind_clearall()
309 {
310     grab_keys(FALSE);
311     destroytree(firstnode);
312     firstnode = NULL;
313     grab_keys(TRUE);
314 }
315
316 void kbind_startup()
317 {
318     gboolean b;
319
320     curpos = firstnode = NULL;
321     grabbed = user_grabbed = FALSE;
322
323     b = translate("C-G", &reset_state, &reset_key);
324     g_assert(b);
325 }
326
327 void kbind_shutdown()
328 {
329     if (grabbed || user_grabbed) {
330         grabbed = FALSE;
331         kbind_grab_keyboard(FALSE);
332     }
333     grab_keys(FALSE);
334     destroytree(firstnode);
335     firstnode = NULL;
336 }
337
338 gboolean kbind_grab_keyboard(gboolean grab)
339 {
340     gboolean ret = TRUE;
341
342     if (!grab)
343         g_message("grab_keyboard(false). grabbed: %d", grabbed);
344
345     user_grabbed = grab;
346     if (!grabbed) {
347         if (grab)
348             ret = XGrabKeyboard(ob_display, ob_root, 0, GrabModeAsync, 
349                                 GrabModeAsync, CurrentTime) == GrabSuccess;
350         else
351             XUngrabKeyboard(ob_display, CurrentTime);
352     }
353     return ret;
354 }