Rename panel, plugin, and plugin_class to Panel, Plugin, and
[lxde/lxpanel.git] / src / plugins / xkb / xkb.c
1 /*
2 // ====================================================================
3 // xfce4-xkb-plugin - XFCE4 Xkb Layout Indicator panel plugin
4 // -------------------------------------------------------------------
5 // Alexander Iliev <sasoiliev@mamul.org>
6 // 20-Feb-04
7 // -------------------------------------------------------------------
8 // Parts of this code belong to Michael Glickman <wmalms@yahooo.com>
9 // and his program wmxkb.
10 // WARNING: DO NOT BOTHER Michael Glickman WITH QUESTIONS ABOUT THIS
11 // PROGRAM!!! SEND INSTEAD EMAILS TO <sasoiliev@mamul.org>
12 //====================================================================
13 */
14
15 /* Modified by Hong Jen Yee (PCMan) <pcman.tw@gmail.com> on 2008-04-06 for lxpanel */
16
17 #include "xkb.h"
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <X11/XKBlib.h>
24
25 #include <gtk/gtk.h>
26 #include <gdk-pixbuf/gdk-pixbuf.h>
27 #include <glib.h>
28
29 Display *dsp;
30
31 int group_title_source;
32 int group_code_count;
33 Bool flexy_groups;
34 char **group_codes;
35 char **custom_names;
36
37 static int base_event_code;
38 static int base_error_code;
39
40 static int device_id;
41
42 int current_group_xkb_no, current_group_res_no;
43 int group_count;
44
45 char *group_names[XkbNumKbdGroups];
46 char *symbol_names[XkbNumKbdGroups];
47
48 GHashTable* pGroupHash = NULL;
49
50 gint
51 get_group_count()
52 {
53 return group_count;
54 }
55
56 static int
57 group_lookup(int source_value, char *from_texts[], char *to_texts[], int count)
58 {
59 if (flexy_groups) {
60 const char *source_text = from_texts[source_value];
61
62 if (source_text != NULL) {
63 const char *target_text;
64 int i;
65
66 for (i=0; i<count; i++) {
67 target_text = to_texts[i];
68 if (strcasecmp(source_text, target_text) == 0) {
69 source_value = i;
70 break;
71 }
72 }
73 }
74 }
75
76 return source_value;
77
78 }
79
80 static int
81 group_xkb_to_res(int group_xkb_no)
82 {
83 return group_lookup(group_xkb_no, symbol_names, group_codes, group_code_count);
84 }
85
86 static int
87 group_no_res_to_xkb(int group_res_no)
88 {
89 return group_lookup(group_res_no, group_codes, symbol_names, group_count);
90 }
91
92 static const char *
93 get_group_name_by_res_no(int group_res_no)
94 {
95 return group_names[group_no_res_to_xkb(group_res_no)];
96 }
97
98 const char *
99 get_symbol_name_by_res_no(int group_res_no)
100 {
101 return symbol_names[group_no_res_to_xkb(group_res_no)];
102 }
103
104 static char *
105 get_current_group_name(void)
106 {
107 const char *tmp = get_symbol_name_by_res_no(current_group_xkb_no);
108 return g_utf8_strdown (tmp, -1);
109 }
110
111 void
112 accomodate_group_xkb(void)
113 {
114 XkbStateRec xkb_state;
115 XkbGetState(dsp, device_id, &xkb_state);
116 current_group_xkb_no = xkb_state.group;
117
118 current_group_res_no = group_xkb_to_res(current_group_xkb_no);
119 }
120
121 int
122 do_init_xkb()
123 {
124 const Atom *group_source;
125 Bool status;
126 int major, minor, oppcode;
127 int i;
128 XkbStateRec xkb_state;
129 XkbDescRec *kbd_desc_ptr = NULL;
130 const Atom *tmp_group_source;
131 Atom cur_group_atom;
132 Atom sym_name_atom;
133 char *ptr;
134 char *sym_name;
135 char *ptr1;
136 int count;
137
138 /* create hash asap, so it'll be ready when events arrive */
139 pGroupHash = g_hash_table_new(g_direct_hash, NULL);
140
141
142 /* Initialize the Xkb extension */
143 status = XkbQueryExtension(dsp, &oppcode,
144 &base_event_code, &base_error_code, &major, &minor);
145
146 device_id = XkbUseCoreKbd;
147
148 kbd_desc_ptr = XkbAllocKeyboard();
149 if (kbd_desc_ptr == NULL) {
150 fprintf(stderr, "Failed to get keyboard description\n");
151 goto HastaLaVista;
152 }
153
154 kbd_desc_ptr->dpy = dsp;
155 if (device_id != XkbUseCoreKbd) kbd_desc_ptr->device_spec = device_id;
156
157 XkbGetControls(dsp, XkbAllControlsMask, kbd_desc_ptr);
158 XkbGetNames(dsp, XkbSymbolsNameMask, kbd_desc_ptr);
159 XkbGetNames(dsp, XkbGroupNamesMask, kbd_desc_ptr);
160
161 if (kbd_desc_ptr->names == NULL) {
162 fprintf(stderr, "Failed to get keyboard description\n");
163 goto HastaLaVista;
164 }
165
166 group_source = kbd_desc_ptr->names->groups;
167
168 /* And more bug patches ! */
169 if (kbd_desc_ptr->ctrls != NULL) {
170 group_count = kbd_desc_ptr->ctrls->num_groups;
171 } else {
172 for (group_count=0;
173 group_count<XkbNumKbdGroups && group_source[group_count] != None;
174 group_count++);
175 }
176
177 if (group_count == 0) group_count=1;
178
179 for (i = 0; i < group_count; i++) {
180 group_names[i] = NULL;
181 symbol_names[i] = NULL;
182 }
183
184 tmp_group_source = kbd_desc_ptr->names->groups;
185
186 for (i = 0; i < group_count; i++) {
187 if ((cur_group_atom = tmp_group_source[i]) != None) {
188 group_names[i] = ptr = XGetAtomName(dsp, cur_group_atom);
189 if (ptr != NULL && (ptr=strchr(ptr, '(')) != NULL)
190 *ptr = '\0';
191 }
192 }
193 sym_name_atom = kbd_desc_ptr->names->symbols;
194 if (sym_name_atom == None ||
195 (sym_name = XGetAtomName(dsp, sym_name_atom)) == NULL) return 0;
196
197 count = 0;
198
199 for(ptr = strtok(sym_name, "+"); ptr != NULL; ptr = strtok(NULL, "+")) {
200 ptr1 = strchr(ptr, '(');
201 if (ptr1 != NULL) *ptr1 = '\0';
202 ptr1 = strchr(ptr, '_');
203 if (ptr1 != NULL && !g_ascii_isupper((int) *(ptr1+1))) *ptr1 = '\0';
204 ptr1 = strchr(ptr, ':');
205 if (ptr1 != NULL) *ptr1 = '\0';
206
207 ptr1 = strrchr(ptr, '/');
208 if (ptr1 != NULL) {
209 /* Filter out cases like pc/pc */
210 if (memcmp(ptr, ptr1+1, ptr1-ptr) == 0) continue;
211
212 ptr = ptr1+1;
213 }
214
215 if (strncmp(ptr, "group", 5) == 0) continue;
216 if (strncmp(ptr, "inet", 4) == 0) continue;
217 /* Filter cases like pc(pc105) (Xorg 7.0 update) */
218 if (strncmp(ptr, "pc", 2) == 0) continue;
219
220 symbol_names[count++] = g_utf8_strup(ptr, -1);
221 }
222
223 if (count == 1 && group_names[0] == NULL &&
224 strcmp(symbol_names[0], "jp") == 0) {
225 group_count = 2;
226 symbol_names[1] = symbol_names[0];
227 symbol_names[0] = strdup("us");
228 group_names[0] = strdup("US/ASCII");
229 group_names[1] = strdup("Japanese");
230 } else {
231 if (count<group_count) {
232 int j=count, k=group_count;
233 while(--j>=0) symbol_names[--k] = symbol_names[j];
234 while(--k>=0) symbol_names[k] = strdup("en_US");
235 }
236 }
237
238 count = (group_title_source == 2) ? group_code_count : group_count;
239
240 for (i = 0; i < count; i++) {
241 if (flexy_groups && group_codes[i] == NULL) {
242 fprintf(stderr, "\nCode is not specified for Group %i !\n", i+1);
243 fprintf(stderr, "Flexy mode is ignored\n");
244 flexy_groups = False;
245 }
246
247 switch(group_title_source) {
248 case 1: /* Group name */
249 if (group_names[i] == NULL) {
250 const char *name = get_symbol_name_by_res_no(i);
251 if (name == NULL) name = "U/A";
252 fprintf(stderr, "\nGroup Name %i is undefined, set to '%s' !\n", i+1, name);
253 group_names[i] = strdup(name);
254 }
255 break;
256
257 case 2: /* Gustom name */
258 if (custom_names[i] == NULL) {
259 const char *name = get_symbol_name_by_res_no(i);
260 if (name == NULL) name = get_group_name_by_res_no(i);
261 if (name == NULL) name = "U/A";
262 fprintf(stderr, "\nCustom Name %i is undefined, set to '%s' !\n", i+1, name);
263 custom_names[i] = strdup(name);
264 }
265 break;
266
267 default: /* Symbolic name (0), No title source but can be used for images (3) */
268 if (symbol_names[i] == NULL) {
269 fprintf(stderr, "\nGroup Symbol %i is undefined, set to 'U/A' !\n", i+1);
270 symbol_names[i] = strdup("U/A");
271 }
272 break;
273 }
274 }
275
276 XkbGetState(dsp, device_id, &xkb_state);
277 current_group_xkb_no = xkb_state.group;
278
279 status = True;
280
281 HastaLaVista:
282 if (kbd_desc_ptr) XkbFreeKeyboard(kbd_desc_ptr, 0, True);
283 return status;
284 }
285
286 gboolean temporary_changed_display_type = FALSE;
287
288 gboolean
289 is_current_group_flag_available(void)
290 {
291 char *filename;
292 gboolean result = FALSE;
293 GdkPixbuf *tmp = NULL;
294 char *group_name = get_current_group_name();
295 filename = g_strdup_printf("%s/%s.png", FLAGSDIR, group_name);
296 DBG ("Try to load image: %s", filename);
297 tmp = gdk_pixbuf_new_from_file(filename, NULL);
298 g_free(filename);
299 g_free (group_name);
300 result = (gboolean) (tmp != NULL);
301 if (tmp)
302 g_object_unref(G_OBJECT (tmp));
303 return result;
304 }
305
306 char *
307 xkb_get_label_markup(t_xkb *xkb)
308 {
309 int font_desc;
310
311 if (xkb->size < 20)
312 font_desc = 4;
313 else if (xkb->size >= 20 && xkb->size < 26)
314 font_desc = 6;
315 else if (xkb->size >= 26 && xkb->size < 32)
316 font_desc = 10;
317 else if (xkb->size >= 32 && xkb->size < 40)
318 font_desc = 12;
319 else if (xkb->size >= 40 && xkb->size < 52)
320 font_desc = 14;
321 else if (xkb->size >= 52 && xkb->size < 62)
322 font_desc = 16;
323 else if (xkb->size >= 62 && xkb->size < 70)
324 font_desc = 18;
325 else if (xkb->size >= 70 && xkb->size < 86)
326 font_desc = 20;
327 else if (xkb->size >= 86 && xkb->size < 100)
328 font_desc = 24;
329 else if (xkb->size >= 100 && xkb->size < 112)
330 font_desc = 28;
331 else if (xkb->size >= 112 && xkb->size < 124)
332 font_desc = 36;
333 else if (xkb->size >= 124)
334 font_desc = 48;
335
336 return g_markup_printf_escaped ("<span font_desc=\"%d\">%s</span>", font_desc, get_symbol_name_by_res_no (current_group_xkb_no));
337 }
338
339 void
340 set_new_locale(t_xkb *ctrl)
341 {
342 t_xkb *plugin = (t_xkb *) ctrl;
343 char *filename;
344 char *label_markup;
345 char *group_name;
346 int size;
347 GdkPixbuf *pixbuf = NULL, *tmp = NULL;
348 gint pid;
349
350 /* Set the label */
351 label_markup = xkb_get_label_markup (plugin);
352 gtk_label_set_markup (GTK_LABEL (plugin->label), label_markup);
353 g_free(label_markup);
354
355 /* Set the image */
356 size = 0.9 * plugin->size;
357 group_name = get_current_group_name();
358 filename = g_strdup_printf("%s/%s.png", FLAGSDIR, group_name);
359 DBG ("Try to load image: %s", filename);
360 tmp = gdk_pixbuf_new_from_file(filename, NULL);
361 g_free(filename);
362 g_free(group_name);
363 if (tmp == NULL) { /* could not be loaded for some reason */
364 if (plugin->display_type == IMAGE) {
365 temporary_changed_display_type = TRUE;
366 gtk_widget_hide(plugin->image);
367 gtk_widget_show(plugin->label);
368 }
369 } else { /* loaded successfully */
370 temporary_changed_display_type = TRUE;
371 pixbuf = gdk_pixbuf_scale_simple(tmp, size, size - (int) (size / 3), GDK_INTERP_BILINEAR);
372 gtk_image_set_from_pixbuf((GtkImage *) plugin->image, pixbuf);
373 if (tmp)
374 g_object_unref(G_OBJECT(tmp));
375
376 if (pixbuf)
377 g_object_unref(G_OBJECT(pixbuf));
378
379 if (plugin->display_type == IMAGE) {
380 /* the image for the previous active layout could not be loaded */
381 gtk_widget_hide(plugin->label);
382 gtk_widget_show(plugin->image);
383 }
384 }
385
386 /* Part of the image may remain visible after image or display type change */
387 gtk_widget_queue_draw_area(plugin->btn, 0, 0, plugin->size, plugin->size);
388
389 /* "locale per process" */
390 /* TBF:: bad here, it's not really a "window" related file */
391 if (pGroupHash && fb_ev_active_window( fbev ) != None )
392 {
393 pid = get_net_wm_pid( fb_ev_active_window( fbev ) );
394 DBG("Storing locale %s for %d\n", get_symbol_name_by_res_no(current_group_xkb_no), pid);
395
396 g_hash_table_insert(pGroupHash, GINT_TO_POINTER(pid), GINT_TO_POINTER(current_group_xkb_no));
397 }
398 }
399
400 void
401 handle_xevent(t_xkb *ctrl)
402 {
403 XkbEvent evnt;
404 int new_group_no;
405
406 XNextEvent(dsp, &evnt.core);
407 if (evnt.type == base_event_code) {
408
409 if (evnt.any.xkb_type == XkbStateNotify &&
410 (new_group_no = evnt.state.group) != current_group_xkb_no) {
411 current_group_xkb_no = new_group_no;
412 accomodate_group_xkb();
413 set_new_locale(ctrl);
414 }
415 }
416 }
417
418 const char *
419 initialize_xkb(t_xkb *ctrl)
420 {
421 XkbStateRec state;
422 int event_code, error_rtrn, major, minor, reason_rtrn;
423 char * display_name;
424 const char *group;
425
426 major = XkbMajorVersion;
427 minor = XkbMinorVersion;
428
429 display_name = "";
430 XkbIgnoreExtension(False);
431 dsp = XkbOpenDisplay(display_name, &event_code, &error_rtrn, &major, &minor, &reason_rtrn);
432
433 switch (reason_rtrn) {
434 case XkbOD_BadLibraryVersion:
435 fprintf(stderr, "Bad XKB library version.\n");
436 return NULL;
437 case XkbOD_ConnectionRefused:
438 fprintf(stderr, "Connection to X server refused.\n");
439 return NULL;
440 case XkbOD_BadServerVersion:
441 fprintf(stderr, "Bad X server version.\n");
442 return NULL;
443 case XkbOD_NonXkbServer:
444 fprintf(stderr, "XKB not present.\n");
445 return NULL;
446 case XkbOD_Success:
447 break;
448 }
449
450 if (do_init_xkb() != True) return "N/A";
451
452 group = get_symbol_name_by_res_no(current_group_xkb_no);
453
454 XkbSelectEventDetails(dsp, XkbUseCoreKbd, XkbStateNotify,
455 XkbAllStateComponentsMask, XkbGroupStateMask);
456
457 XkbGetState(dsp, device_id, &state);
458 current_group_xkb_no = (current_group_xkb_no != state.group) ? state.group : current_group_xkb_no;
459 accomodate_group_xkb();
460
461 if (ctrl != NULL) set_new_locale(ctrl);
462
463 return group;
464 }
465
466 static void
467 deinit_group_names()
468 {
469 int i;
470 for (i=0; i< group_count; i++) {
471 if (group_names[i] != NULL) {
472 free(group_names[i]);
473 group_names[i] = NULL;
474 }
475 if (symbol_names[i] != NULL) {
476 free(symbol_names[i]);
477 symbol_names[i] = NULL;
478 }
479 }
480 }
481
482 void
483 deinitialize_xkb()
484 {
485 deinit_group_names();
486 XCloseDisplay(dsp);
487 dsp = NULL;
488
489 g_hash_table_destroy(pGroupHash);
490 pGroupHash = NULL;
491 }
492
493 int
494 get_connection_number()
495 {
496 return ConnectionNumber(dsp);
497 }
498
499 /* Sets the kb layout to the next layout */
500 int
501 do_change_group(int increment, t_xkb *ctrl)
502 {
503 if (group_count <= 1) return 0;
504 XkbLockGroup(dsp, device_id,
505 (current_group_xkb_no + group_count + increment) % group_count);
506 /* why not simply (current_group_xkb_no + increment) % group_count ? */
507 handle_xevent(ctrl);
508 return 1;
509 }
510
511 int
512 do_set_group(int group, t_xkb *ctrl)
513 {
514 if (group >= group_count)
515 return 0;
516
517 XkbLockGroup(dsp, device_id, group);
518 accomodate_group_xkb();
519 set_new_locale(ctrl);
520
521 return 1;
522 }
523
524 gboolean
525 gio_callback(GIOChannel *source, GIOCondition condition, gpointer data)
526 {
527 handle_xevent((t_xkb *) data);
528 return TRUE;
529 }
530
531 void
532 react_active_window_changed(gint pid, t_xkb *ctrl)
533 {
534 if (ctrl->enable_perapp)
535 {
536 gpointer pKey=0, pVal=0;
537 gint new_group_xkb_no = ctrl->default_group;
538
539 if (pGroupHash && g_hash_table_lookup_extended(pGroupHash, GINT_TO_POINTER(pid), &pKey, &pVal))
540 new_group_xkb_no = GPOINTER_TO_INT(pVal);
541
542 do_set_group(new_group_xkb_no, ctrl);
543 }
544 }
545
546 /* TODO: destroy hashtable if perapp layout is disabled? */
547 void
548 react_application_closed(gint pid)
549 {
550 g_debug( "pid = %d", pid );
551 if ( pid && pGroupHash) {
552 g_hash_table_remove(pGroupHash, GINT_TO_POINTER(pid));
553 }
554 }
555