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