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