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