this is a big one! im putting stats in here just cuz!
[dana/openbox.git] / plugins / keyboard / keyboard.c
1 #include "kernel/focus.h"
2 #include "kernel/dispatch.h"
3 #include "kernel/openbox.h"
4 #include "kernel/event.h"
5 #include "kernel/grab.h"
6 #include "kernel/action.h"
7 #include "kernel/prop.h"
8 #include "kernel/timer.h"
9 #include "parser/parse.h"
10 #include "tree.h"
11 #include "keyboard.h"
12 #include "translate.h"
13 #include <glib.h>
14
15 /*
16
17 <keybind key="C-x">
18   <action name="ChangeDesktop">
19     <desktop>3</desktop>
20   </action>
21 </keybind>
22
23 */
24
25 static void parse_key(xmlDocPtr doc, xmlNodePtr node, GList *keylist)
26 {
27     char *key;
28     Action *action;
29     xmlNodePtr n, nact;
30     GList *it;
31
32     n = parse_find_node("keybind", node);
33     while (n) {
34         if (parse_attr_string("key", n, &key)) {
35             keylist = g_list_append(keylist, key);
36
37             parse_key(doc, n->xmlChildrenNode, keylist);
38
39             it = g_list_last(keylist);
40             g_free(it->data);
41             keylist = g_list_delete_link(keylist, it);
42         }
43         n = parse_find_node("keybind", n->next);
44     }
45     if (keylist) {
46         nact = parse_find_node("action", node);
47         while (nact) {
48             if ((action = action_parse(doc, nact))) {
49                 /* validate that its okay for a key binding */
50                 if (action->func == action_moveresize &&
51                     action->data.moveresize.corner !=
52                     prop_atoms.net_wm_moveresize_move_keyboard &&
53                     action->data.moveresize.corner !=
54                     prop_atoms.net_wm_moveresize_size_keyboard) {
55                     action_free(action);
56                     action = NULL;
57                 }
58
59                 if (action)
60                     kbind(keylist, action);
61             }
62             nact = parse_find_node("action", nact->next);
63         }
64     }
65 }
66
67 static void parse_xml(xmlDocPtr doc, xmlNodePtr node, void *d)
68 {
69     parse_key(doc, node, NULL);
70 }
71
72 void plugin_setup_config()
73 {
74     parse_register("keyboard", parse_xml, NULL);
75 }
76
77 KeyBindingTree *firstnode = NULL;
78
79 static KeyBindingTree *curpos;
80 static guint reset_key, reset_state, button_return, button_escape;
81 static Timer *chain_timer;
82
83 static void grab_keys()
84 {
85     KeyBindingTree *p;
86
87     ungrab_all_keys();
88
89     p = curpos ? curpos->first_child : firstnode;
90     while (p) {
91         grab_key(p->key, p->state, GrabModeAsync);
92         p = p->next_sibling;
93     }
94     if (curpos)
95         grab_key(reset_key, reset_state, GrabModeAsync);
96 }
97
98 static void reset_chains()
99 {
100     if (chain_timer) {
101         timer_stop(chain_timer);
102         chain_timer = NULL;
103     }
104     if (curpos) {
105         curpos = NULL;
106         grab_keys();
107     }
108 }
109
110 static void chain_timeout(void *data)
111 {
112     reset_chains();
113 }
114
115 gboolean kbind(GList *keylist, Action *action)
116 {
117     KeyBindingTree *tree, *t;
118     gboolean conflict;
119
120     g_assert(keylist != NULL);
121     g_assert(action != NULL);
122
123     if (!(tree = tree_build(keylist)))
124         return FALSE;
125
126     if ((t = tree_find(tree, &conflict)) != NULL) {
127         /* already bound to something, use the existing tree */
128         tree_destroy(tree);
129         tree = NULL;
130     } else
131         t = tree;
132     while (t->first_child) t = t->first_child;
133
134     if (conflict) {
135         g_message("conflict with binding");
136         tree_destroy(tree);
137         return FALSE;
138     }
139
140     /* set the action */
141     t->actions = g_slist_append(t->actions, action);
142     /* assimilate this built tree into the main tree. assimilation
143        destroys/uses the tree */
144     if (tree) tree_assimilate(tree);
145
146     return TRUE;
147 }
148
149 static void event(ObEvent *e, void *foo)
150 {
151     static KeyBindingTree *grabbed_key = NULL;
152
153     if (grabbed_key) {
154         gboolean done = FALSE;
155
156         if ((e->type == Event_X_KeyRelease && 
157              !(grabbed_key->state & e->data.x.e->xkey.state)))
158             done = TRUE;
159         else if (e->type == Event_X_KeyPress) {
160             if (e->data.x.e->xkey.keycode == button_return)
161                 done = TRUE;
162             else if (e->data.x.e->xkey.keycode == button_escape) {
163                 GSList *it;
164                 for (it = grabbed_key->actions; it; it = it->next) {
165                     Action *act = it->data;
166                     act->data.cycle.cancel = TRUE;
167                 }
168                 done = TRUE;
169             }
170         }
171         if (done) { 
172             GSList *it;
173             for (it = grabbed_key->actions; it; it = it->next) {
174                 Action *act = it->data;
175                 act->data.cycle.final = TRUE;
176                 act->func(&act->data);
177             }
178             grabbed_key = NULL;
179             grab_keyboard(FALSE);
180             reset_chains();
181             return;
182         }
183     }
184     if (e->type == Event_X_KeyRelease)
185         return;
186
187     g_assert(e->type == Event_X_KeyPress);
188
189     if (e->data.x.e->xkey.keycode == reset_key &&
190         e->data.x.e->xkey.state == reset_state) {
191         reset_chains();
192     } else {
193         KeyBindingTree *p;
194         if (curpos == NULL)
195             p = firstnode;
196         else
197             p = curpos->first_child;
198         while (p) {
199             if (p->key == e->data.x.e->xkey.keycode &&
200                 p->state == e->data.x.e->xkey.state) {
201                 if (p->first_child != NULL) { /* part of a chain */
202                     if (chain_timer) timer_stop(chain_timer);
203                     /* 5 second timeout for chains */
204                     chain_timer = timer_start(5000*1000, chain_timeout,
205                                               NULL);
206                     curpos = p;
207                     grab_keys();
208                 } else {
209                     GSList *it;
210                     for (it = p->actions; it; it = it->next) {
211                         Action *act = it->data;
212                         if (act->func != NULL) {
213                             act->data.any.c = focus_client;
214
215                             if (act->func == action_cycle_windows) {
216                                 act->data.cycle.final = FALSE;
217                                 act->data.cycle.cancel = FALSE;
218                             }
219
220                             act->data.any.c = focus_client;
221                             act->func(&act->data);
222
223                             if (act->func == action_cycle_windows &&
224                                 !grabbed_key) {
225                                 grabbed_key = p;
226                                 grab_keyboard(TRUE);
227                                 break;
228                             }
229                         }
230                     }
231
232                     reset_chains();
233                 }
234                 break;
235             }
236             p = p->next_sibling;
237         }
238     }
239 }
240
241 void plugin_startup()
242 {
243     guint i;
244
245     curpos = NULL;
246     chain_timer = NULL;
247
248     dispatch_register(Event_X_KeyPress | Event_X_KeyRelease,
249                       (EventHandler)event, NULL);
250
251     translate_key("C-g", &reset_state, &reset_key);
252     translate_key("Escape", &i, &button_escape);
253     translate_key("Return", &i, &button_return);
254
255     grab_keys();
256 }
257
258 void plugin_shutdown()
259 {
260     dispatch_register(0, (EventHandler)event, NULL);
261
262     tree_destroy(firstnode);
263     firstnode = NULL;
264     ungrab_all_keys();
265 }
266