Merging upstream version 0.7.0 (Closes: #493243, #510888, #567617, #699414, #709777...
[debian/lxpanel.git] / plugins / xkb / xkb.c
1 /**
2 * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 /* Originally derived from xfce4-xkb-plugin, Copyright 2004 Alexander Iliev,
20 * which credits Michael Glickman. */
21
22 /* Modified by Giuseppe Penone <giuspen@gmail.com> starting from 2012-07 and lxpanel 0.5.10 */
23
24 #include "xkb.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <X11/XKBlib.h>
31
32 #include <gtk/gtk.h>
33 #include <gdk-pixbuf/gdk-pixbuf.h>
34 #include <glib.h>
35
36 /* The X Keyboard Extension: Library Specification
37 * http://www.xfree86.org/current/XKBlib.pdf */
38
39 typedef enum
40 {
41 NEW_KBD_STATE_NOTIFY_IGNORE_NO,
42 NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET,
43 NEW_KBD_STATE_NOTIFY_IGNORE_YES_ALL,
44
45 } t_new_kbd_notify_ignore;
46
47 static void xkb_enter_locale_by_process(XkbPlugin * xkb);
48 static void refresh_group_xkb(XkbPlugin * xkb);
49 static int initialize_keyboard_description(XkbPlugin * xkb);
50 static GdkFilterReturn xkb_event_filter(GdkXEvent * xevent, GdkEvent * event, XkbPlugin * xkb);
51
52 static t_new_kbd_notify_ignore xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_NO;
53
54
55 static gboolean xkb_new_kbd_notify_ignore_slot(gpointer p_data)
56 {
57 xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_NO;
58 return FALSE; // remove source
59 }
60
61 /* Insert a process and its layout into the hash table. */
62 static void xkb_enter_locale_by_process(XkbPlugin * xkb)
63 {
64 if ((xkb->p_hash_table_group != NULL) && (fb_ev_active_window(fbev) != None))
65 {
66 Window * win = fb_ev_active_window(fbev);
67 if (*win != None)
68 g_hash_table_insert(xkb->p_hash_table_group, GINT_TO_POINTER(*win), GINT_TO_POINTER(xkb->current_group_xkb_no));
69 }
70 }
71
72 /* Return the current group Xkb ID. */
73 int xkb_get_current_group_xkb_no(XkbPlugin * xkb)
74 {
75 return xkb->current_group_xkb_no;
76 }
77
78 /* Return the count of members in the current group. */
79 int xkb_get_group_count(XkbPlugin * xkb)
80 {
81 return xkb->group_count;
82 }
83
84 /* Get the current group name. */
85 const char * xkb_get_current_group_name(XkbPlugin * xkb)
86 {
87 return xkb->group_names[xkb->current_group_xkb_no];
88 }
89
90 /* Convert a group number to a symbol name. */
91 const char * xkb_get_symbol_name_by_res_no(XkbPlugin * xkb, int n)
92 {
93 return xkb->symbol_names[n];
94 }
95
96 /* Get the current symbol name. */
97 const char * xkb_get_current_symbol_name(XkbPlugin * xkb)
98 {
99 return xkb_get_symbol_name_by_res_no(xkb, xkb->current_group_xkb_no);
100 }
101
102 /* Get the current symbol name converted to lowercase. */
103 const char * xkb_get_current_symbol_name_lowercase(XkbPlugin * xkb)
104 {
105 const char * tmp = xkb_get_current_symbol_name(xkb);
106 return ((tmp != NULL) ? g_utf8_strdown(tmp, -1) : NULL);
107 }
108
109 /* Refresh current group number from Xkb state. */
110 static void refresh_group_xkb(XkbPlugin * xkb)
111 {
112 /* Get the current group number.
113 * This shouldn't be necessary, but mask the group number down for safety. */
114 XkbStateRec xkb_state;
115 XkbGetState(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XkbUseCoreKbd, &xkb_state);
116 xkb->current_group_xkb_no = xkb_state.group & (XkbNumKbdGroups - 1);
117 }
118
119 /* Initialize the keyboard description initially or after a NewKeyboard event. */
120 static int initialize_keyboard_description(XkbPlugin * xkb)
121 {
122 /* Allocate a keyboard description. */
123 XkbDescRec * xkb_desc = XkbAllocKeyboard();
124 if (xkb_desc == NULL)
125 g_warning("XkbAllocKeyboard failed\n");
126 else
127 {
128 /* Read necessary values into the keyboard description. */
129 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
130 XkbGetControls(xdisplay, XkbAllControlsMask, xkb_desc);
131 XkbGetNames(xdisplay, XkbSymbolsNameMask | XkbGroupNamesMask, xkb_desc);
132 if ((xkb_desc->names == NULL) || (xkb_desc->ctrls == NULL) || (xkb_desc->names->groups == NULL))
133 g_warning("XkbGetControls/XkbGetNames failed\n");
134 else
135 {
136 /* Get the group name of each keyboard layout. Infer the group count from the highest available. */
137 Atom * group_source = xkb_desc->names->groups;
138 int i;
139 for (i = 0; i < XkbNumKbdGroups; i += 1)
140 {
141 g_free(xkb->group_names[i]);
142 xkb->group_names[i] = NULL;
143 if (group_source[i] != None)
144 {
145 xkb->group_count = i + 1;
146 char * p = XGetAtomName(xdisplay, group_source[i]);
147 xkb->group_names[i] = g_strdup(p);
148 XFree(p);
149 }
150 }
151
152 /* Reinitialize the symbol name storage. */
153 for (i = 0; i < XkbNumKbdGroups; i += 1)
154 {
155 g_free(xkb->symbol_names[i]);
156 xkb->symbol_names[i] = NULL;
157 }
158
159 /* Get the symbol name of all keyboard layouts.
160 * This is a plus-sign separated string. */
161 if (xkb_desc->names->symbols != None)
162 {
163 char * symbol_string = XGetAtomName(xdisplay, xkb_desc->names->symbols);
164 if (symbol_string != NULL)
165 {
166 char * p = symbol_string;
167 char * q = p;
168 int symbol_group_number = 0;
169 for ( ; symbol_group_number < XkbNumKbdGroups; p += 1)
170 {
171 char c = *p;
172 if ((c == '\0') || (c == '+'))
173 {
174 /* End of a symbol. Ignore the symbols "pc" and "inet" and "group". */
175 *p = '\0';
176 if ((strcmp(q, "pc") != 0) && (strcmp(q, "inet") != 0) && (strcmp(q, "group") != 0))
177 {
178 xkb->symbol_names[symbol_group_number] = g_ascii_strup(q, -1);
179 symbol_group_number += 1;
180 }
181 if (c == '\0')
182 break;
183 q = p + 1;
184 }
185 else if ((c == ':') && (p[1] >= '1') && (p[1] < ('1' + XkbNumKbdGroups)))
186 {
187 /* Construction ":n" at the end of a symbol. The digit is a one-based index of the symbol.
188 * If not present, we will default to "next index". */
189 *p = '\0';
190 symbol_group_number = p[1] - '1';
191 xkb->symbol_names[symbol_group_number] = g_ascii_strup(q, -1);
192 symbol_group_number += 1;
193 p += 2;
194 if (*p == '\0')
195 break;
196 q = p + 1;
197 }
198 else if ((*p >= 'A') && (*p <= 'Z'))
199 *p |= 'a' - 'A';
200 else if ((*p < 'a') || (*p > 'z'))
201 *p = '\0';
202 }
203
204 /* Crosscheck the group count determined from the "ctrls" structure,
205 * that determined from the "groups" vector, and that determined from the "symbols" string.
206 * The "ctrls" structure is considered less reliable because it has been observed to be incorrect. */
207 if ((xkb->group_count != symbol_group_number)
208 || (xkb->group_count != xkb_desc->ctrls->num_groups))
209 {
210 //g_warning("Group count mismatch, ctrls = %d, groups = %d, symbols = %d\n", xkb_desc->ctrls->num_groups, xkb->group_count, symbol_group_number);
211
212 /* Maximize the "groups" and "symbols" value. */
213 if (xkb->group_count < symbol_group_number)
214 xkb->group_count = symbol_group_number;
215 }
216 XFree(symbol_string);
217 }
218 }
219 }
220 XkbFreeKeyboard(xkb_desc, 0, True);
221 }
222
223 /* Ensure that all elements within the name vectors are initialized. */
224 int i;
225 for (i = 0; i < XkbNumKbdGroups; i += 1)
226 {
227 if (xkb->group_names[i] == NULL)
228 xkb->group_names[i] = g_strdup("Unknown");
229 if (xkb->symbol_names[i] == NULL)
230 xkb->symbol_names[i] = g_strdup("None");
231 }
232
233 /* Create or recreate hash table */
234 if (xkb->p_hash_table_group != NULL)
235 g_hash_table_destroy(xkb->p_hash_table_group);
236 xkb->p_hash_table_group = g_hash_table_new(g_direct_hash, NULL);
237
238 return TRUE;
239 }
240
241 /* GDK event filter that receives events from all windows and the Xkb extension. */
242 static GdkFilterReturn xkb_event_filter(GdkXEvent * xevent, GdkEvent * event, XkbPlugin * xkb)
243 {
244 XEvent * ev = (XEvent *) xevent;
245
246 if (ev->xany.type == xkb->base_event_code + XkbEventCode)
247 {
248 /* Xkb event. */
249 XkbEvent * xkbev = (XkbEvent *) ev;
250 if (xkbev->any.xkb_type == XkbNewKeyboardNotify)
251 {
252 if(xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_NO)
253 {
254 //g_print("xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_NO\n");
255 xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET;
256 (void)g_timeout_add(1000/*msec*/, xkb_new_kbd_notify_ignore_slot, NULL);
257 xkb_setxkbmap(xkb);
258 }
259 else if(xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET)
260 {
261 //g_print("xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET\n");
262 xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_YES_ALL;
263 initialize_keyboard_description(xkb);
264 refresh_group_xkb(xkb);
265 xkb_redraw(xkb);
266 xkb_enter_locale_by_process(xkb);
267 }
268 }
269 else if (xkbev->any.xkb_type == XkbStateNotify)
270 {
271 if (xkbev->state.group != xkb->current_group_xkb_no)
272 {
273 /* Switch to the new group and redraw the display.
274 * This shouldn't be necessary, but mask the group number down for safety. */
275 xkb->current_group_xkb_no = xkbev->state.group & (XkbNumKbdGroups - 1);
276 refresh_group_xkb(xkb);
277 xkb_redraw(xkb);
278 xkb_enter_locale_by_process(xkb);
279 }
280 }
281 }
282 return GDK_FILTER_CONTINUE;
283 }
284
285 /* Initialize the Xkb interface. */
286 void xkb_mechanism_constructor(XkbPlugin * xkb)
287 {
288 /* Initialize Xkb extension. */
289 int opcode;
290 int maj = XkbMajorVersion;
291 int min = XkbMinorVersion;
292 if ((XkbLibraryVersion(&maj, &min))
293 && (XkbQueryExtension(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
294 &opcode, &xkb->base_event_code, &xkb->base_error_code, &maj, &min)))
295 {
296 Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
297
298 /* Read the keyboard description. */
299 initialize_keyboard_description(xkb);
300
301 /* Establish GDK event filter. */
302 gdk_window_add_filter(NULL, (GdkFilterFunc) xkb_event_filter, (gpointer) xkb);
303
304 /* Specify events we will receive. */
305 XkbSelectEvents(xdisplay, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
306 XkbSelectEventDetails(xdisplay, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
307
308 /* Get current state. */
309 refresh_group_xkb(xkb);
310 }
311 }
312
313 /* Deallocate resources associated with Xkb interface. */
314 void xkb_mechanism_destructor(XkbPlugin * xkb)
315 {
316 /* Remove event filter. */
317 gdk_window_remove_filter(NULL, (GdkFilterFunc) xkb_event_filter, xkb);
318
319 /* Free group and symbol name memory. */
320 int i;
321 for (i = 0; i < XkbNumKbdGroups; i++)
322 {
323 if (xkb->group_names[i] != NULL)
324 {
325 g_free(xkb->group_names[i]);
326 xkb->group_names[i] = NULL;
327 }
328 if (xkb->symbol_names[i] != NULL)
329 {
330 g_free(xkb->symbol_names[i]);
331 xkb->symbol_names[i] = NULL;
332 }
333 }
334
335 /* Destroy the hash table. */
336 g_hash_table_destroy(xkb->p_hash_table_group);
337 xkb->p_hash_table_group = NULL;
338 }
339
340 /* Set the layout to the next layout. */
341 int xkb_change_group(XkbPlugin * xkb, int increment)
342 {
343 /* Apply the increment and wrap the result. */
344 int next_group = xkb->current_group_xkb_no + increment;
345 if (next_group < 0) next_group = xkb->group_count - 1;
346 if (next_group >= xkb->group_count) next_group = 0;
347
348 /* Execute the change. */
349 XkbLockGroup(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XkbUseCoreKbd, next_group);
350 refresh_group_xkb(xkb);
351 xkb_redraw(xkb);
352 xkb_enter_locale_by_process(xkb);
353 return 1;
354 }
355
356 /* React to change of focus by switching to the application's layout or the default layout. */
357 void xkb_active_window_changed(XkbPlugin * xkb, Window window)
358 {
359 gint new_group_xkb_no = 0;
360
361 gpointer pKey = 0, pVal = 0;
362 if ((xkb->p_hash_table_group != NULL) && (g_hash_table_lookup_extended(xkb->p_hash_table_group, GINT_TO_POINTER(window), &pKey, &pVal)))
363 new_group_xkb_no = GPOINTER_TO_INT(pVal);
364
365 if (new_group_xkb_no < xkb->group_count)
366 {
367 XkbLockGroup(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
368 XkbUseCoreKbd, new_group_xkb_no);
369 refresh_group_xkb(xkb);
370 }
371 }