Fix build on Debian 7
[claws.git] / src / plugins / notification / gtkhotkey / x11 / tomboykeybinder.c
1
2 #include <string.h>
3 #include <stdio.h>
4 #include <unistd.h>
5 #include <sys/socket.h>
6 #include <gdk/gdk.h>
7 #include <gdk/gdkx.h>
8 #include <X11/Xlib.h>
9
10 #include "eggaccelerators.h"
11 #include "tomboykeybinder.h"
12
13 /* Uncomment the next line to print a debug trace. */
14 //#define DEBUG 1
15
16 #ifdef DEBUG
17 #  define TRACE(x) x
18 #else
19 #  define TRACE(x) do {} while (FALSE);
20 #endif
21
22 typedef struct _Binding {
23         TomboyBindkeyHandler  handler;
24         gpointer              user_data;
25         char                 *keystring;
26         uint                  keycode;
27         uint                  modifiers;
28 } Binding;
29
30 static GSList *bindings = NULL;
31 static guint32 last_event_time = 0;
32 static gboolean processing_event = FALSE;
33
34 static guint num_lock_mask, caps_lock_mask, scroll_lock_mask;
35
36 static void
37 lookup_ignorable_modifiers (GdkKeymap *keymap)
38 {
39         egg_keymap_resolve_virtual_modifiers (keymap, 
40                                               EGG_VIRTUAL_LOCK_MASK,
41                                               &caps_lock_mask);
42
43         egg_keymap_resolve_virtual_modifiers (keymap, 
44                                               EGG_VIRTUAL_NUM_LOCK_MASK,
45                                               &num_lock_mask);
46
47         egg_keymap_resolve_virtual_modifiers (keymap, 
48                                               EGG_VIRTUAL_SCROLL_LOCK_MASK,
49                                               &scroll_lock_mask);
50 }
51
52 static void
53 grab_ungrab_with_ignorable_modifiers (GdkWindow *rootwin, 
54                                       Binding   *binding,
55                                       gboolean   grab)
56 {
57         guint mod_masks [] = {
58                 0, /* modifier only */
59                 num_lock_mask,
60                 caps_lock_mask,
61                 scroll_lock_mask,
62                 num_lock_mask  | caps_lock_mask,
63                 num_lock_mask  | scroll_lock_mask,
64                 caps_lock_mask | scroll_lock_mask,
65                 num_lock_mask  | caps_lock_mask | scroll_lock_mask,
66         };
67         int i;
68
69         for (i = 0; i < G_N_ELEMENTS (mod_masks); i++) {
70                 if (grab) {
71                         XGrabKey (GDK_WINDOW_XDISPLAY (rootwin), 
72                                   binding->keycode, 
73                                   binding->modifiers | mod_masks [i], 
74                                   GDK_WINDOW_XWINDOW (rootwin), 
75                                   False, 
76                                   GrabModeAsync,
77                                   GrabModeAsync);
78                 } else {
79                         XUngrabKey (GDK_WINDOW_XDISPLAY (rootwin),
80                                     binding->keycode,
81                                     binding->modifiers | mod_masks [i], 
82                                     GDK_WINDOW_XWINDOW (rootwin));
83                 }
84         }
85 }
86
87 static gboolean 
88 do_grab_key (Binding *binding)
89 {
90         GdkKeymap *keymap = gdk_keymap_get_default ();
91         GdkWindow *rootwin = gdk_get_default_root_window ();
92
93         EggVirtualModifierType virtual_mods = 0;
94         guint keysym = 0;
95
96         TRACE (g_print ("Preparing to bind %s\n", binding->keystring));
97         
98         g_return_val_if_fail (keymap != NULL, FALSE);
99         g_return_val_if_fail (rootwin != NULL, FALSE);
100
101         if (!egg_accelerator_parse_virtual (binding->keystring, 
102                                             &keysym, 
103                                             &virtual_mods)) {
104                 TRACE (g_print("Failed to parse '%s'", binding->keystring));
105                 return FALSE;
106         }
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!", 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         return success;
266 }
267
268 void
269 tomboy_keybinder_unbind (const char           *keystring, 
270                          TomboyBindkeyHandler  handler)
271 {
272         GSList *iter;
273
274         for (iter = bindings; iter != NULL; iter = iter->next) {
275                 Binding *binding = (Binding *) iter->data;
276
277                 if (strcmp (keystring, binding->keystring) != 0 ||
278                     handler != binding->handler) 
279                         continue;
280
281                 do_ungrab_key (binding);
282
283                 bindings = g_slist_remove (bindings, binding);
284
285                 g_free (binding->keystring);
286                 g_free (binding);
287                 break;
288         }
289 }
290
291 /* 
292  * From eggcellrenderkeys.c.
293  */
294 gboolean
295 tomboy_keybinder_is_modifier (guint keycode)
296 {
297         gint i;
298         gint map_size;
299         XModifierKeymap *mod_keymap;
300         gboolean retval = FALSE;
301
302         mod_keymap = XGetModifierMapping (gdk_display);
303
304         map_size = 8 * mod_keymap->max_keypermod;
305
306         i = 0;
307         while (i < map_size) {
308                 if (keycode == mod_keymap->modifiermap[i]) {
309                         retval = TRUE;
310                         break;
311                 }
312                 ++i;
313         }
314
315         XFreeModifiermap (mod_keymap);
316
317         return retval;
318 }
319
320 guint32
321 tomboy_keybinder_get_current_event_time (void)
322 {
323         if (processing_event) 
324                 return last_event_time;
325         else
326                 return GDK_CURRENT_TIME;
327 }