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