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