1 /* Keyboard Accessibility Status Applet
2 * Copyright 2003, 2004 Sun Microsystems Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include <config.h>
21
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <glib/gi18n.h>
26 #include <glib-object.h>
27 #include <gtk/gtk.h>
28 #include <gio/gio.h>
29 #include <gio/gdesktopappinfo.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <gdk/gdkx.h>
32 #include <mate-panel-applet.h>
33 #include <X11/XKBlib.h>
34
35 #define XK_MISCELLANY
36 #define XK_XKB_KEYS
37
38 #include <X11/keysymdef.h>
39 #include "applet.h"
40
41 static int xkb_base_event_type = 0;
42
43 #define ALT_GRAPH_LED_MASK (0x10)
44 #define ICON_PADDING 4
45
46 typedef enum {
47 modifier_Shift = 0,
48 modifier_Control,
49 modifier_Mod1,
50 modifier_Mod2,
51 modifier_Mod3,
52 modifier_Mod4,
53 modifier_Mod5,
54 modifier_n
55 } E_modifiers;
56
57 typedef struct {
58 unsigned int mask;
59 GtkWidget* indicator;
60 gchar *icon_name;
61 } ModifierStruct;
62
63 static ModifierStruct modifiers[modifier_n] = {
64 [modifier_Shift] = {ShiftMask, NULL, SHIFT_KEY_ICON},
65 [modifier_Control] = {ControlMask, NULL, CONTROL_KEY_ICON},
66 [modifier_Mod1] = {Mod1Mask, NULL, ALT_KEY_ICON},
67 [modifier_Mod2] = {Mod2Mask, NULL, META_KEY_ICON},
68 [modifier_Mod3] = {Mod3Mask, NULL, HYPER_KEY_ICON},
69 [modifier_Mod4] = {Mod4Mask, NULL, SUPER_KEY_ICON},
70 [modifier_Mod5] = {Mod5Mask, NULL, ALTGRAPH_KEY_ICON}
71 };
72
73 typedef struct {
74 unsigned int mask;
75 gchar* icon_name;
76 } ButtonIconStruct;
77
78 static ButtonIconStruct button_icons[] = {
79 {Button1Mask, MOUSEKEYS_BUTTON_LEFT},
80 {Button2Mask, MOUSEKEYS_BUTTON_MIDDLE},
81 {Button3Mask, MOUSEKEYS_BUTTON_RIGHT}
82 };
83
84 static void
85 popup_error_dialog (AccessxStatusApplet* sapplet);
86
87 /* cribbed from geyes */
88 static void
about_cb(GtkAction * action,AccessxStatusApplet * sapplet)89 about_cb (GtkAction* action,
90 AccessxStatusApplet* sapplet)
91 {
92 static const gchar* authors[] = {
93 "Calum Benson <calum.benson@sun.com>",
94 "Bill Haneman <bill.haneman@sun.com>",
95 NULL
96 };
97
98 const gchar* documenters[] = {
99 "Bill Haneman <bill.haneman@sun.com>",
100 N_("Sun GNOME Documentation Team <gdocteam@sun.com>"),
101 N_("MATE Documentation Team"),
102 NULL
103 };
104
105 #ifdef ENABLE_NLS
106 const char **p;
107 for (p = documenters; *p; ++p)
108 *p = _(*p);
109 #endif
110
111 gtk_show_about_dialog (NULL,
112 "title", _("About AccessX Status"),
113 "version", VERSION,
114 "comments", _("Shows the state of AccessX features such as latched modifiers"),
115 "copyright", _("Copyright \xc2\xa9 2003 Sun Microsystems\n"
116 "Copyright \xc2\xa9 2012-2021 MATE developers"),
117 "authors", authors,
118 "documenters", documenters,
119 "translator-credits", _("translator-credits"),
120 "logo-icon-name", ACCESSX_APPLET,
121 NULL);
122 }
123
124 static void
help_cb(GtkAction * action,AccessxStatusApplet * sapplet)125 help_cb (GtkAction* action,
126 AccessxStatusApplet* sapplet)
127 {
128 GError* error = NULL;
129 GdkScreen* screen = gtk_widget_get_screen (GTK_WIDGET (sapplet->applet));
130
131 gtk_show_uri_on_window (NULL,
132 "help:mate-accessx-status",
133 gtk_get_current_event_time (),
134 &error);
135
136 if (error)
137 {
138 GtkWidget* parent = gtk_widget_get_parent (GTK_WIDGET (sapplet->applet));
139
140 GtkWidget* dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
141 GTK_DIALOG_DESTROY_WITH_PARENT,
142 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
143 _("There was an error launching the help viewer: %s"),
144 error->message);
145
146 g_signal_connect (dialog, "response",
147 G_CALLBACK (gtk_widget_destroy),
148 NULL);
149
150 gtk_window_set_screen (GTK_WINDOW (dialog), screen);
151 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
152
153 gtk_widget_show (dialog);
154 g_error_free (error);
155 }
156 }
157
158 static void
dialog_cb(GtkAction * action,AccessxStatusApplet * sapplet)159 dialog_cb (GtkAction* action,
160 AccessxStatusApplet* sapplet)
161 {
162 GError* error = NULL;
163 GdkScreen *screen;
164 GdkAppLaunchContext *launch_context;
165 GAppInfo *appinfo;
166
167 if (sapplet->error_type != ACCESSX_STATUS_ERROR_NONE)
168 {
169 popup_error_dialog (sapplet);
170 return;
171 }
172
173
174 screen = gtk_widget_get_screen (GTK_WIDGET (sapplet->applet));
175 appinfo = g_app_info_create_from_commandline ("mate-keyboard-properties --a11y",
176 _("Open the keyboard preferences dialog"),
177 G_APP_INFO_CREATE_NONE,
178 &error);
179
180 if (!error) {
181 launch_context =
182 gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (sapplet->applet)));
183
184 gdk_app_launch_context_set_screen (launch_context, screen);
185 g_app_info_launch (appinfo,
186 NULL,
187 G_APP_LAUNCH_CONTEXT (launch_context),
188 &error);
189
190 g_object_unref (launch_context);
191 }
192
193 if (error != NULL)
194 {
195 GtkWidget* dialog = gtk_message_dialog_new (NULL,
196 GTK_DIALOG_DESTROY_WITH_PARENT,
197 GTK_MESSAGE_ERROR,
198 GTK_BUTTONS_CLOSE,
199 _("There was an error launching the keyboard preferences dialog: %s"),
200 error->message);
201
202 g_signal_connect (dialog, "response",
203 G_CALLBACK (gtk_widget_destroy),
204 NULL);
205
206 gtk_window_set_screen (GTK_WINDOW (dialog), screen);
207 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
208
209 gtk_widget_show (dialog);
210 g_error_free (error);
211 }
212
213 g_object_unref (appinfo);
214 }
215
216 static const GtkActionEntry accessx_status_applet_menu_actions[] = {
217 {"Dialog", "document-properties",
218 N_("_Keyboard Accessibility Preferences"),
219 NULL, NULL, G_CALLBACK (dialog_cb)},
220 {"Help", "help-browser", N_("_Help"),
221 NULL, NULL, G_CALLBACK (help_cb)},
222 {"About", "help-about", N_("_About"),
223 NULL, NULL, G_CALLBACK (about_cb)}
224 };
225
226 static XkbDescPtr
accessx_status_applet_get_xkb_desc(AccessxStatusApplet * sapplet)227 accessx_status_applet_get_xkb_desc (AccessxStatusApplet* sapplet)
228 {
229 Display* display;
230
231 if (sapplet->xkb == NULL)
232 {
233 int ir, reason_return;
234 char* display_name = getenv ("DISPLAY");
235 display = XkbOpenDisplay (display_name,
236 &xkb_base_event_type,
237 &ir,
238 NULL, NULL,
239 &reason_return);
240 g_assert (display);
241
242 /* TODO: change error message below to something user-viewable */
243 if (display == NULL)
244 {
245 g_warning ("Xkb extension could not be initialized! (error code %x)",
246 reason_return);
247 }
248 else
249 {
250 sapplet->xkb = XkbGetMap (display,
251 XkbAllComponentsMask,
252 XkbUseCoreKbd);
253 }
254
255 g_assert (sapplet->xkb);
256
257 if (sapplet->xkb == NULL)
258 {
259 g_warning ("Xkb keyboard description not available!");
260 }
261
262 sapplet->xkb_display = display;
263 }
264 return sapplet->xkb;
265 }
266
267 static gboolean
accessx_status_applet_xkb_select(AccessxStatusApplet * sapplet)268 accessx_status_applet_xkb_select (AccessxStatusApplet* sapplet)
269 {
270 int opcode_rtn, error_rtn;
271 gboolean retval = FALSE;
272 GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sapplet->applet));
273
274 g_assert (sapplet && sapplet->applet && window);
275
276 Display* display = GDK_WINDOW_XDISPLAY (window);
277
278 g_assert (display);
279
280 retval = XkbQueryExtension (display,
281 &opcode_rtn,
282 &xkb_base_event_type,
283 &error_rtn,
284 NULL, NULL);
285
286 if (retval)
287 {
288 retval = XkbSelectEvents (display,
289 XkbUseCoreKbd,
290 XkbAllEventsMask,
291 XkbAllEventsMask);
292 sapplet->xkb = accessx_status_applet_get_xkb_desc (sapplet);
293 }
294 else
295 {
296 sapplet->error_type = ACCESSX_STATUS_ERROR_XKB_DISABLED;
297 }
298
299 return retval;
300 }
301
302 static void
accessx_status_applet_init_modifiers(AccessxStatusApplet * sapplet)303 accessx_status_applet_init_modifiers (AccessxStatusApplet* sapplet)
304 {
305 unsigned int hyper_mask, super_mask, alt_gr_mask;
306
307 unsigned int alt_mask = XkbKeysymToModifiers (sapplet->xkb_display, XK_Alt_L);
308 unsigned int meta_mask = XkbKeysymToModifiers (sapplet->xkb_display, XK_Meta_L);
309
310 g_assert (sapplet->meta_indicator);
311
312 if (meta_mask && (meta_mask != alt_mask))
313 {
314 gtk_widget_show (sapplet->meta_indicator);
315 }
316 else
317 {
318 gtk_widget_hide (sapplet->meta_indicator);
319 }
320
321 hyper_mask = XkbKeysymToModifiers (sapplet->xkb_display, XK_Hyper_L);
322
323 if (hyper_mask)
324 {
325 gtk_widget_show (sapplet->hyper_indicator);
326 }
327 else
328 {
329 gtk_widget_hide (sapplet->hyper_indicator);
330 }
331
332 super_mask = XkbKeysymToModifiers (sapplet->xkb_display, XK_Super_L);
333
334 if (super_mask)
335 {
336 gtk_widget_show (sapplet->super_indicator);
337 }
338 else
339 {
340 gtk_widget_hide (sapplet->super_indicator);
341 }
342
343 alt_gr_mask = XkbKeysymToModifiers (sapplet->xkb_display, XK_Mode_switch) |
344 XkbKeysymToModifiers (sapplet->xkb_display, XK_ISO_Level3_Shift) |
345 XkbKeysymToModifiers (sapplet->xkb_display, XK_ISO_Level3_Latch) |
346 XkbKeysymToModifiers (sapplet->xkb_display, XK_ISO_Level3_Lock);
347
348 if (alt_gr_mask)
349 {
350 gtk_widget_show (sapplet->alt_graph_indicator);
351 }
352 else
353 {
354 gtk_widget_hide (sapplet->alt_graph_indicator);
355 }
356
357 modifiers[modifier_Shift].indicator = sapplet->shift_indicator;
358 modifiers[modifier_Control].indicator = sapplet->ctrl_indicator;
359 modifiers[modifier_Mod1].indicator = sapplet->alt_indicator;
360 modifiers[modifier_Mod2].indicator = sapplet->meta_indicator;
361 modifiers[modifier_Mod3].indicator = sapplet->hyper_indicator;
362 modifiers[modifier_Mod4].indicator = sapplet->super_indicator;
363 modifiers[modifier_Mod5].indicator = sapplet->alt_graph_indicator;
364 }
365
366 static gboolean
timer_reset_slowkeys_image(AccessxStatusApplet * sapplet)367 timer_reset_slowkeys_image (AccessxStatusApplet* sapplet)
368 {
369 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
370 gint icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
371 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
372 cairo_surface_t* surface = gtk_icon_theme_load_surface (icon_theme,
373 SLOWKEYS_IDLE_ICON,
374 icon_size,
375 icon_scale,
376 NULL, 0, NULL);
377
378 gtk_image_set_from_surface (GTK_IMAGE (sapplet->slowfoo), surface);
379 cairo_surface_destroy (surface);
380
381 return G_SOURCE_REMOVE;
382 }
383
384 static gboolean
timer_reset_bouncekeys_image(AccessxStatusApplet * sapplet)385 timer_reset_bouncekeys_image (AccessxStatusApplet* sapplet)
386 {
387 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
388 gint icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
389 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
390 cairo_surface_t* surface = gtk_icon_theme_load_surface (icon_theme,
391 BOUNCEKEYS_ICON,
392 icon_size,
393 icon_scale,
394 NULL, 0, NULL);
395
396 gtk_image_set_from_surface (GTK_IMAGE (sapplet->bouncefoo), surface);
397 cairo_surface_destroy (surface);
398
399 return G_SOURCE_REMOVE;
400 }
401
402 static GdkPixbuf*
accessx_status_applet_get_glyph_pixbuf(GtkWidget * widget,GdkPixbuf * base,GdkRGBA * fg,gchar * glyphstring)403 accessx_status_applet_get_glyph_pixbuf (GtkWidget* widget,
404 GdkPixbuf* base,
405 GdkRGBA* fg,
406 gchar* glyphstring)
407 {
408 GdkPixbuf* glyph_pixbuf;
409 cairo_surface_t *surface;
410 PangoLayout* layout;
411 PangoRectangle ink, logic;
412 PangoContext* pango_context;
413 PangoFontDescription* font_description;
414 static gint font_size = 0;
415 gint w = gdk_pixbuf_get_width (base);
416 gint h = gdk_pixbuf_get_height (base);
417 gint icon_scale = 2;
418 cairo_t *cr;
419
420 surface = gdk_window_create_similar_surface (gdk_get_default_root_window (),
421 CAIRO_CONTENT_COLOR_ALPHA, w, h);
422
423 pango_context = gtk_widget_get_pango_context (widget);
424
425 font_description = pango_context_get_font_description (pango_context);
426 if (font_size == 0)
427 font_size = pango_font_description_get_size (font_description);
428 pango_font_description_set_size (font_description, font_size * icon_scale);
429
430 layout = pango_layout_new (pango_context);
431 pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
432 pango_layout_set_text (layout, glyphstring, -1);
433
434 cr = cairo_create (surface);
435 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
436 gdk_cairo_set_source_rgba (cr, fg);
437
438 pango_layout_get_pixel_extents (layout, &ink, &logic);
439
440 cairo_move_to (cr, (w - ink.x - ink.width)/2, (h - ink.y - ink.height)/2);
441 pango_cairo_show_layout (cr, layout);
442 cairo_destroy (cr);
443
444 g_object_unref (layout);
445 glyph_pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, w, h);
446 cairo_surface_destroy (surface);
447 return glyph_pixbuf;
448 }
449
450 static cairo_surface_t*
accessx_status_applet_altgraph_image(AccessxStatusApplet * sapplet,GtkStateFlags state)451 accessx_status_applet_altgraph_image (AccessxStatusApplet *sapplet,
452 GtkStateFlags state)
453 {
454 GtkIconTheme *icon_theme;
455 GdkPixbuf* pixbuf;
456 GdkPixbuf* glyph_pixbuf;
457 GdkPixbuf* icon_base;
458 cairo_surface_t *surface;
459 GdkRGBA fg;
460 gchar* icon_name;
461 int alpha;
462 int icon_size, icon_scale;
463
464 icon_theme = gtk_icon_theme_get_default ();
465 icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
466 icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
467
468 switch (state)
469 {
470 case GTK_STATE_FLAG_NORMAL:
471 icon_name = ACCESSX_BASE_ICON_BASE;
472 alpha = 255;
473 gdk_rgba_parse (&fg, "black");
474 break;
475 case GTK_STATE_FLAG_SELECTED:
476 icon_name = ACCESSX_BASE_ICON_INVERSE;
477 alpha = 255;
478 gdk_rgba_parse (&fg, "white");
479 break;
480 case GTK_STATE_FLAG_INSENSITIVE:
481 default:
482 icon_name = ACCESSX_BASE_ICON;
483 alpha = 63;
484 gdk_rgba_parse (&fg, "black");
485 break;
486 }
487
488 icon_base = gtk_icon_theme_load_icon_for_scale (icon_theme,
489 icon_name,
490 icon_size,
491 icon_scale,
492 0, NULL);
493 pixbuf = gdk_pixbuf_copy (icon_base);
494 g_object_unref (icon_base);
495 /*
496 * should be N_("ae"));
497 * need en_ locale for this.
498 */
499 /*
500 * Translators: substitute an easily-recognized single glyph
501 * from Level 2, i.e. an AltGraph character from a common keyboard
502 * in your locale.
503 */
504 glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf (GTK_WIDGET (sapplet->applet),
505 pixbuf, &fg, ("æ"));
506 gdk_pixbuf_composite (glyph_pixbuf,
507 pixbuf, 0, 0,
508 gdk_pixbuf_get_width (glyph_pixbuf),
509 gdk_pixbuf_get_height (glyph_pixbuf),
510 0., 0., 1.0, 1.0,
511 GDK_INTERP_NEAREST, alpha);
512 g_object_unref (glyph_pixbuf);
513
514 surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, icon_scale, NULL);
515 g_object_unref (pixbuf);
516
517 return surface;
518 }
519
520 static cairo_surface_t*
accessx_status_applet_slowkeys_image(AccessxStatusApplet * sapplet,XkbAccessXNotifyEvent * event)521 accessx_status_applet_slowkeys_image (AccessxStatusApplet* sapplet,
522 XkbAccessXNotifyEvent* event)
523 {
524 GdkPixbuf* ret_pixbuf;
525 cairo_surface_t *surface;
526 GdkWindow* window;
527 gboolean is_idle = TRUE;
528 gchar* icon_name = SLOWKEYS_IDLE_ICON;
529 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
530 gint icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
531 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
532
533 if (event != NULL)
534 {
535 is_idle = FALSE;
536
537 switch (event->detail)
538 {
539 case XkbAXN_SKPress:
540 icon_name = ACCESSX_BASE_ICON;
541 break;
542 case XkbAXN_SKAccept:
543 icon_name = ACCESSX_ACCEPT_BASE;
544 break;
545 case XkbAXN_SKReject:
546 icon_name = ACCESSX_REJECT_BASE;
547 g_timeout_add_full (G_PRIORITY_HIGH_IDLE,
548 MAX (event->sk_delay, 150),
549 (GSourceFunc)timer_reset_slowkeys_image,
550 sapplet, NULL);
551 break;
552 case XkbAXN_SKRelease:
553 default:
554 icon_name = SLOWKEYS_IDLE_ICON;
555 is_idle = TRUE;
556 break;
557 }
558 }
559
560 ret_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
561 icon_name,
562 icon_size,
563 icon_scale,
564 0, NULL);
565
566 if (!is_idle)
567 {
568 GdkPixbuf* glyph_pixbuf;
569 GdkPixbuf* tmp_pixbuf;
570 GdkRGBA fg;
571 gchar* glyphstring = N_("a");
572 gint alpha;
573 tmp_pixbuf = ret_pixbuf;
574 ret_pixbuf = gdk_pixbuf_copy (tmp_pixbuf);
575 g_object_unref (tmp_pixbuf);
576
577 window = gtk_widget_get_window (GTK_WIDGET (sapplet->applet));
578
579 if (event && window)
580 {
581 KeySym keysym = XkbKeycodeToKeysym (GDK_WINDOW_XDISPLAY (window),
582 event->keycode, 0, 0);
583 glyphstring = XKeysymToString (keysym);
584
585 if ((!g_utf8_validate (glyphstring, -1, NULL)) ||
586 (g_utf8_strlen (glyphstring, -1) > 1))
587 {
588 glyphstring = "";
589 }
590 }
591
592 switch (gtk_widget_get_state_flags (GTK_WIDGET (sapplet->applet)))
593 {
594 case GTK_STATE_FLAG_NORMAL:
595 alpha = 255;
596 gdk_rgba_parse (&fg, "black");
597 break;
598 case GTK_STATE_FLAG_SELECTED:
599 alpha = 255;
600 gdk_rgba_parse (&fg, "white");
601 break;
602 case GTK_STATE_FLAG_INSENSITIVE:
603 default:
604 alpha = 63;
605 gdk_rgba_parse (&fg, "black");
606 break;
607 }
608
609 glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf (GTK_WIDGET (sapplet->applet),
610 ret_pixbuf,
611 &fg,
612 glyphstring);
613
614 gdk_pixbuf_composite (glyph_pixbuf,
615 ret_pixbuf,
616 0, 0,
617 gdk_pixbuf_get_width (glyph_pixbuf),
618 gdk_pixbuf_get_height (glyph_pixbuf),
619 0., 0., 1.0, 1.0,
620 GDK_INTERP_NEAREST, alpha);
621
622 g_object_unref (glyph_pixbuf);
623 }
624
625 surface = gdk_cairo_surface_create_from_pixbuf (ret_pixbuf, icon_scale, NULL);
626 g_object_unref (ret_pixbuf);
627
628 return surface;
629 }
630
631 static cairo_surface_t*
accessx_status_applet_bouncekeys_image(AccessxStatusApplet * sapplet,XkbAccessXNotifyEvent * event)632 accessx_status_applet_bouncekeys_image (AccessxStatusApplet* sapplet,
633 XkbAccessXNotifyEvent* event)
634 {
635 GdkRGBA fg;
636 GdkPixbuf* icon_base = NULL;
637 GdkPixbuf* tmp_pixbuf;
638 cairo_surface_t *surface;
639 /* Note to translators: the first letter of the alphabet, not the indefinite article */
640 gchar* glyphstring = N_("a");
641 gchar* icon_name = ACCESSX_BASE_ICON;
642 gint alpha;
643 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
644 gint icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
645 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
646
647 g_assert (sapplet->applet);
648
649 switch (gtk_widget_get_state_flags (GTK_WIDGET (sapplet->applet)))
650 {
651 case GTK_STATE_FLAG_NORMAL:
652 alpha = 255;
653 gdk_rgba_parse (&fg, "black");
654 break;
655 case GTK_STATE_FLAG_SELECTED:
656 alpha = 255;
657 gdk_rgba_parse (&fg, "white");
658 break;
659 case GTK_STATE_FLAG_INSENSITIVE:
660 default:
661 alpha = 63;
662 gdk_rgba_parse (&fg, "black");
663 break;
664 }
665
666 if (event != NULL)
667 {
668 switch (event->detail)
669 {
670 case XkbAXN_BKAccept:
671 icon_name = SLOWKEYS_ACCEPT_ICON;
672 break;
673 case XkbAXN_BKReject:
674 icon_name = SLOWKEYS_REJECT_ICON;
675 g_timeout_add_full (G_PRIORITY_HIGH_IDLE,
676 MAX (event->debounce_delay, 150),
677 (GSourceFunc)timer_reset_bouncekeys_image,
678 sapplet, NULL);
679 break;
680 default:
681 icon_name = ACCESSX_BASE_ICON;
682 break;
683 }
684 }
685 tmp_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
686 icon_name,
687 icon_size,
688 icon_scale,
689 0, NULL);
690
691 if (tmp_pixbuf)
692 {
693 GdkPixbuf* glyph_pixbuf;
694 icon_base = gdk_pixbuf_copy (tmp_pixbuf);
695 g_object_unref (tmp_pixbuf);
696 glyph_pixbuf = accessx_status_applet_get_glyph_pixbuf (GTK_WIDGET (sapplet->applet),
697 icon_base,
698 &fg,
699 glyphstring);
700
701 gdk_pixbuf_composite (glyph_pixbuf,
702 icon_base,
703 2, 2,
704 gdk_pixbuf_get_width (glyph_pixbuf) - 2,
705 gdk_pixbuf_get_height (glyph_pixbuf) - 2,
706 -2., -2., 1.0, 1.0,
707 GDK_INTERP_NEAREST, 96);
708
709 gdk_pixbuf_composite (glyph_pixbuf,
710 icon_base,
711 1, 1,
712 gdk_pixbuf_get_width (glyph_pixbuf) - 1,
713 gdk_pixbuf_get_height (glyph_pixbuf) - 1,
714 1., 1., 1.0, 1.0,
715 GDK_INTERP_NEAREST, alpha);
716
717 g_object_unref (glyph_pixbuf);
718 }
719
720 surface = gdk_cairo_surface_create_from_pixbuf (icon_base,
721 icon_scale,
722 NULL);
723 g_object_unref (icon_base);
724
725 return surface;
726 }
727
728 static cairo_surface_t*
accessx_status_applet_mousekeys_image(AccessxStatusApplet * sapplet,XkbStateNotifyEvent * event)729 accessx_status_applet_mousekeys_image (AccessxStatusApplet* sapplet,
730 XkbStateNotifyEvent* event)
731 {
732 GdkPixbuf* mouse_pixbuf = NULL, *button_pixbuf, *dot_pixbuf, *tmp_pixbuf;
733 cairo_surface_t *surface;
734 gchar* which_dot = MOUSEKEYS_DOT_LEFT;
735 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
736 gint icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
737 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
738 tmp_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
739 MOUSEKEYS_BASE_ICON,
740 icon_size,
741 icon_scale,
742 0, NULL);
743
744 mouse_pixbuf = gdk_pixbuf_copy (tmp_pixbuf);
745 g_object_unref (tmp_pixbuf);
746 /* composite in the buttons */
747 if (mouse_pixbuf && event && event->ptr_buttons)
748 {
749 gint i;
750
751 for (i = 0; i < G_N_ELEMENTS (button_icons); ++i)
752 {
753 if (event->ptr_buttons & button_icons[i].mask)
754 {
755 button_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
756 button_icons[i].icon_name,
757 icon_size,
758 icon_scale,
759 0, NULL);
760
761 gdk_pixbuf_composite (button_pixbuf,
762 mouse_pixbuf,
763 0, 0,
764 gdk_pixbuf_get_width (button_pixbuf),
765 gdk_pixbuf_get_height (button_pixbuf),
766 0.0, 0.0, 1.0, 1.0,
767 GDK_INTERP_NEAREST, 255);
768
769 g_object_unref (button_pixbuf);
770 }
771 }
772 }
773
774 if (event)
775 {
776 switch (sapplet->xkb->ctrls->mk_dflt_btn)
777 {
778 case Button2:
779 which_dot = MOUSEKEYS_DOT_MIDDLE;
780 break;
781 case Button3:
782 which_dot = MOUSEKEYS_DOT_RIGHT;
783 break;
784 case Button1:
785 default:
786 which_dot = MOUSEKEYS_DOT_LEFT;
787 break;
788 }
789 }
790 dot_pixbuf = gtk_icon_theme_load_icon_for_scale (icon_theme,
791 which_dot,
792 icon_size,
793 icon_scale,
794 0, NULL);
795
796 gdk_pixbuf_composite (dot_pixbuf,
797 mouse_pixbuf,
798 0, 0,
799 gdk_pixbuf_get_width (dot_pixbuf),
800 gdk_pixbuf_get_height (dot_pixbuf),
801 0.0, 0.0, 1.0, 1.0,
802 GDK_INTERP_NEAREST, 255);
803
804 surface = gdk_cairo_surface_create_from_pixbuf (mouse_pixbuf,
805 icon_scale,
806 NULL);
807
808 g_object_unref (mouse_pixbuf);
809 g_object_unref (dot_pixbuf);
810
811 return surface;
812 }
813
814 static void
accessx_status_applet_set_state_icon(AccessxStatusApplet * sapplet,ModifierStruct * modifier,GtkStateFlags state)815 accessx_status_applet_set_state_icon (AccessxStatusApplet* sapplet,
816 ModifierStruct* modifier,
817 GtkStateFlags state)
818 {
819 cairo_surface_t* surface = NULL;
820 GtkIconTheme *icon_theme;
821 gint icon_size, icon_scale;
822 gchar *icon_name = NULL;
823
824 switch (modifier->mask)
825 {
826 case ShiftMask:
827 if (state == GTK_STATE_FLAG_SELECTED)
828 icon_name = SHIFT_KEY_ICON_LOCKED;
829 else if (state == GTK_STATE_FLAG_NORMAL)
830 icon_name = SHIFT_KEY_ICON_LATCHED;
831 else
832 icon_name = SHIFT_KEY_ICON;
833 break;
834
835 case ControlMask:
836 if (state == GTK_STATE_FLAG_SELECTED)
837 icon_name = CONTROL_KEY_ICON_LOCKED;
838 else if (state == GTK_STATE_FLAG_NORMAL)
839 icon_name = CONTROL_KEY_ICON_LATCHED;
840 else
841 icon_name = CONTROL_KEY_ICON;
842 break;
843
844 case Mod1Mask:
845 if (state == GTK_STATE_FLAG_SELECTED)
846 icon_name = ALT_KEY_ICON_LOCKED;
847 else if (state == GTK_STATE_FLAG_NORMAL)
848 icon_name = ALT_KEY_ICON_LATCHED;
849 else
850 icon_name = ALT_KEY_ICON;
851 break;
852
853 case Mod2Mask:
854 if (state == GTK_STATE_FLAG_SELECTED)
855 icon_name = META_KEY_ICON_LOCKED;
856 else if (state == GTK_STATE_FLAG_NORMAL)
857 icon_name = META_KEY_ICON_LATCHED;
858 else
859 icon_name = META_KEY_ICON;
860 break;
861
862 case Mod3Mask:
863 if (state == GTK_STATE_FLAG_SELECTED)
864 icon_name = HYPER_KEY_ICON_LOCKED;
865 else if (state == GTK_STATE_FLAG_NORMAL)
866 icon_name = HYPER_KEY_ICON_LATCHED;
867 else
868 icon_name = HYPER_KEY_ICON;
869 break;
870
871 case Mod4Mask:
872 if (state == GTK_STATE_FLAG_SELECTED)
873 icon_name = SUPER_KEY_ICON_LOCKED;
874 else if (state == GTK_STATE_FLAG_NORMAL)
875 icon_name = SUPER_KEY_ICON_LATCHED;
876 else
877 icon_name = SUPER_KEY_ICON;
878 break;
879
880 case Mod5Mask:
881 surface = accessx_status_applet_altgraph_image (sapplet, state);
882 break;
883 }
884
885 if (surface == NULL && icon_name != NULL)
886 {
887 icon_theme = gtk_icon_theme_get_default ();
888 icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
889 icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
890 surface = gtk_icon_theme_load_surface (icon_theme,
891 icon_name,
892 icon_size,
893 icon_scale,
894 NULL, 0, NULL);
895 }
896
897 if (surface != NULL)
898 {
899 gtk_image_set_from_surface (GTK_IMAGE (modifier->indicator), surface);
900 cairo_surface_destroy (surface);
901 }
902 }
903
904 static void
accessx_status_applet_update(AccessxStatusApplet * sapplet,AccessxStatusNotifyType notify_type,XkbEvent * event)905 accessx_status_applet_update (AccessxStatusApplet* sapplet,
906 AccessxStatusNotifyType notify_type,
907 XkbEvent* event)
908 {
909 GdkWindow* window;
910 gint i;
911
912 window = gtk_widget_get_window (GTK_WIDGET (sapplet->applet));
913
914 if (notify_type & ACCESSX_STATUS_MODIFIERS)
915 {
916 unsigned int locked_mods = 0, latched_mods = 0;
917
918 if (event != NULL)
919 {
920 locked_mods = event->state.locked_mods;
921 latched_mods = event->state.latched_mods;
922 }
923 else if (sapplet->applet && window)
924 {
925 XkbStateRec state;
926 XkbGetState (GDK_WINDOW_XDISPLAY (window), XkbUseCoreKbd, &state);
927 locked_mods = state.locked_mods;
928 latched_mods = state.latched_mods;
929 }
930 /* determine which modifiers are locked, and set state accordingly */
931 for (i = 0; i < modifier_n; ++i)
932 {
933 if (modifiers[i].indicator != NULL && modifiers[i].mask)
934 {
935 if (locked_mods & modifiers[i].mask)
936 {
937 gtk_widget_set_sensitive (modifiers[i].indicator, TRUE);
938 accessx_status_applet_set_state_icon (sapplet,
939 &modifiers[i],
940 GTK_STATE_FLAG_SELECTED);
941 }
942 else if (latched_mods & modifiers[i].mask)
943 {
944 gtk_widget_set_sensitive (modifiers[i].indicator, TRUE);
945 accessx_status_applet_set_state_icon (sapplet,
946 &modifiers[i],
947 GTK_STATE_FLAG_NORMAL);
948 }
949 else
950 {
951 gtk_widget_set_sensitive (modifiers[i].indicator, FALSE);
952 accessx_status_applet_set_state_icon (sapplet,
953 &modifiers[i],
954 GTK_STATE_FLAG_INSENSITIVE);
955 }
956 }
957 }
958 }
959
960 if ((notify_type & ACCESSX_STATUS_SLOWKEYS) && (event != NULL))
961 {
962 cairo_surface_t* surface = accessx_status_applet_slowkeys_image (sapplet,
963 &event->accessx);
964 gtk_image_set_from_surface (GTK_IMAGE (sapplet->slowfoo), surface);
965 cairo_surface_destroy (surface);
966 }
967
968 if ((notify_type & ACCESSX_STATUS_BOUNCEKEYS) && (event != NULL))
969 {
970 cairo_surface_t* surface = accessx_status_applet_bouncekeys_image (sapplet,
971 &event->accessx);
972 gtk_image_set_from_surface (GTK_IMAGE (sapplet->bouncefoo), surface);
973 cairo_surface_destroy (surface);
974 }
975
976 if ((notify_type & ACCESSX_STATUS_MOUSEKEYS) && (event != NULL))
977 {
978 cairo_surface_t* surface = accessx_status_applet_mousekeys_image (sapplet,
979 &event->state);
980 gtk_image_set_from_surface (GTK_IMAGE (sapplet->mousefoo), surface);
981 cairo_surface_destroy (surface);
982 }
983
984 if (notify_type & ACCESSX_STATUS_ENABLED)
985 {
986 /* Update the visibility of widgets in the box */
987 /* XkbMouseKeysMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask */
988 XkbGetControls (GDK_WINDOW_XDISPLAY (window), XkbAllControlsMask, sapplet->xkb);
989
990 if (!(sapplet->xkb->ctrls->enabled_ctrls &
991 (XkbMouseKeysMask | XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask)))
992 {
993 gtk_widget_show (sapplet->idlefoo);
994 }
995 else
996 {
997 gtk_widget_hide (sapplet->idlefoo);
998 }
999
1000 if (sapplet->xkb->ctrls->enabled_ctrls & XkbMouseKeysMask)
1001 {
1002 gtk_widget_show (sapplet->mousefoo);
1003 }
1004 else
1005 {
1006 gtk_widget_hide (sapplet->mousefoo);
1007 }
1008
1009 if (sapplet->xkb->ctrls->enabled_ctrls & XkbStickyKeysMask)
1010 {
1011 gtk_widget_show (sapplet->stickyfoo);
1012 }
1013 else
1014 {
1015 gtk_widget_hide (sapplet->stickyfoo);
1016 }
1017
1018 if (sapplet->xkb->ctrls->enabled_ctrls & XkbSlowKeysMask)
1019 {
1020 gtk_widget_show (sapplet->slowfoo);
1021 }
1022 else
1023 {
1024 gtk_widget_hide (sapplet->slowfoo);
1025 }
1026
1027 if (sapplet->xkb->ctrls->enabled_ctrls & XkbBounceKeysMask)
1028 {
1029 gtk_widget_show (sapplet->bouncefoo);
1030 }
1031 else
1032 {
1033 gtk_widget_hide (sapplet->bouncefoo);
1034 }
1035 }
1036
1037 return;
1038 }
1039
1040 static void
accessx_status_applet_notify_xkb_ax(AccessxStatusApplet * sapplet,XkbAccessXNotifyEvent * event)1041 accessx_status_applet_notify_xkb_ax (AccessxStatusApplet* sapplet,
1042 XkbAccessXNotifyEvent* event)
1043 {
1044 AccessxStatusNotifyType notify_mask = 0;
1045
1046 switch (event->detail)
1047 {
1048 case XkbAXN_SKPress:
1049 case XkbAXN_SKAccept:
1050 case XkbAXN_SKRelease:
1051 case XkbAXN_SKReject:
1052 notify_mask |= ACCESSX_STATUS_SLOWKEYS;
1053 break;
1054 case XkbAXN_BKAccept:
1055 case XkbAXN_BKReject:
1056 notify_mask |= ACCESSX_STATUS_BOUNCEKEYS;
1057 break;
1058 case XkbAXN_AXKWarning:
1059 break;
1060 default:
1061 break;
1062 }
1063
1064 accessx_status_applet_update (sapplet,
1065 notify_mask,
1066 (XkbEvent*) event);
1067 }
1068
1069 static void
accessx_status_applet_notify_xkb_state(AccessxStatusApplet * sapplet,XkbStateNotifyEvent * event)1070 accessx_status_applet_notify_xkb_state (AccessxStatusApplet* sapplet,
1071 XkbStateNotifyEvent* event)
1072 {
1073 AccessxStatusNotifyType notify_mask = 0;
1074
1075 if (event->changed & XkbPointerButtonMask)
1076 {
1077 notify_mask |= ACCESSX_STATUS_MOUSEKEYS;
1078 }
1079
1080 if (event->changed & (XkbModifierLatchMask | XkbModifierLockMask))
1081 {
1082 notify_mask |= ACCESSX_STATUS_MODIFIERS;
1083 }
1084
1085 accessx_status_applet_update (sapplet,
1086 notify_mask,
1087 (XkbEvent*) event);
1088 }
1089
1090 static void
accessx_status_applet_notify_xkb_device(AccessxStatusApplet * sapplet,XkbExtensionDeviceNotifyEvent * event)1091 accessx_status_applet_notify_xkb_device (AccessxStatusApplet* sapplet,
1092 XkbExtensionDeviceNotifyEvent* event)
1093 {
1094 if (event->reason == XkbXI_IndicatorStateMask)
1095 {
1096 if (event->led_state &= ALT_GRAPH_LED_MASK)
1097 {
1098 gtk_widget_set_sensitive (sapplet->alt_graph_indicator, TRUE);
1099 accessx_status_applet_set_state_icon (sapplet,
1100 &modifiers[modifier_Mod5],
1101 GTK_STATE_FLAG_NORMAL);
1102 }
1103 else
1104 {
1105 gtk_widget_set_sensitive (sapplet->alt_graph_indicator, FALSE);
1106 accessx_status_applet_set_state_icon (sapplet,
1107 &modifiers[modifier_Mod5],
1108 GTK_STATE_FLAG_INSENSITIVE);
1109 }
1110 }
1111 }
1112
1113 static void
accessx_status_applet_notify_xkb_controls(AccessxStatusApplet * sapplet,XkbControlsNotifyEvent * event)1114 accessx_status_applet_notify_xkb_controls (AccessxStatusApplet* sapplet,
1115 XkbControlsNotifyEvent* event)
1116 {
1117 unsigned int mask = XkbStickyKeysMask | XkbSlowKeysMask | XkbBounceKeysMask | XkbMouseKeysMask;
1118 unsigned int notify_mask = 0;
1119
1120 XkbGetControls (sapplet->xkb_display, XkbMouseKeysMask, sapplet->xkb);
1121
1122 if (event->enabled_ctrl_changes & mask)
1123 {
1124 notify_mask = ACCESSX_STATUS_ENABLED;
1125 }
1126
1127 if (event->changed_ctrls & XkbMouseKeysMask)
1128 {
1129 notify_mask |= ACCESSX_STATUS_MOUSEKEYS;
1130 }
1131
1132 if (notify_mask)
1133 {
1134 accessx_status_applet_update (sapplet, notify_mask, (XkbEvent*) event);
1135 }
1136 }
1137
1138 static void
accessx_status_applet_notify_xkb_event(AccessxStatusApplet * sapplet,XkbEvent * event)1139 accessx_status_applet_notify_xkb_event (AccessxStatusApplet* sapplet,
1140 XkbEvent* event)
1141 {
1142 switch (event->any.xkb_type)
1143 {
1144 case XkbStateNotify:
1145 accessx_status_applet_notify_xkb_state (sapplet, &event->state);
1146 break;
1147 case XkbAccessXNotify:
1148 accessx_status_applet_notify_xkb_ax (sapplet, &event->accessx);
1149 break;
1150 case XkbControlsNotify:
1151 accessx_status_applet_notify_xkb_controls (sapplet, &event->ctrls);
1152 break;
1153 case XkbExtensionDeviceNotify:
1154 /* This is a hack around the fact that XFree86's XKB doesn't give AltGr notifications */
1155 accessx_status_applet_notify_xkb_device (sapplet, &event->device);
1156 break;
1157 default:
1158 break;
1159 }
1160 }
1161
1162 static
accessx_status_xkb_filter(GdkXEvent * gdk_xevent,GdkEvent * event,gpointer user_data)1163 GdkFilterReturn accessx_status_xkb_filter (GdkXEvent* gdk_xevent,
1164 GdkEvent* event,
1165 gpointer user_data)
1166 {
1167 AccessxStatusApplet* sapplet = user_data;
1168 XkbEvent* xevent = gdk_xevent;
1169
1170 if (xevent->any.type == xkb_base_event_type)
1171 {
1172 accessx_status_applet_notify_xkb_event (sapplet, xevent);
1173 }
1174
1175 return GDK_FILTER_CONTINUE;
1176 }
1177
1178 static void
accessx_status_applet_reparent_widget(GtkWidget * widget,GtkContainer * container)1179 accessx_status_applet_reparent_widget (GtkWidget* widget,
1180 GtkContainer* container)
1181 {
1182 if (widget)
1183 {
1184 if (gtk_widget_get_parent (widget))
1185 {
1186 g_object_ref (G_OBJECT (widget));
1187 gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)),
1188 widget);
1189 }
1190
1191 gtk_container_add (container, widget);
1192 }
1193 }
1194
1195 static void
accessx_status_applet_layout_box(AccessxStatusApplet * sapplet,GtkWidget * box,GtkWidget * stickyfoo)1196 accessx_status_applet_layout_box (AccessxStatusApplet* sapplet,
1197 GtkWidget* box,
1198 GtkWidget* stickyfoo)
1199 {
1200 AtkObject* atko;
1201
1202 accessx_status_applet_reparent_widget (sapplet->shift_indicator,
1203 GTK_CONTAINER (stickyfoo));
1204 accessx_status_applet_reparent_widget (sapplet->ctrl_indicator,
1205 GTK_CONTAINER (stickyfoo));
1206 accessx_status_applet_reparent_widget (sapplet->alt_indicator,
1207 GTK_CONTAINER (stickyfoo));
1208 accessx_status_applet_reparent_widget (sapplet->meta_indicator,
1209 GTK_CONTAINER (stickyfoo));
1210 accessx_status_applet_reparent_widget (sapplet->hyper_indicator,
1211 GTK_CONTAINER (stickyfoo));
1212 accessx_status_applet_reparent_widget (sapplet->super_indicator,
1213 GTK_CONTAINER (stickyfoo));
1214 accessx_status_applet_reparent_widget (sapplet->alt_graph_indicator,
1215 GTK_CONTAINER (stickyfoo));
1216 accessx_status_applet_reparent_widget (sapplet->idlefoo,
1217 GTK_CONTAINER (box));
1218 accessx_status_applet_reparent_widget (sapplet->mousefoo,
1219 GTK_CONTAINER (box));
1220 accessx_status_applet_reparent_widget (stickyfoo,
1221 GTK_CONTAINER (box));
1222 accessx_status_applet_reparent_widget (sapplet->slowfoo,
1223 GTK_CONTAINER (box));
1224 accessx_status_applet_reparent_widget (sapplet->bouncefoo,
1225 GTK_CONTAINER (box));
1226
1227 if (sapplet->stickyfoo)
1228 {
1229 gtk_widget_destroy (sapplet->stickyfoo);
1230 }
1231
1232 if (sapplet->box)
1233 {
1234 gtk_container_remove (GTK_CONTAINER (sapplet->applet), sapplet->box);
1235 }
1236
1237 gtk_container_add (GTK_CONTAINER (sapplet->applet), box);
1238 sapplet->stickyfoo = stickyfoo;
1239 sapplet->box = box;
1240
1241 atko = gtk_widget_get_accessible (sapplet->box);
1242 atk_object_set_name (atko, _("AccessX Status"));
1243 atk_object_set_description (atko,
1244 _("Shows keyboard status when accessibility features are used."));
1245
1246 gtk_widget_show (sapplet->box);
1247 gtk_widget_show (GTK_WIDGET (sapplet->applet));
1248
1249 if (gtk_widget_get_realized (sapplet->box) && sapplet->initialized)
1250 {
1251 accessx_status_applet_update (sapplet, ACCESSX_STATUS_ALL, NULL);
1252 }
1253 }
1254
1255 static void
disable_applet(AccessxStatusApplet * sapplet)1256 disable_applet (AccessxStatusApplet* sapplet)
1257 {
1258 gtk_widget_hide (sapplet->meta_indicator);
1259 gtk_widget_hide (sapplet->hyper_indicator);
1260 gtk_widget_hide (sapplet->super_indicator);
1261 gtk_widget_hide (sapplet->alt_graph_indicator);
1262 gtk_widget_hide (sapplet->shift_indicator);
1263 gtk_widget_hide (sapplet->ctrl_indicator);
1264 gtk_widget_hide (sapplet->alt_indicator);
1265 gtk_widget_hide (sapplet->mousefoo);
1266 gtk_widget_hide (sapplet->stickyfoo);
1267 gtk_widget_hide (sapplet->slowfoo);
1268 gtk_widget_hide (sapplet->bouncefoo);
1269 }
1270
1271 static void
popup_error_dialog(AccessxStatusApplet * sapplet)1272 popup_error_dialog (AccessxStatusApplet* sapplet)
1273 {
1274 GtkWidget* dialog;
1275 gchar* error_txt;
1276
1277 switch (sapplet->error_type)
1278 {
1279 case ACCESSX_STATUS_ERROR_XKB_DISABLED:
1280 error_txt = g_strdup (_("XKB Extension is not enabled"));
1281 break;
1282
1283 case ACCESSX_STATUS_ERROR_UNKNOWN:
1284
1285 default: error_txt = g_strdup (_("Unknown error"));
1286 break;
1287 }
1288
1289 dialog = gtk_message_dialog_new (NULL,
1290 GTK_DIALOG_DESTROY_WITH_PARENT,
1291 GTK_MESSAGE_ERROR,
1292 GTK_BUTTONS_CLOSE,
1293 _("Error: %s"),
1294 error_txt);
1295
1296 g_signal_connect (dialog, "response",
1297 G_CALLBACK (gtk_widget_destroy),
1298 NULL);
1299
1300 gtk_window_set_screen (GTK_WINDOW (dialog),
1301 gtk_widget_get_screen (GTK_WIDGET (sapplet->applet)));
1302
1303 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1304
1305 gtk_widget_show (dialog);
1306 g_free (error_txt);
1307 }
1308
1309 static AccessxStatusApplet*
create_applet(MatePanelApplet * applet)1310 create_applet (MatePanelApplet* applet)
1311 {
1312 AccessxStatusApplet* sapplet = g_new0 (AccessxStatusApplet, 1);
1313 GtkWidget* box;
1314 GtkWidget* stickyfoo;
1315 AtkObject* atko;
1316 cairo_surface_t *surface;
1317 GtkIconTheme *icon_theme;
1318 gint icon_size, icon_scale;
1319
1320 g_set_application_name (_("AccessX Status"));
1321
1322 sapplet->xkb = NULL;
1323 sapplet->xkb_display = NULL;
1324 sapplet->box = NULL;
1325 sapplet->initialized = False; /* there must be a better way */
1326 sapplet->error_type = ACCESSX_STATUS_ERROR_NONE;
1327 sapplet->applet = applet;
1328 mate_panel_applet_set_flags (applet, MATE_PANEL_APPLET_EXPAND_MINOR);
1329 sapplet->orient = mate_panel_applet_get_orient (applet);
1330
1331 if (sapplet->orient == MATE_PANEL_APPLET_ORIENT_LEFT ||
1332 sapplet->orient == MATE_PANEL_APPLET_ORIENT_RIGHT)
1333 {
1334 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1335 stickyfoo = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1336 }
1337 else
1338 {
1339 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1340 stickyfoo = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1341 }
1342
1343 gtk_box_set_homogeneous (GTK_BOX (stickyfoo), TRUE);
1344
1345 icon_theme = gtk_icon_theme_get_default ();
1346 icon_size = mate_panel_applet_get_size (sapplet->applet) - ICON_PADDING;
1347 icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
1348
1349 surface = accessx_status_applet_mousekeys_image (sapplet, NULL);
1350 sapplet->mousefoo = gtk_image_new_from_surface (surface);
1351 cairo_surface_destroy (surface);
1352 gtk_widget_hide (sapplet->mousefoo);
1353
1354 surface = gtk_icon_theme_load_surface (icon_theme,
1355 SHIFT_KEY_ICON,
1356 icon_size,
1357 icon_scale,
1358 NULL, 0, NULL);
1359
1360 sapplet->shift_indicator = gtk_image_new_from_surface (surface);
1361 cairo_surface_destroy (surface);
1362
1363 surface = gtk_icon_theme_load_surface (icon_theme,
1364 CONTROL_KEY_ICON,
1365 icon_size, icon_scale,
1366 NULL, 0, NULL);
1367
1368 sapplet->ctrl_indicator = gtk_image_new_from_surface (surface);
1369 cairo_surface_destroy (surface);
1370
1371 surface = gtk_icon_theme_load_surface (icon_theme,
1372 ALT_KEY_ICON,
1373 icon_size,
1374 icon_scale,
1375 NULL, 0, NULL);
1376
1377 sapplet->alt_indicator = gtk_image_new_from_surface (surface);
1378 cairo_surface_destroy (surface);
1379
1380 surface = gtk_icon_theme_load_surface (icon_theme,
1381 META_KEY_ICON,
1382 icon_size,
1383 icon_scale,
1384 NULL, 0, NULL);
1385
1386 sapplet->meta_indicator = gtk_image_new_from_surface (surface);
1387 cairo_surface_destroy (surface);
1388 gtk_widget_set_sensitive (sapplet->meta_indicator, FALSE);
1389 gtk_widget_hide (sapplet->meta_indicator);
1390
1391 surface = gtk_icon_theme_load_surface (icon_theme,
1392 HYPER_KEY_ICON,
1393 icon_size,
1394 icon_scale,
1395 NULL, 0, NULL);
1396
1397 sapplet->hyper_indicator = gtk_image_new_from_surface (surface);
1398 cairo_surface_destroy (surface);
1399 gtk_widget_set_sensitive (sapplet->hyper_indicator, FALSE);
1400 gtk_widget_hide (sapplet->hyper_indicator);
1401
1402 surface = gtk_icon_theme_load_surface (icon_theme,
1403 SUPER_KEY_ICON,
1404 icon_size,
1405 icon_scale,
1406 NULL, 0, NULL);
1407
1408 sapplet->super_indicator = gtk_image_new_from_surface (surface);
1409 cairo_surface_destroy (surface);
1410 gtk_widget_set_sensitive (sapplet->super_indicator, FALSE);
1411 gtk_widget_hide (sapplet->super_indicator);
1412
1413 surface = accessx_status_applet_altgraph_image (sapplet,
1414 GTK_STATE_FLAG_NORMAL);
1415
1416 sapplet->alt_graph_indicator = gtk_image_new_from_surface (surface);
1417 cairo_surface_destroy (surface);
1418 gtk_widget_set_sensitive (sapplet->alt_graph_indicator, FALSE);
1419
1420 surface = accessx_status_applet_slowkeys_image (sapplet, NULL);
1421 sapplet->slowfoo = gtk_image_new_from_surface (surface);
1422 cairo_surface_destroy (surface);
1423 gtk_widget_hide (sapplet->slowfoo);
1424
1425 surface = accessx_status_applet_bouncekeys_image (sapplet, NULL);
1426 sapplet->bouncefoo = gtk_image_new_from_surface (surface);
1427 cairo_surface_destroy (surface);
1428 gtk_widget_hide (sapplet->bouncefoo);
1429
1430 surface = gtk_icon_theme_load_surface (icon_theme,
1431 ACCESSX_APPLET,
1432 icon_size,
1433 icon_scale,
1434 NULL, 0, NULL);
1435
1436 sapplet->idlefoo = gtk_image_new_from_surface (surface);
1437 cairo_surface_destroy (surface);
1438 gtk_widget_show (sapplet->idlefoo);
1439
1440 accessx_status_applet_layout_box (sapplet, box, stickyfoo);
1441 atko = gtk_widget_get_accessible (GTK_WIDGET (sapplet->applet));
1442 atk_object_set_name (atko, _("AccessX Status"));
1443 atk_object_set_description (atko,
1444 _("Shows keyboard status when accessibility features are used."));
1445 return sapplet;
1446 }
1447
1448 static void
accessx_status_applet_destroy(GtkWidget * widget,gpointer user_data)1449 accessx_status_applet_destroy (GtkWidget* widget,
1450 gpointer user_data)
1451 {
1452 AccessxStatusApplet* sapplet = user_data;
1453 /* do we need to free the icon factory ? */
1454
1455 gdk_window_remove_filter (NULL, accessx_status_xkb_filter, sapplet);
1456
1457 if (sapplet->xkb)
1458 {
1459 XkbFreeKeyboard (sapplet->xkb, 0, True);
1460 }
1461
1462 if (sapplet->xkb_display)
1463 {
1464 XCloseDisplay (sapplet->xkb_display);
1465 }
1466 }
1467
1468 static void
accessx_status_applet_reorient(GtkWidget * widget,MatePanelAppletOrient o,gpointer user_data)1469 accessx_status_applet_reorient (GtkWidget* widget,
1470 MatePanelAppletOrient o,
1471 gpointer user_data)
1472 {
1473 AccessxStatusApplet* sapplet = user_data;
1474 GtkWidget* box;
1475 GtkWidget* stickyfoo;
1476
1477 sapplet->orient = o;
1478
1479 if (o == MATE_PANEL_APPLET_ORIENT_LEFT || o == MATE_PANEL_APPLET_ORIENT_RIGHT)
1480 {
1481 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1482 stickyfoo = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1483 }
1484 else
1485 {
1486 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1487 stickyfoo = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1488 }
1489 gtk_box_set_homogeneous (GTK_BOX (stickyfoo), TRUE);
1490 accessx_status_applet_layout_box (sapplet, box, stickyfoo);
1491 }
1492
1493 static void
accessx_status_applet_resize(GtkWidget * widget,int size,gpointer user_data)1494 accessx_status_applet_resize (GtkWidget* widget,
1495 int size,
1496 gpointer user_data)
1497 {
1498 cairo_surface_t *surface;
1499
1500 AccessxStatusApplet* sapplet = user_data;
1501 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
1502 gint icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sapplet->applet));
1503
1504 accessx_status_applet_update (sapplet, ACCESSX_STATUS_ALL, NULL);
1505
1506 surface = accessx_status_applet_slowkeys_image (sapplet, NULL);
1507 gtk_image_set_from_surface (GTK_IMAGE (sapplet->slowfoo), surface);
1508 cairo_surface_destroy (surface);
1509
1510 surface = accessx_status_applet_bouncekeys_image (sapplet, NULL);
1511 gtk_image_set_from_surface (GTK_IMAGE (sapplet->bouncefoo), surface);
1512 cairo_surface_destroy (surface);
1513
1514 surface = accessx_status_applet_mousekeys_image (sapplet, NULL);
1515 gtk_image_set_from_surface (GTK_IMAGE (sapplet->mousefoo), surface);
1516 cairo_surface_destroy (surface);
1517
1518 surface = gtk_icon_theme_load_surface (icon_theme,
1519 ACCESSX_APPLET, size - ICON_PADDING,
1520 icon_scale,
1521 NULL, 0, NULL);
1522
1523 gtk_image_set_from_surface (GTK_IMAGE (sapplet->idlefoo), surface);
1524 cairo_surface_destroy (surface);
1525 }
1526
1527 static gboolean
button_press_cb(GtkWidget * widget,GdkEventButton * event,AccessxStatusApplet * sapplet)1528 button_press_cb (GtkWidget* widget,
1529 GdkEventButton* event,
1530 AccessxStatusApplet* sapplet)
1531 {
1532 if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
1533 {
1534 dialog_cb (NULL, sapplet);
1535 }
1536
1537 return FALSE;
1538 }
1539
1540 static gboolean
key_press_cb(GtkWidget * widget,GdkEventKey * event,AccessxStatusApplet * sapplet)1541 key_press_cb (GtkWidget* widget,
1542 GdkEventKey* event,
1543 AccessxStatusApplet* sapplet)
1544 {
1545 switch (event->keyval)
1546 {
1547 case GDK_KEY_KP_Enter:
1548 case GDK_KEY_ISO_Enter:
1549 case GDK_KEY_3270_Enter:
1550 case GDK_KEY_Return:
1551 case GDK_KEY_space:
1552 case GDK_KEY_KP_Space:
1553 dialog_cb (NULL, sapplet);
1554 return TRUE;
1555
1556 default:
1557 break;
1558 }
1559
1560 return FALSE;
1561 }
1562
1563 static gboolean
accessx_status_applet_reset(gpointer user_data)1564 accessx_status_applet_reset (gpointer user_data)
1565 {
1566 AccessxStatusApplet* sapplet = user_data;
1567 g_assert (sapplet->applet);
1568 accessx_status_applet_reorient (GTK_WIDGET (sapplet->applet),
1569 mate_panel_applet_get_orient (sapplet->applet),
1570 sapplet);
1571
1572 return FALSE;
1573 }
1574
1575 static gboolean
accessx_status_applet_initialize(AccessxStatusApplet * sapplet)1576 accessx_status_applet_initialize (AccessxStatusApplet* sapplet)
1577 {
1578 if (!sapplet->initialized)
1579 {
1580 sapplet->initialized = True;
1581
1582 if (!accessx_status_applet_xkb_select (sapplet))
1583 {
1584 disable_applet (sapplet);
1585 popup_error_dialog (sapplet);
1586 return FALSE ;
1587 }
1588
1589 gdk_window_add_filter (NULL, accessx_status_xkb_filter, sapplet);
1590 }
1591
1592 accessx_status_applet_init_modifiers (sapplet);
1593 accessx_status_applet_update (sapplet, ACCESSX_STATUS_ALL, NULL);
1594
1595 return TRUE;
1596 }
1597
1598 static void
accessx_status_applet_realize(GtkWidget * widget,gpointer user_data)1599 accessx_status_applet_realize (GtkWidget* widget,
1600 gpointer user_data)
1601 {
1602 AccessxStatusApplet* sapplet = user_data;
1603
1604 if (!accessx_status_applet_initialize (sapplet))
1605 {
1606 return;
1607 }
1608
1609 g_idle_add (accessx_status_applet_reset, sapplet);
1610
1611 return;
1612 }
1613
1614 static gboolean
accessx_status_applet_fill(MatePanelApplet * applet)1615 accessx_status_applet_fill (MatePanelApplet* applet)
1616 {
1617 AccessxStatusApplet* sapplet;
1618 AtkObject* atk_object;
1619 GtkActionGroup* action_group;
1620 gboolean was_realized = FALSE;
1621
1622 sapplet = create_applet (applet);
1623
1624 if (!gtk_widget_get_realized (sapplet->box))
1625 {
1626 g_signal_connect_after (sapplet->box, "realize",
1627 G_CALLBACK (accessx_status_applet_realize),
1628 sapplet);
1629 }
1630 else
1631 {
1632 accessx_status_applet_initialize (sapplet);
1633 was_realized = TRUE;
1634 }
1635
1636 g_object_connect (sapplet->applet,
1637 "signal::destroy", accessx_status_applet_destroy, sapplet,
1638 "signal::change_orient", accessx_status_applet_reorient, sapplet,
1639 "signal::change_size", accessx_status_applet_resize, sapplet,
1640 NULL);
1641
1642 g_signal_connect (sapplet->applet, "button_press_event",
1643 G_CALLBACK (button_press_cb),
1644 sapplet);
1645
1646 g_signal_connect (sapplet->applet, "key_press_event",
1647 G_CALLBACK (key_press_cb),
1648 sapplet);
1649
1650 action_group = gtk_action_group_new ("Accessx Applet Actions");
1651 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
1652 gtk_action_group_add_actions (action_group,
1653 accessx_status_applet_menu_actions,
1654 G_N_ELEMENTS (accessx_status_applet_menu_actions),
1655 sapplet);
1656
1657 mate_panel_applet_setup_menu_from_resource (sapplet->applet,
1658 ACCESSX_RESOURCE_PATH "accessx-status-applet-menu.xml",
1659 action_group);
1660
1661 if (mate_panel_applet_get_locked_down (sapplet->applet))
1662 {
1663 GtkAction* action = gtk_action_group_get_action (action_group, "Dialog");
1664 gtk_action_set_visible (action, FALSE);
1665 }
1666
1667 g_object_unref (action_group);
1668
1669 gtk_widget_set_tooltip_text (GTK_WIDGET (sapplet->applet),
1670 _("Keyboard Accessibility Status"));
1671
1672 atk_object = gtk_widget_get_accessible (GTK_WIDGET (sapplet->applet));
1673 atk_object_set_name (atk_object, _("AccessX Status"));
1674 atk_object_set_description (atk_object,
1675 _("Displays current state of keyboard accessibility features"));
1676 gtk_widget_show_all (GTK_WIDGET (sapplet->applet));
1677
1678 if (was_realized)
1679 {
1680 accessx_status_applet_reset (sapplet);
1681 }
1682
1683 return TRUE;
1684 }
1685
1686 static gboolean
accessx_status_applet_factory(MatePanelApplet * applet,const gchar * iid,gpointer data)1687 accessx_status_applet_factory (MatePanelApplet* applet,
1688 const gchar* iid,
1689 gpointer data)
1690 {
1691 gboolean retval = FALSE;
1692
1693 if (!strcmp (iid, "AccessxStatusApplet"))
1694 {
1695 retval = accessx_status_applet_fill (applet);
1696 }
1697
1698 return retval;
1699 }
1700
1701 MATE_PANEL_APPLET_OUT_PROCESS_FACTORY ("AccessxStatusAppletFactory",
1702 PANEL_TYPE_APPLET,
1703 "accessx-status",
1704 accessx_status_applet_factory,
1705 NULL)
1706
1707