2
* Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details.
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.
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.
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.
19
/* Originally derived from xfce4-xkb-plugin, Copyright 2004 Alexander Iliev,
20
* which credits Michael Glickman. */
22
/* Modified by Giuseppe Penone <giuspen@gmail.com> starting from 2012-07 and lxpanel 0.5.10 */
30
#include <X11/XKBlib.h>
33
#include <gdk-pixbuf/gdk-pixbuf.h>
36
/* The X Keyboard Extension: Library Specification
37
* http://www.xfree86.org/current/XKBlib.pdf */
41
NEW_KBD_STATE_NOTIFY_IGNORE_NO,
42
NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET,
43
NEW_KBD_STATE_NOTIFY_IGNORE_YES_ALL,
45
} t_new_kbd_notify_ignore;
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);
52
static t_new_kbd_notify_ignore xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_NO;
55
static gboolean xkb_new_kbd_notify_ignore_slot(gpointer p_data)
57
xkb_new_kbd_notify_ignore = NEW_KBD_STATE_NOTIFY_IGNORE_NO;
58
return FALSE; // remove source
61
/* Insert a process and its layout into the hash table. */
62
static void xkb_enter_locale_by_process(XkbPlugin * xkb)
64
if ((xkb->p_hash_table_group != NULL) && (fb_ev_active_window(fbev) != None))
66
Window * win = fb_ev_active_window(fbev);
68
g_hash_table_insert(xkb->p_hash_table_group, GINT_TO_POINTER(*win), GINT_TO_POINTER(xkb->current_group_xkb_no));
72
/* Return the current group Xkb ID. */
73
int xkb_get_current_group_xkb_no(XkbPlugin * xkb)
75
return xkb->current_group_xkb_no;
78
/* Return the count of members in the current group. */
79
int xkb_get_group_count(XkbPlugin * xkb)
81
return xkb->group_count;
84
/* Get the current group name. */
85
const char * xkb_get_current_group_name(XkbPlugin * xkb)
87
return xkb->group_names[xkb->current_group_xkb_no];
90
/* Convert a group number to a symbol name. */
91
const char * xkb_get_symbol_name_by_res_no(XkbPlugin * xkb, int n)
93
return xkb->symbol_names[n];
96
/* Get the current symbol name. */
97
const char * xkb_get_current_symbol_name(XkbPlugin * xkb)
99
return xkb_get_symbol_name_by_res_no(xkb, xkb->current_group_xkb_no);
102
/* Get the current symbol name converted to lowercase. */
103
const char * xkb_get_current_symbol_name_lowercase(XkbPlugin * xkb)
105
const char * tmp = xkb_get_current_symbol_name(xkb);
106
return ((tmp != NULL) ? g_utf8_strdown(tmp, -1) : NULL);
109
/* Refresh current group number from Xkb state. */
110
static void refresh_group_xkb(XkbPlugin * xkb)
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);
119
/* Initialize the keyboard description initially or after a NewKeyboard event. */
120
static int initialize_keyboard_description(XkbPlugin * xkb)
122
/* Allocate a keyboard description. */
123
XkbDescRec * xkb_desc = XkbAllocKeyboard();
124
if (xkb_desc == NULL)
125
g_warning("XkbAllocKeyboard failed\n");
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");
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;
139
for (i = 0; i < XkbNumKbdGroups; i += 1)
141
g_free(xkb->group_names[i]);
142
xkb->group_names[i] = NULL;
143
if (group_source[i] != None)
145
xkb->group_count = i + 1;
146
char * p = XGetAtomName(xdisplay, group_source[i]);
147
xkb->group_names[i] = g_strdup(p);
152
/* Reinitialize the symbol name storage. */
153
for (i = 0; i < XkbNumKbdGroups; i += 1)
155
g_free(xkb->symbol_names[i]);
156
xkb->symbol_names[i] = NULL;
159
/* Get the symbol name of all keyboard layouts.
160
* This is a plus-sign separated string. */
161
if (xkb_desc->names->symbols != None)
163
char * symbol_string = XGetAtomName(xdisplay, xkb_desc->names->symbols);
164
if (symbol_string != NULL)
166
char * p = symbol_string;
168
int symbol_group_number = 0;
169
for ( ; symbol_group_number < XkbNumKbdGroups; p += 1)
172
if ((c == '\0') || (c == '+'))
174
/* End of a symbol. Ignore the symbols "pc" and "inet" and "group". */
176
if ((strcmp(q, "pc") != 0) && (strcmp(q, "inet") != 0) && (strcmp(q, "group") != 0))
178
xkb->symbol_names[symbol_group_number] = g_ascii_strup(q, -1);
179
symbol_group_number += 1;
185
else if ((c == ':') && (p[1] >= '1') && (p[1] < ('1' + XkbNumKbdGroups)))
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". */
190
symbol_group_number = p[1] - '1';
191
xkb->symbol_names[symbol_group_number] = g_ascii_strup(q, -1);
192
symbol_group_number += 1;
198
else if ((*p >= 'A') && (*p <= 'Z'))
200
else if ((*p < 'a') || (*p > 'z'))
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))
210
//g_warning("Group count mismatch, ctrls = %d, groups = %d, symbols = %d\n", xkb_desc->ctrls->num_groups, xkb->group_count, symbol_group_number);
212
/* Maximize the "groups" and "symbols" value. */
213
if (xkb->group_count < symbol_group_number)
214
xkb->group_count = symbol_group_number;
216
XFree(symbol_string);
220
XkbFreeKeyboard(xkb_desc, 0, True);
223
/* Ensure that all elements within the name vectors are initialized. */
225
for (i = 0; i < XkbNumKbdGroups; i += 1)
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");
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);
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)
244
XEvent * ev = (XEvent *) xevent;
246
if (ev->xany.type == xkb->base_event_code + XkbEventCode)
249
XkbEvent * xkbev = (XkbEvent *) ev;
250
if (xkbev->any.xkb_type == XkbNewKeyboardNotify)
252
if(xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_NO)
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);
259
else if(xkb_new_kbd_notify_ignore == NEW_KBD_STATE_NOTIFY_IGNORE_YES_SET)
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);
266
xkb_enter_locale_by_process(xkb);
269
else if (xkbev->any.xkb_type == XkbStateNotify)
271
if (xkbev->state.group != xkb->current_group_xkb_no)
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);
278
xkb_enter_locale_by_process(xkb);
282
return GDK_FILTER_CONTINUE;
285
/* Initialize the Xkb interface. */
286
void xkb_mechanism_constructor(XkbPlugin * xkb)
288
/* Initialize Xkb extension. */
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)))
296
Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
298
/* Read the keyboard description. */
299
initialize_keyboard_description(xkb);
301
/* Establish GDK event filter. */
302
gdk_window_add_filter(NULL, (GdkFilterFunc) xkb_event_filter, (gpointer) xkb);
304
/* Specify events we will receive. */
305
XkbSelectEvents(xdisplay, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
306
XkbSelectEventDetails(xdisplay, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
308
/* Get current state. */
309
refresh_group_xkb(xkb);
313
/* Deallocate resources associated with Xkb interface. */
314
void xkb_mechanism_destructor(XkbPlugin * xkb)
316
/* Remove event filter. */
317
gdk_window_remove_filter(NULL, (GdkFilterFunc) xkb_event_filter, xkb);
319
/* Free group and symbol name memory. */
321
for (i = 0; i < XkbNumKbdGroups; i++)
323
if (xkb->group_names[i] != NULL)
325
g_free(xkb->group_names[i]);
326
xkb->group_names[i] = NULL;
328
if (xkb->symbol_names[i] != NULL)
330
g_free(xkb->symbol_names[i]);
331
xkb->symbol_names[i] = NULL;
335
/* Destroy the hash table. */
336
g_hash_table_destroy(xkb->p_hash_table_group);
337
xkb->p_hash_table_group = NULL;
340
/* Set the layout to the next layout. */
341
int xkb_change_group(XkbPlugin * xkb, int increment)
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;
348
/* Execute the change. */
349
XkbLockGroup(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), XkbUseCoreKbd, next_group);
350
refresh_group_xkb(xkb);
352
xkb_enter_locale_by_process(xkb);
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)
359
gint new_group_xkb_no = 0;
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);
365
if (new_group_xkb_no < xkb->group_count)
367
XkbLockGroup(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
368
XkbUseCoreKbd, new_group_xkb_no);
369
refresh_group_xkb(xkb);