draw the keychain popup only when necessary
[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 "translate.h"
35 #include "moveresize.h"
36 #include "popup.h"
37 #include "gettext.h"
38
39 #include <glib.h>
40
41 typedef struct {
42     gboolean active;
43     guint state;
44     ObClient *client;
45     ObAction *action;
46 } ObInteractiveState;
47
48 KeyBindingTree *keyboard_firstnode = NULL;
49 static ObPopup *popup = NULL;
50 static ObInteractiveState istate;
51 static KeyBindingTree *curpos;
52
53 static void grab_keys(gboolean grab)
54 {
55     KeyBindingTree *p;
56
57     ungrab_all_keys(RootWindow(ob_display, ob_screen));
58
59     if (grab) {
60         p = curpos ? curpos->first_child : keyboard_firstnode;
61         while (p) {
62             grab_key(p->key, p->state, RootWindow(ob_display, ob_screen),
63                      GrabModeAsync);
64             p = p->next_sibling;
65         }
66         if (curpos)
67             grab_key(config_keyboard_reset_keycode,
68                      config_keyboard_reset_state,
69                      RootWindow(ob_display, ob_screen), GrabModeAsync);
70     }
71 }
72
73 static gboolean chain_timeout(gpointer data)
74 {
75     keyboard_reset_chains(0);
76     return FALSE; /* don't repeat */
77 }
78
79 static void set_curpos(KeyBindingTree *newpos)
80 {
81     if (curpos == newpos) return;
82
83     grab_keys(FALSE);
84     curpos = newpos;
85     grab_keys(TRUE);
86
87     if (curpos != NULL) {
88         gchar *text = NULL;
89         GList *it;
90
91         for (it = curpos->keylist; it; it = g_list_next(it)) {
92             gchar *oldtext = text;
93             if (text == NULL)
94                 text = g_strdup(it->data);
95             else
96                 text = g_strconcat(text, " - ", it->data, NULL);
97             g_free(oldtext);
98         }
99
100         popup_position(popup, NorthWestGravity, 10, 10);
101         /* 1 second delay for the popup to show */
102         popup_delay_show(popup, G_USEC_PER_SEC, text);
103         g_free(text);
104     } else {
105         popup_hide(popup);
106     }
107 }
108
109 void keyboard_reset_chains(gint break_chroots)
110 {
111     KeyBindingTree *p;
112
113     for (p = curpos; p; p = p->parent) {
114         if (p->chroot) {
115             if (break_chroots == 0) break; /* stop here */
116             if (break_chroots > 0)
117                 --break_chroots;
118         }
119     }
120     set_curpos(p);
121 }
122
123 void keyboard_unbind_all()
124 {
125     tree_destroy(keyboard_firstnode);
126     keyboard_firstnode = NULL;
127 }
128
129 void keyboard_chroot(GList *keylist)
130 {
131     /* try do it in the existing tree. if we can't that means it is an empty
132        chroot binding. so add it to the tree then. */
133     if (!tree_chroot(keyboard_firstnode, keylist)) {
134         KeyBindingTree *tree;
135         if (!(tree = tree_build(keylist)))
136             return;
137         tree_chroot(tree, keylist);
138         tree_assimilate(tree);
139     }
140 }
141
142 gboolean keyboard_bind(GList *keylist, ObAction *action)
143 {
144     KeyBindingTree *tree, *t;
145     gboolean conflict;
146     gboolean mods = TRUE;
147
148     g_assert(keylist != NULL);
149     g_assert(action != NULL);
150
151     if (!(tree = tree_build(keylist)))
152         return FALSE;
153
154     if ((t = tree_find(tree, &conflict)) != NULL) {
155         /* already bound to something, use the existing tree */
156         tree_destroy(tree);
157         tree = NULL;
158     } else
159         t = tree;
160
161     if (conflict) {
162         g_message(_("Conflict with key binding in config file"));
163         tree_destroy(tree);
164         return FALSE;
165     }
166
167     /* find if every key in this chain has modifiers, and also find the
168        bottom node of the tree */
169     while (t->first_child) {
170         if (!t->state)
171             mods = FALSE;
172         t = t->first_child;
173     }
174
175     /* when there are no modifiers in the binding, then the action cannot
176        be interactive */
177     if (!mods && action->data.any.interactive) {
178         action->data.any.interactive = FALSE;
179         action->data.inter.final = TRUE;
180     }
181
182     /* set the action */
183     t->actions = g_slist_append(t->actions, action);
184     /* assimilate this built tree into the main tree. assimilation
185        destroys/uses the tree */
186     if (tree) tree_assimilate(tree);
187
188     return TRUE;
189 }
190
191 static void keyboard_interactive_end(guint state, gboolean cancel, Time time,
192                                      gboolean ungrab)
193 {
194     GSList *alist;
195
196     g_assert(istate.active);
197
198     /* ungrab first so they won't be NotifyWhileGrabbed */
199     if (ungrab)
200         ungrab_keyboard();
201
202     /* set this before running the actions so they know the keyboard is not
203        grabbed */
204     istate.active = FALSE;
205
206     alist = g_slist_append(NULL, istate.action);
207     action_run_interactive(alist, istate.client, state, time, cancel, TRUE);
208     g_slist_free(alist);
209 }
210
211 static void keyboard_interactive_end_client(ObClient *client, gpointer data)
212 {
213     if (istate.active && istate.client == client)
214         istate.client = NULL;
215 }
216
217
218 void keyboard_interactive_cancel()
219 {
220     keyboard_interactive_end(0, TRUE, event_curtime, TRUE);
221 }
222
223 gboolean keyboard_interactive_grab(guint state, ObClient *client,
224                                    ObAction *action)
225 {
226     g_assert(action->data.any.interactive);
227
228     if (!istate.active) {
229         if (!grab_keyboard())
230             return FALSE;
231     } else if (action->func != istate.action->func) {
232         keyboard_interactive_end(state, TRUE, action->data.any.time, FALSE);
233     }
234
235     istate.active = TRUE;
236     istate.state = state;
237     istate.client = client;
238     istate.action = action;
239
240     return TRUE;
241 }
242
243 gboolean keyboard_process_interactive_grab(const XEvent *e, ObClient **client)
244 {
245     gboolean handled = FALSE;
246     gboolean done = FALSE;
247     gboolean cancel = FALSE;
248
249     if (istate.active) {
250         if ((e->type == KeyRelease && !(istate.state & e->xkey.state))) {
251             done = TRUE;
252             handled = TRUE;
253         } else if (e->type == KeyPress) {
254             /*if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
255               done = TRUE;
256               else */if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE)) {
257                   cancel = done = TRUE;
258                   handled = TRUE;
259               }
260         } else if (e->type == ButtonPress) {
261             cancel = TRUE;
262             done = TRUE;
263             handled = FALSE;
264         }
265
266         if (done)
267             keyboard_interactive_end(e->xkey.state, cancel, e->xkey.time,TRUE);
268
269         if (handled)
270             *client = istate.client;
271     }
272
273     return handled;
274 }
275
276 void keyboard_event(ObClient *client, const XEvent *e)
277 {
278     KeyBindingTree *p;
279
280     g_assert(e->type == KeyPress);
281
282     if (e->xkey.keycode == config_keyboard_reset_keycode &&
283         e->xkey.state == config_keyboard_reset_state)
284     {
285         ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
286         keyboard_reset_chains(-1);
287         return;
288     }
289
290     if (curpos == NULL)
291         p = keyboard_firstnode;
292     else
293         p = curpos->first_child;
294     while (p) {
295         if (p->key == e->xkey.keycode &&
296             p->state == e->xkey.state)
297         {
298             /* if we hit a key binding, then close any open menus and run it */
299             if (menu_frame_visible)
300                 menu_frame_hide_all();
301
302             if (p->first_child != NULL) { /* part of a chain */
303                 ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
304                 /* 3 second timeout for chains */
305                 ob_main_loop_timeout_add(ob_main_loop, 3 * G_USEC_PER_SEC,
306                                          chain_timeout, NULL,
307                                          g_direct_equal, NULL);
308                 set_curpos(p);
309             } else if (p->chroot)         /* an empty chroot */
310                 set_curpos(p);
311             else {
312                 keyboard_reset_chains(0);
313
314                 /* If we don't have the keyboard grabbed, then ungrab it with
315                    XUngrabKeyboard, so that there is not a passive grab left
316                    on from the KeyPress. If the grab is left on, and focus
317                    moves during that time, it will be NotifyWhileGrabbed, and
318                    applications like to ignore those! */
319                 if (!keyboard_interactively_grabbed())
320                     XUngrabKeyboard(ob_display, e->xkey.time);
321
322                 action_run_key(p->actions, client, e->xkey.state,
323                                e->xkey.x_root, e->xkey.y_root,
324                                e->xkey.time);
325             }
326             break;
327         }
328         p = p->next_sibling;
329     }
330 }
331
332 gboolean keyboard_interactively_grabbed()
333 {
334     return istate.active;
335 }
336
337 void keyboard_startup(gboolean reconfig)
338 {
339     grab_keys(TRUE);
340     popup = popup_new(FALSE);
341
342     if (!reconfig)
343         client_add_destroy_notify(keyboard_interactive_end_client, NULL);
344 }
345
346 void keyboard_shutdown(gboolean reconfig)
347 {
348     if (!reconfig)
349         client_remove_destroy_notify(keyboard_interactive_end_client);
350
351     if (istate.active)
352         keyboard_interactive_cancel();
353
354     ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
355
356     keyboard_unbind_all();
357     set_curpos(NULL);
358
359     popup_free(popup);
360     popup = NULL;
361 }
362