Clean up Tomboy's code
[tilda-gobject.git] / tomboykeybinder.c
1 #include <string.h>
2
3 #include <gdk/gdk.h>
4 #include <gdk/gdkwindow.h>
5 #include <gdk/gdkx.h>
6 #include <X11/Xlib.h>
7
8 #include "eggaccelerators.h"
9 #include "tomboykeybinder.h"
10
11 /* Uncomment the next line to print a debug trace. */
12 /* #define DEBUG */
13
14 #ifdef DEBUG
15 #  define TRACE(x) x
16 #else
17 #  define TRACE(x) do {} while (FALSE);
18 #endif
19
20 typedef struct _Binding {
21         TomboyBindkeyHandler  handler;
22         gpointer              user_data;
23         char                 *keystring;
24         uint                  keycode;
25         uint                  modifiers;
26 } Binding;
27
28 static GSList *bindings = NULL;
29 static guint32 last_event_time = 0;
30 static gboolean processing_event = FALSE;
31
32 static guint num_lock_mask, caps_lock_mask, scroll_lock_mask;
33
34 static void
35 lookup_ignorable_modifiers (GdkKeymap *keymap)
36 {
37         egg_keymap_resolve_virtual_modifiers (keymap, 
38                                               EGG_VIRTUAL_LOCK_MASK,
39                                               &caps_lock_mask);
40
41         egg_keymap_resolve_virtual_modifiers (keymap, 
42                                               EGG_VIRTUAL_NUM_LOCK_MASK,
43                                               &num_lock_mask);
44
45         egg_keymap_resolve_virtual_modifiers (keymap, 
46                                               EGG_VIRTUAL_SCROLL_LOCK_MASK,
47                                               &scroll_lock_mask);
48 }
49
50 static void
51 grab_ungrab_with_ignorable_modifiers (GdkWindow *rootwin, 
52                                       Binding   *binding,
53                                       gboolean   grab)
54 {
55         guint mod_masks [] = {
56                 0, /* modifier only */
57                 num_lock_mask,
58                 caps_lock_mask,
59                 scroll_lock_mask,
60                 num_lock_mask  | caps_lock_mask,
61                 num_lock_mask  | scroll_lock_mask,
62                 caps_lock_mask | scroll_lock_mask,
63                 num_lock_mask  | caps_lock_mask | scroll_lock_mask,
64         };
65         int i;
66
67         for (i = 0; i < G_N_ELEMENTS (mod_masks); i++) {
68                 if (grab) {
69                         XGrabKey (GDK_WINDOW_XDISPLAY (rootwin), 
70                                   binding->keycode, 
71                                   binding->modifiers | mod_masks [i], 
72                                   GDK_WINDOW_XWINDOW (rootwin), 
73                                   False, 
74                                   GrabModeAsync,
75                                   GrabModeAsync);
76                 } else {
77                         XUngrabKey (GDK_WINDOW_XDISPLAY (rootwin),
78                                     binding->keycode,
79                                     binding->modifiers | mod_masks [i], 
80                                     GDK_WINDOW_XWINDOW (rootwin));
81                 }
82         }
83 }
84
85 static gboolean 
86 do_grab_key (Binding *binding)
87 {
88         GdkKeymap *keymap = gdk_keymap_get_default ();
89         GdkWindow *rootwin = gdk_get_default_root_window ();
90
91         EggVirtualModifierType virtual_mods = 0;
92         guint keysym = 0;
93
94         if (keymap == NULL || rootwin == NULL)
95                 return FALSE;
96
97         gtk_accelerator_parse (binding->keystring, &keysym, &virtual_mods);
98
99         if (keysym == 0 && virtual_mods == 0)
100                 return FALSE;
101 #if 0
102         if (!egg_accelerator_parse_virtual (binding->keystring, 
103                                             &keysym, 
104                                             &virtual_mods))
105                 return FALSE;
106 #endif
107
108         TRACE (g_print ("Got accel %d, %d\n", keysym, virtual_mods));
109
110         binding->keycode = XKeysymToKeycode (GDK_WINDOW_XDISPLAY (rootwin), 
111                                              keysym);
112         if (binding->keycode == 0)
113                 return FALSE;
114
115         TRACE (g_print ("Got keycode %d\n", binding->keycode));
116
117         egg_keymap_resolve_virtual_modifiers (keymap,
118                                               virtual_mods,
119                                               &binding->modifiers);
120
121         TRACE (g_print ("Got modmask %d\n", binding->modifiers));
122
123         gdk_error_trap_push ();
124
125         grab_ungrab_with_ignorable_modifiers (rootwin, 
126                                               binding, 
127                                               TRUE /* grab */);
128
129         gdk_flush ();
130
131         if (gdk_error_trap_pop ()) {
132            g_warning ("Binding '%s' failed!\n", binding->keystring);
133            return FALSE;
134         }
135
136         return TRUE;
137 }
138
139 static gboolean 
140 do_ungrab_key (Binding *binding)
141 {
142         GdkWindow *rootwin = gdk_get_default_root_window ();
143
144         TRACE (g_print ("Removing grab for '%s'\n", binding->keystring));
145
146         grab_ungrab_with_ignorable_modifiers (rootwin, 
147                                               binding, 
148                                               FALSE /* ungrab */);
149
150         return TRUE;
151 }
152
153 static GdkFilterReturn
154 filter_func (GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
155 {
156         GdkFilterReturn return_val = GDK_FILTER_CONTINUE;
157         XEvent *xevent = (XEvent *) gdk_xevent;
158         guint event_mods;
159         GSList *iter;
160
161         TRACE (g_print ("Got Event! %d, %d\n", xevent->type, event->type));
162
163         switch (xevent->type) {
164         case KeyPress:
165                 TRACE (g_print ("Got KeyPress! keycode: %d, modifiers: %d\n", 
166                                 xevent->xkey.keycode, 
167                                 xevent->xkey.state));
168
169                 /* 
170                  * Set the last event time for use when showing
171                  * windows to avoid anti-focus-stealing code.
172                  */
173                 processing_event = TRUE;
174                 last_event_time = xevent->xkey.time;
175
176                 event_mods = xevent->xkey.state & ~(num_lock_mask  | 
177                                                     caps_lock_mask | 
178                                                     scroll_lock_mask);
179
180                 for (iter = bindings; iter != NULL; iter = iter->next) {
181                         Binding *binding = (Binding *) iter->data;
182                                                        
183                         if (binding->keycode == xevent->xkey.keycode &&
184                             binding->modifiers == event_mods) {
185
186                                 TRACE (g_print ("Calling handler for '%s'...\n", 
187                                                 binding->keystring));
188
189                                 (binding->handler) (binding->keystring, 
190                                                     binding->user_data);
191                         }
192                 }
193
194                 processing_event = FALSE;
195                 break;
196         case KeyRelease:
197                 TRACE (g_print ("Got KeyRelease! \n"));
198                 break;
199         }
200
201         return return_val;
202 }
203
204 static void 
205 keymap_changed (GdkKeymap *map)
206 {
207         GdkKeymap *keymap = gdk_keymap_get_default ();
208         GSList *iter;
209
210         TRACE (g_print ("Keymap changed! Regrabbing keys..."));
211
212         for (iter = bindings; iter != NULL; iter = iter->next) {
213                 Binding *binding = (Binding *) iter->data;
214                 do_ungrab_key (binding);
215         }
216
217         lookup_ignorable_modifiers (keymap);
218
219         for (iter = bindings; iter != NULL; iter = iter->next) {
220                 Binding *binding = (Binding *) iter->data;
221                 do_grab_key (binding);
222         }
223 }
224
225 void 
226 tomboy_keybinder_init (void)
227 {
228         GdkKeymap *keymap = gdk_keymap_get_default ();
229         GdkWindow *rootwin = gdk_get_default_root_window ();
230
231         lookup_ignorable_modifiers (keymap);
232
233         gdk_window_add_filter (rootwin, 
234                                filter_func, 
235                                NULL);
236
237         g_signal_connect (keymap, 
238                           "keys_changed",
239                           G_CALLBACK (keymap_changed),
240                           NULL);
241 }
242
243 gboolean
244 tomboy_keybinder_bind (const char           *keystring,
245                        TomboyBindkeyHandler  handler,
246                        gpointer              user_data)
247 {
248         Binding *binding;
249         gboolean success;
250
251         binding = g_new0 (Binding, 1);
252         binding->keystring = g_strdup (keystring);
253         binding->handler = handler;
254         binding->user_data = user_data;
255
256         /* Sets the binding's keycode and modifiers */
257         success = do_grab_key (binding);
258
259         if (success) {
260                 bindings = g_slist_prepend (bindings, binding);
261         } else {
262                 g_free (binding->keystring);
263                 g_free (binding);
264         }
265
266         return success;
267 }
268
269 void
270 tomboy_keybinder_unbind (const char           *keystring, 
271                          TomboyBindkeyHandler  handler)
272 {
273         GSList *iter;
274
275         for (iter = bindings; iter != NULL; iter = iter->next) {
276                 Binding *binding = (Binding *) iter->data;
277
278                 if (strcmp (keystring, binding->keystring) != 0 ||
279                     handler != binding->handler) 
280                         continue;
281
282                 do_ungrab_key (binding);
283
284                 bindings = g_slist_remove (bindings, binding);
285
286                 g_free (binding->keystring);
287                 g_free (binding);
288                 break;
289         }
290 }
291
292 #if 0
293 /* 
294  * From eggcellrenderkeys.c.
295  */
296 gboolean
297 tomboy_keybinder_is_modifier (guint keycode)
298 {
299         gint i;
300         gint map_size;
301         XModifierKeymap *mod_keymap;
302         gboolean retval = FALSE;
303
304         mod_keymap = XGetModifierMapping (gdk_display);
305
306         map_size = 8 * mod_keymap->max_keypermod;
307
308         i = 0;
309         while (i < map_size) {
310                 if (keycode == mod_keymap->modifiermap[i]) {
311                         retval = TRUE;
312                         break;
313                 }
314                 ++i;
315         }
316
317         XFreeModifiermap (mod_keymap);
318
319         return retval;
320 }
321 #endif
322
323 guint32
324 tomboy_keybinder_get_current_event_time (void)
325 {
326         if (processing_event) 
327                 return last_event_time;
328         else
329                 return GDK_CURRENT_TIME;
330 }