make either key work for people who have super_l and super_r (or whatever ones) bound...
[mikachu/openbox.git] / openbox / keyboard.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    keyboard.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "mainloop.h"
21 #include "focus.h"
22 #include "screen.h"
23 #include "frame.h"
24 #include "openbox.h"
25 #include "event.h"
26 #include "grab.h"
27 #include "client.h"
28 #include "action.h"
29 #include "prop.h"
30 #include "menuframe.h"
31 #include "config.h"
32 #include "keytree.h"
33 #include "keyboard.h"
34 #include "moveresize.h"
35 #include "popup.h"
36 #include "gettext.h"
37
38 #include <glib.h>
39
40 typedef struct {
41     gboolean active;
42     guint state;
43     ObClient *client;
44     ObAction *action;
45 } ObInteractiveState;
46
47 KeyBindingTree *keyboard_firstnode = NULL;
48 static ObPopup *popup = NULL;
49 static ObInteractiveState istate;
50 static KeyBindingTree *curpos;
51
52 static void grab_keys(gboolean grab)
53 {
54     KeyBindingTree *p;
55
56     ungrab_all_keys(RootWindow(ob_display, ob_screen));
57
58     if (grab) {
59         p = curpos ? curpos->first_child : keyboard_firstnode;
60         while (p) {
61             grab_key(p->key, p->state, RootWindow(ob_display, ob_screen),
62                      GrabModeAsync);
63             p = p->next_sibling;
64         }
65         if (curpos)
66             grab_key(config_keyboard_reset_keycode,
67                      config_keyboard_reset_state,
68                      RootWindow(ob_display, ob_screen), GrabModeAsync);
69     }
70 }
71
72 static gboolean chain_timeout(gpointer data)
73 {
74     keyboard_reset_chains(0);
75     return FALSE; /* don't repeat */
76 }
77
78 static void set_curpos(KeyBindingTree *newpos)
79 {
80     if (curpos != newpos) {
81         grab_keys(FALSE);
82         curpos = newpos;
83         grab_keys(TRUE);
84     }
85
86     if (curpos != NULL) {
87         gchar *text = NULL;
88         GList *it;
89
90         for (it = curpos->keylist; it; it = g_list_next(it)) {
91             gchar *oldtext = text;
92             if (text == NULL)
93                 text = g_strdup(it->data);
94             else
95                 text = g_strconcat(text, " - ", it->data, NULL);
96             g_free(oldtext);
97         }
98
99         popup_position(popup, NorthWestGravity, 10, 10);
100         /* 1 second delay for the popup to show */
101         popup_delay_show(popup, G_USEC_PER_SEC, text);
102         g_free(text);
103     } else {
104         popup_hide(popup);
105     }
106 }
107
108 void keyboard_reset_chains(gint break_chroots)
109 {
110     KeyBindingTree *p;
111
112     for (p = curpos; p; p = p->parent) {
113         if (p->chroot) {
114             if (break_chroots == 0) break; /* stop here */
115             if (break_chroots > 0)
116                 --break_chroots;
117         }
118     }
119     set_curpos(p);
120 }
121
122 void keyboard_unbind_all()
123 {
124     tree_destroy(keyboard_firstnode);
125     keyboard_firstnode = NULL;
126 }
127
128 void keyboard_chroot(GList *keylist)
129 {
130     /* try do it in the existing tree. if we can't that means it is an empty
131        chroot binding. so add it to the tree then. */
132     if (!tree_chroot(keyboard_firstnode, keylist)) {
133         KeyBindingTree *tree;
134         if (!(tree = tree_build(keylist)))
135             return;
136         tree_chroot(tree, keylist);
137         tree_assimilate(tree);
138     }
139 }
140
141 gboolean keyboard_bind(GList *keylist, ObAction *action)
142 {
143     KeyBindingTree *tree, *t;
144     gboolean conflict;
145     gboolean mods = TRUE;
146
147     g_assert(keylist != NULL);
148     g_assert(action != NULL);
149
150     if (!(tree = tree_build(keylist)))
151         return FALSE;
152
153     if ((t = tree_find(tree, &conflict)) != NULL) {
154         /* already bound to something, use the existing tree */
155         tree_destroy(tree);
156         tree = NULL;
157     } else
158         t = tree;
159
160     if (conflict) {
161         g_message(_("Conflict with key binding in config file"));
162         tree_destroy(tree);
163         return FALSE;
164     }
165
166     /* find if every key in this chain has modifiers, and also find the
167        bottom node of the tree */
168     while (t->first_child) {
169         if (!t->state)
170             mods = FALSE;
171         t = t->first_child;
172     }
173
174     /* when there are no modifiers in the binding, then the action cannot
175        be interactive */
176     if (!mods && action->data.any.interactive) {
177         action->data.any.interactive = FALSE;
178         action->data.inter.final = TRUE;
179     }
180
181     /* set the action */
182     t->actions = g_slist_append(t->actions, action);
183     /* assimilate this built tree into the main tree. assimilation
184        destroys/uses the tree */
185     if (tree) tree_assimilate(tree);
186
187     return TRUE;
188 }
189
190 static void keyboard_interactive_end(guint state, gboolean cancel, Time time,
191                                      gboolean ungrab)
192 {
193     GSList *alist;
194
195     g_assert(istate.active);
196
197     /* ungrab first so they won't be NotifyWhileGrabbed */
198     if (ungrab)
199         ungrab_keyboard();
200
201     /* set this before running the actions so they know the keyboard is not
202        grabbed */
203     istate.active = FALSE;
204
205     alist = g_slist_append(NULL, istate.action);
206     action_run_interactive(alist, istate.client, state, time, cancel, TRUE);
207     g_slist_free(alist);
208 }
209
210 static void keyboard_interactive_end_client(ObClient *client, gpointer data)
211 {
212     if (istate.active && istate.client == client)
213         istate.client = NULL;
214 }
215
216
217 void keyboard_interactive_cancel()
218 {
219     keyboard_interactive_end(0, TRUE, event_curtime, TRUE);
220 }
221
222 gboolean keyboard_interactive_grab(guint state, ObClient *client,
223                                    ObAction *action)
224 {
225     g_assert(action->data.any.interactive);
226
227     if (!istate.active) {
228         if (!grab_keyboard())
229             return FALSE;
230     } else if (action->func != istate.action->func) {
231         keyboard_interactive_end(state, TRUE, action->data.any.time, FALSE);
232     }
233
234     istate.active = TRUE;
235     istate.state = state;
236     istate.client = client;
237     istate.action = action;
238
239     return TRUE;
240 }
241
242 gboolean keyboard_process_interactive_grab(const XEvent *e, ObClient **client)
243 {
244     gboolean handled = FALSE;
245     gboolean done = FALSE;
246     gboolean cancel = FALSE;
247
248     if (istate.active) {
249         if ((e->type == KeyRelease && !(istate.state & e->xkey.state))) {
250             done = TRUE;
251             handled = TRUE;
252         } else if (e->type == KeyPress) {
253             /*if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
254               done = TRUE;
255               else */if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
256                   cancel = done = TRUE;
257                   handled = TRUE;
258               }
259         } else if (e->type == ButtonPress) {
260             cancel = TRUE;
261             done = TRUE;
262             handled = FALSE;
263         }
264
265         if (done)
266             keyboard_interactive_end(e->xkey.state, cancel, e->xkey.time,TRUE);
267
268         if (handled)
269             *client = istate.client;
270     }
271
272     return handled;
273 }
274
275 void keyboard_event(ObClient *client, const XEvent *e)
276 {
277     KeyBindingTree *p;
278
279     g_assert(e->type == KeyPress);
280
281     if (e->xkey.keycode == config_keyboard_reset_keycode &&
282         e->xkey.state == config_keyboard_reset_state)
283     {
284         ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
285         keyboard_reset_chains(-1);
286         return;
287     }
288
289     if (curpos == NULL)
290         p = keyboard_firstnode;
291     else
292         p = curpos->first_child;
293     while (p) {
294         if (p->key == e->xkey.keycode &&
295             /* tricksy.
296                if you have Super_L and Super_R bound to different modXmasks,
297                this makes either mod mask count as Super
298
299                e.g. Super_L is mod1 and Super_R is mod2, so..
300                p->state = 00011
301                key state is just Super_L so 00001
302                00011 | 00001 == 00011 == p->state
303             */
304             (p->state | e->xkey.state) == p->state)
305         {
306             /* if we hit a key binding, then close any open menus and run it */
307             if (menu_frame_visible)
308                 menu_frame_hide_all();
309
310             if (p->first_child != NULL) { /* part of a chain */
311                 ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
312                 /* 3 second timeout for chains */
313                 ob_main_loop_timeout_add(ob_main_loop, 3 * G_USEC_PER_SEC,
314                                          chain_timeout, NULL,
315                                          g_direct_equal, NULL);
316                 set_curpos(p);
317             } else if (p->chroot)         /* an empty chroot */
318                 set_curpos(p);
319             else {
320                 keyboard_reset_chains(0);
321
322                 /* If we don't have the keyboard grabbed, then ungrab it with
323                    XUngrabKeyboard, so that there is not a passive grab left
324                    on from the KeyPress. If the grab is left on, and focus
325                    moves during that time, it will be NotifyWhileGrabbed, and
326                    applications like to ignore those! */
327                 if (!keyboard_interactively_grabbed())
328                     XUngrabKeyboard(ob_display, e->xkey.time);
329
330                 action_run_key(p->actions, client, e->xkey.state,
331                                e->xkey.x_root, e->xkey.y_root,
332                                e->xkey.time);
333             }
334             break;
335         }
336         p = p->next_sibling;
337     }
338 }
339
340 gboolean keyboard_interactively_grabbed()
341 {
342     return istate.active;
343 }
344
345 void keyboard_startup(gboolean reconfig)
346 {
347     grab_keys(TRUE);
348     popup = popup_new(FALSE);
349
350     if (!reconfig)
351         client_add_destroy_notify(keyboard_interactive_end_client, NULL);
352 }
353
354 void keyboard_shutdown(gboolean reconfig)
355 {
356     if (!reconfig)
357         client_remove_destroy_notify(keyboard_interactive_end_client);
358
359     if (istate.active)
360         keyboard_interactive_cancel();
361
362     ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
363
364     keyboard_unbind_all();
365     set_curpos(NULL);
366
367     popup_free(popup);
368     popup = NULL;
369 }
370