new code for bindings/callbacks. much sexier. now passes python classes back to the...
[mikachu/openbox.git] / src / bindings.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 #include "bindings.hh"
8 #include "screen.hh"
9 #include "openbox.hh"
10 #include "client.hh"
11 #include "frame.hh"
12 #include "python.hh"
13 #include "otk/display.hh"
14
15 extern "C" {
16 #include <X11/Xlib.h>
17
18 #include "gettext.h"
19 #define _(str) gettext(str)
20 }
21
22 namespace ob {
23
24 static bool buttonvalue(const std::string &button, unsigned int *val)
25 {
26   if (button == "1" || button == "Button1") {
27     *val |= Button1;
28   } else if (button == "2" || button == "Button2") {
29     *val |= Button2;
30   } else if (button == "3" || button == "Button3") {
31     *val |= Button3;
32   } else if (button == "4" || button == "Button4") {
33     *val |= Button4;
34   } else if (button == "5" || button == "Button5") {
35     *val |= Button5;
36   } else
37     return false;
38   return true;
39 }
40
41 static bool modvalue(const std::string &mod, unsigned int *val)
42 {
43   if (mod == "C") {           // control
44     *val |= ControlMask;
45   } else if (mod == "S") {    // shift
46     *val |= ShiftMask;
47   } else if (mod == "A" ||    // alt/mod1
48              mod == "M" ||
49              mod == "Mod1" ||
50              mod == "M1") {
51     *val |= Mod1Mask;
52   } else if (mod == "Mod2" ||   // mod2
53              mod == "M2") {
54     *val |= Mod2Mask;
55   } else if (mod == "Mod3" ||   // mod3
56              mod == "M3") {
57     *val |= Mod3Mask;
58   } else if (mod == "W" ||    // windows/mod4
59              mod == "Mod4" ||
60              mod == "M4") {
61     *val |= Mod4Mask;
62   } else if (mod == "Mod5" ||   // mod5
63              mod == "M5") {
64     *val |= Mod5Mask;
65   } else {                    // invalid
66     return false;
67   }
68   return true;
69 }
70
71 bool OBBindings::translate(const std::string &str, Binding &b) const
72 {
73   // parse out the base key name
74   std::string::size_type keybegin = str.find_last_of('-');
75   keybegin = (keybegin == std::string::npos) ? 0 : keybegin + 1;
76   std::string key(str, keybegin);
77
78   // parse out the requested modifier keys
79   unsigned int modval = 0;
80   std::string::size_type begin = 0, end;
81   while (begin != keybegin) {
82     end = str.find_first_of('-', begin);
83
84     std::string mod(str, begin, end-begin);
85     if (!modvalue(mod, &modval)) {
86       printf(_("Invalid modifier element in key binding: %s\n"), mod.c_str());
87       return false;
88     }
89     
90     begin = end + 1;
91   }
92
93   // set the binding
94   b.modifiers = modval;
95   KeySym sym = XStringToKeysym(const_cast<char *>(key.c_str()));
96   if (sym == NoSymbol) {
97     printf(_("Invalid Key name in key binding: %s\n"), key.c_str());
98     return false;
99   }
100   if (!(b.key = XKeysymToKeycode(otk::OBDisplay::display, sym)))
101     printf(_("No valid keycode for Key in key binding: %s\n"), key.c_str());
102   return b.key != 0;
103 }
104
105 static void destroytree(BindingTree *tree)
106 {
107   while (tree) {
108     BindingTree *c = tree->first_child;
109     delete tree;
110     tree = c;
111   }
112 }
113
114 BindingTree *OBBindings::buildtree(const StringVect &keylist,
115                                    PyObject *callback) const
116 {
117   if (keylist.empty()) return 0; // nothing in the list.. return 0
118
119   BindingTree *ret = 0, *p;
120
121   StringVect::const_reverse_iterator it, end = keylist.rend();
122   for (it = keylist.rbegin(); it != end; ++it) {
123     p = ret;
124     ret = new BindingTree(callback);
125     if (!p) ret->chain = false; // only the first built node
126     ret->first_child = p;
127     if (!translate(*it, ret->binding)) {
128       destroytree(ret);
129       ret = 0;
130       break;
131     }
132   }
133   return ret;
134 }
135
136
137 OBBindings::OBBindings()
138   : _curpos(&_tree),
139     _resetkey(0,0),
140     _timer(Openbox::instance->timerManager(),
141            (otk::OBTimeoutHandler)reset, this)
142 {
143   _timer.setTimeout(5000); // chains reset after 5 seconds
144   
145   setResetKey("C-g"); // set the default reset key
146 }
147
148
149 OBBindings::~OBBindings()
150 {
151   grabKeys(false);
152   removeAll();
153 }
154
155
156 void OBBindings::assimilate(BindingTree *node)
157 {
158   BindingTree *a, *b, *tmp, *last;
159
160   if (!_tree.first_child) {
161     // there are no nodes at this level yet
162     _tree.first_child = node;
163   } else {
164     a = _tree.first_child;
165     last = a;
166     b = node;
167     while (a) {
168       last = a;
169       if (a->binding != b->binding) {
170         a = a->next_sibling;
171       } else {
172         tmp = b;
173         b = b->first_child;
174         delete tmp;
175         a = a->first_child;
176       }
177     }
178     if (last->binding != b->binding)
179       last->next_sibling = b;
180     else {
181       last->first_child = b->first_child;
182       delete b;
183     }
184   }
185 }
186
187
188 PyObject *OBBindings::find(BindingTree *search, bool *conflict) const {
189   *conflict = false;
190   BindingTree *a, *b;
191   a = _tree.first_child;
192   b = search;
193   while (a && b) {
194     if (a->binding != b->binding) {
195       a = a->next_sibling;
196     } else {
197       if (a->chain == b->chain) {
198         if (!a->chain) {
199           // found it! (return the actual id, not the search's)
200           return a->callback;
201         }
202       } else {
203         *conflict = true;
204         return 0; // the chain status' don't match (conflict!)
205       }
206       b = b->first_child;
207       a = a->first_child;
208     }
209   }
210   return 0; // it just isn't in here
211 }
212
213
214 bool OBBindings::add(const StringVect &keylist, PyObject *callback)
215 {
216   BindingTree *tree;
217   bool conflict;
218
219   if (!(tree = buildtree(keylist, callback)))
220     return false; // invalid binding requested
221
222   if (find(tree, &conflict) || conflict) {
223     // conflicts with another binding
224     destroytree(tree);
225     return false;
226   }
227
228   grabKeys(false);
229   
230   // assimilate this built tree into the main tree
231   assimilate(tree); // assimilation destroys/uses the tree
232
233   Py_INCREF(callback);
234
235   grabKeys(true); 
236  
237   return true;
238 }
239
240
241 bool OBBindings::remove(const StringVect &keylist)
242 {
243   assert(false); // XXX: function not implemented yet
244
245   BindingTree *tree;
246   bool conflict;
247
248   if (!(tree = buildtree(keylist, 0)))
249     return false; // invalid binding requested
250
251   PyObject *func = find(tree, &conflict);
252   if (func) {
253     grabKeys(false);
254
255     _curpos = &_tree;
256     
257     // XXX do shit here ...
258     Py_DECREF(func);
259     
260     grabKeys(true);
261     return true;
262   }
263   return false;
264 }
265
266
267 void OBBindings::setResetKey(const std::string &key)
268 {
269   Binding b(0, 0);
270   if (translate(key, b)) {
271     grabKeys(false);
272     _resetkey.key = b.key;
273     _resetkey.modifiers = b.modifiers;
274     grabKeys(true);
275   }
276 }
277
278
279 static void remove_branch(BindingTree *first)
280 {
281   BindingTree *p = first;
282
283   while (p) {
284     if (p->first_child)
285       remove_branch(p->first_child);
286     BindingTree *s = p->next_sibling;
287     Py_XDECREF(p->callback);
288     delete p;
289     p = s;
290   }
291 }
292
293
294 void OBBindings::removeAll()
295 {
296   if (_tree.first_child) {
297     remove_branch(_tree.first_child);
298     _tree.first_child = 0;
299   }
300 }
301
302
303 void OBBindings::grabKeys(bool grab)
304 {
305   for (int i = 0; i < Openbox::instance->screenCount(); ++i) {
306     Window root = otk::OBDisplay::screenInfo(i)->rootWindow();
307
308     BindingTree *p = _curpos->first_child;
309     while (p) {
310       if (grab) {
311         otk::OBDisplay::grabKey(p->binding.key, p->binding.modifiers,
312                                 root, false, GrabModeAsync, GrabModeAsync,
313                                 false);
314       }
315       else
316         otk::OBDisplay::ungrabKey(p->binding.key, p->binding.modifiers,
317                                   root);
318       p = p->next_sibling;
319     }
320
321     if (grab)
322       otk::OBDisplay::grabKey(_resetkey.key, _resetkey.modifiers,
323                               root, true, GrabModeAsync, GrabModeAsync,
324                               false);
325     else
326       otk::OBDisplay::ungrabKey(_resetkey.key, _resetkey.modifiers,
327                                 root);
328   }
329 }
330
331
332 void OBBindings::fire(unsigned int modifiers, unsigned int key, Time time)
333 {
334   if (key == _resetkey.key && modifiers == _resetkey.modifiers) {
335     reset(this);
336   } else {
337     BindingTree *p = _curpos->first_child;
338     while (p) {
339       if (p->binding.key == key && p->binding.modifiers == modifiers) {
340         if (p->chain) {
341           _timer.start(); // start/restart the timer
342           grabKeys(false);
343           _curpos = p;
344           grabKeys(true);
345         } else {
346           Window win = None;
347           OBClient *c = Openbox::instance->focusedClient();
348           if (c) win = c->window();
349           python_callback(p->callback, win, modifiers, key, time);
350           reset(this);
351         }
352         break;
353       }
354       p = p->next_sibling;
355     }
356   }
357 }
358
359 void OBBindings::reset(OBBindings *self)
360 {
361   self->_timer.stop();
362   self->grabKeys(false);
363   self->_curpos = &self->_tree;
364   self->grabKeys(true);
365 }
366
367 }