]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/keyboard.c
add the delay for killing keychains back (but it won't kill chroots)
[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 "config.h"
31 #include "keytree.h"
32 #include "keyboard.h"
33 #include "translate.h"
34 #include "moveresize.h"
35 #include "popup.h"
36 #include "gettext.h"
37
38 #include <glib.h>
39
40 typedef struct {
41     guint state;
42     ObClient *client;
43     GSList *actions;
44     ObFrameContext context;
45 } ObInteractiveState;
46
47 KeyBindingTree *keyboard_firstnode = NULL;
48 static ObPopup *popup = NULL;
49 static GSList *interactive_states;
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 gboolean popup_show_timeout(gpointer data)
79 {
80     gchar *text = data;
81     popup_show(popup, text);
82
83     return FALSE; /* don't repeat */
84 }
85
86 static void set_curpos(KeyBindingTree *newpos)
87 {
88     grab_keys(FALSE);
89     curpos = newpos;
90     grab_keys(TRUE);
91
92     if (curpos != NULL) {
93         gchar *text = NULL;
94         GList *it;
95
96         for (it = curpos->keylist; it; it = g_list_next(it)) {
97             gchar *oldtext = text;
98             if (text == NULL)
99                 text = g_strdup(it->data);
100             else
101                 text = g_strconcat(text, " - ", it->data, NULL);
102             g_free(oldtext);
103         }
104
105         popup_position(popup, NorthWestGravity, 10, 10);
106         if (popup->mapped) {
107             popup_show_timeout(text);
108             g_free(text);
109         } else {
110             ob_main_loop_timeout_remove(ob_main_loop, popup_show_timeout);
111             /* 1 second delay for the popup to show */
112             ob_main_loop_timeout_add(ob_main_loop, G_USEC_PER_SEC,
113                                      popup_show_timeout, text,
114                                      g_direct_equal, g_free);
115         }
116     } else {
117         popup_hide(popup);
118         ob_main_loop_timeout_remove(ob_main_loop, popup_show_timeout);
119     }
120 }
121
122 void keyboard_reset_chains(gint break_chroots)
123 {
124     KeyBindingTree *p;
125
126     for (p = curpos; p; p = p->parent) {
127         if (p->chroot) {
128             if (break_chroots == 0) break; /* stop here */
129             if (break_chroots > 0)
130                 --break_chroots;
131         }
132     }
133     set_curpos(p);
134 }
135
136 void keyboard_unbind_all()
137 {
138     tree_destroy(keyboard_firstnode);
139     keyboard_firstnode = NULL;
140 }
141
142 void keyboard_chroot(GList *keylist)
143 {
144     /* try do it in the existing tree. if we can't that means it is an empty
145        chroot binding. so add it to the tree then. */
146     if (!tree_chroot(keyboard_firstnode, keylist)) {
147         KeyBindingTree *tree;
148         if (!(tree = tree_build(keylist)))
149             return;
150         tree_chroot(tree, keylist);
151         tree_assimilate(tree);
152     }
153 }
154
155 gboolean keyboard_bind(GList *keylist, ObAction *action)
156 {
157     KeyBindingTree *tree, *t;
158     gboolean conflict;
159     gboolean mods = TRUE;
160
161     g_assert(keylist != NULL);
162     g_assert(action != NULL);
163
164     if (!(tree = tree_build(keylist)))
165         return FALSE;
166
167     if ((t = tree_find(tree, &conflict)) != NULL) {
168         /* already bound to something, use the existing tree */
169         tree_destroy(tree);
170         tree = NULL;
171     } else
172         t = tree;
173
174     if (conflict) {
175         g_message(_("Conflict with key binding in config file"));
176         tree_destroy(tree);
177         return FALSE;
178     }
179
180     /* find if every key in this chain has modifiers, and also find the
181        bottom node of the tree */
182     while (t->first_child) {
183         if (!t->state)
184             mods = FALSE;
185         t = t->first_child;
186     }
187
188     /* when there are no modifiers in the binding, then the action cannot
189        be interactive */
190     if (!mods && action->data.any.interactive) {
191         action->data.any.interactive = FALSE;
192         action->data.inter.final = TRUE;
193     }
194
195     /* set the action */
196     t->actions = g_slist_append(t->actions, action);
197     /* assimilate this built tree into the main tree. assimilation
198        destroys/uses the tree */
199     if (tree) tree_assimilate(tree);
200
201     return TRUE;
202 }
203
204 gboolean keyboard_interactive_grab(guint state, ObClient *client,
205                                    ObAction *action)
206 {
207     ObInteractiveState *s;
208
209     g_assert(action->data.any.interactive);
210
211     if (!interactive_states) {
212         grab_pointer(TRUE, FALSE, OB_CURSOR_POINTER);
213         if (!grab_keyboard(TRUE)) {
214             grab_pointer(FALSE, FALSE, OB_CURSOR_NONE);
215             return FALSE;
216         }
217     }
218
219     s = g_new(ObInteractiveState, 1);
220
221     s->state = state;
222     s->client = client;
223     s->actions = g_slist_append(NULL, action);
224
225     interactive_states = g_slist_append(interactive_states, s);
226
227     return TRUE;
228 }
229
230 void keyboard_interactive_end(ObInteractiveState *s,
231                               guint state, gboolean cancel, Time time)
232 {
233     action_run_interactive(s->actions, s->client, state, time, cancel, TRUE);
234
235     g_slist_free(s->actions);
236     g_free(s);
237
238     interactive_states = g_slist_remove(interactive_states, s);
239
240     if (!interactive_states) {
241         grab_keyboard(FALSE);
242         grab_pointer(FALSE, FALSE, OB_CURSOR_NONE);
243     }
244 }
245
246 void keyboard_interactive_end_client(ObClient *client, gpointer data)
247 {
248     GSList *it, *next;
249
250     for (it = interactive_states; it; it = next) {
251         ObInteractiveState *s = it->data;
252
253         next = g_slist_next(it);
254
255         if (s->client == client)
256             s->client = NULL;
257     }
258 }
259
260 gboolean keyboard_process_interactive_grab(const XEvent *e, ObClient **client)
261 {
262     GSList *it, *next;
263     gboolean handled = FALSE;
264     gboolean done = FALSE;
265     gboolean cancel = FALSE;
266
267     for (it = interactive_states; it; it = next) {
268         ObInteractiveState *s = it->data;
269
270         next = g_slist_next(it);
271         
272         if ((e->type == KeyRelease && 
273              !(s->state & e->xkey.state)))
274             done = TRUE;
275         else if (e->type == KeyPress) {
276             /*if (e->xkey.keycode == ob_keycode(OB_KEY_RETURN))
277                 done = TRUE;
278             else */if (e->xkey.keycode == ob_keycode(OB_KEY_ESCAPE))
279                 cancel = done = TRUE;
280         } else if (e->type == ButtonPress)
281             cancel = done = TRUE;
282
283         if (done) {
284             keyboard_interactive_end(s, e->xkey.state, cancel, e->xkey.time);
285
286             handled = TRUE;
287         } else
288             *client = s->client;
289     }
290
291     return handled;
292 }
293
294 void keyboard_event(ObClient *client, const XEvent *e)
295 {
296     KeyBindingTree *p;
297
298     g_assert(e->type == KeyPress);
299
300     if (e->xkey.keycode == config_keyboard_reset_keycode &&
301         e->xkey.state == config_keyboard_reset_state)
302     {
303         ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
304         keyboard_reset_chains(-1);
305         return;
306     }
307
308     if (curpos == NULL)
309         p = keyboard_firstnode;
310     else
311         p = curpos->first_child;
312     while (p) {
313         if (p->key == e->xkey.keycode &&
314             p->state == e->xkey.state)
315         {
316             if (p->first_child != NULL) { /* part of a chain */
317                 ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
318                 /* 3 second timeout for chains */
319                 ob_main_loop_timeout_add(ob_main_loop, 3 * G_USEC_PER_SEC,
320                                          chain_timeout, NULL,
321                                          g_direct_equal, NULL);
322                 set_curpos(p);
323             } else if (p->chroot)         /* an empty chroot */
324                 set_curpos(p);
325             else {
326                 keyboard_reset_chains(0);
327
328                 action_run_key(p->actions, client, e->xkey.state,
329                                e->xkey.x_root, e->xkey.y_root,
330                                e->xkey.time);
331             }
332             break;
333         }
334         p = p->next_sibling;
335     }
336 }
337
338 gboolean keyboard_interactively_grabbed()
339 {
340     return !!interactive_states;
341 }
342
343 void keyboard_startup(gboolean reconfig)
344 {
345     grab_keys(TRUE);
346     popup = popup_new(FALSE);
347
348     if (!reconfig)
349         client_add_destructor(keyboard_interactive_end_client, NULL);
350 }
351
352 void keyboard_shutdown(gboolean reconfig)
353 {
354     GSList *it;
355
356     if (!reconfig)
357         client_remove_destructor(keyboard_interactive_end_client);
358
359     for (it = interactive_states; it; it = g_slist_next(it))
360         g_free(it->data);
361     g_slist_free(interactive_states);
362     interactive_states = NULL;
363
364     ob_main_loop_timeout_remove(ob_main_loop, chain_timeout);
365     ob_main_loop_timeout_remove(ob_main_loop, popup_show_timeout);
366
367     keyboard_unbind_all();
368     set_curpos(NULL);
369
370     popup_free(popup);
371     popup = NULL;
372 }
373