[gnome-flashback] a11y-keyboard: initial version



commit 15a4900dfb17860b7c60476a6e6bb3eddd622eaf
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Fri Sep 7 15:15:11 2018 +0300

    a11y-keyboard: initial version
    
    GNOME Settings Daemon removed a11y-keyboard plugin in 9553071d0c26
    commit because related functionality has been moved to mutter.
    
    Fix this by adding new a11y-keyboard module in gnome-flashback. This
    module is heavily based on original plugin from gnome-settings-daemon
    and should provide same functionality.
    
    https://gitlab.gnome.org/GNOME/gnome-flashback/issues/6

 configure.ac                                       |  10 +-
 data/schemas/org.gnome.gnome-flashback.gschema.xml |   5 +
 gnome-flashback/Makefile.am                        |   2 +
 gnome-flashback/gf-application.c                   |   4 +
 gnome-flashback/liba11y-keyboard/Makefile.am       |  50 ++
 .../liba11y-keyboard/gf-a11y-keyboard.c            | 941 +++++++++++++++++++++
 .../liba11y-keyboard/gf-a11y-keyboard.h            |  33 +
 .../org.freedesktop.Notifications.xml              |  45 +
 po/POTFILES.in                                     |   1 +
 9 files changed, 1090 insertions(+), 1 deletion(-)
---
diff --git a/configure.ac b/configure.ac
index 0d22748..f18a39d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,7 +92,7 @@ GTK_REQUIRED=3.22.0
 LIBGNOME_DESKTOP_REQUIRED=3.12.0
 CANBERRA_REQUIRED=0.13
 GLIB_REQUIRED=2.44.0
-GSETTINGS_DESKTOP_SCHEMAS_REQUIRED=3.21.4
+GSETTINGS_DESKTOP_SCHEMAS_REQUIRED=3.24.0
 POLKIT_AGENT_REQUIRED=0.97
 POLKIT_GOBJECT_REQUIRED=0.97
 IBUS_REQUIRED=1.5.2
@@ -115,6 +115,13 @@ PKG_CHECK_MODULES([GNOME_FLASHBACK], [
   gtk+-3.0 >= $GTK_REQUIRED
 ])
 
+PKG_CHECK_MODULES([A11Y_KEYBOARD], [
+  gio-unix-2.0 >= $GLIB_REQUIRED
+  gtk+-3.0
+  gsettings-desktop-schemas >= $GSETTINGS_DESKTOP_SCHEMAS_REQUIRED
+  x11
+])
+
 PKG_CHECK_MODULES([AUDIO_DEVICE_SELECTION], [
   gio-unix-2.0 >= $GLIB_REQUIRED
   gtk+-3.0 >= $GTK_REQUIRED
@@ -290,6 +297,7 @@ AC_CONFIG_FILES([
   data/xsessions/Makefile
 
   gnome-flashback/Makefile
+  gnome-flashback/liba11y-keyboard/Makefile
   gnome-flashback/libaudio-device-selection/Makefile
   gnome-flashback/libautomount-manager/Makefile
   gnome-flashback/libbluetooth-applet/Makefile
diff --git a/data/schemas/org.gnome.gnome-flashback.gschema.xml 
b/data/schemas/org.gnome.gnome-flashback.gschema.xml
index 79d115b..529ae34 100644
--- a/data/schemas/org.gnome.gnome-flashback.gschema.xml
+++ b/data/schemas/org.gnome.gnome-flashback.gschema.xml
@@ -1,5 +1,10 @@
 <schemalist gettext-domain="gnome-flashback">
   <schema id="org.gnome.gnome-flashback" path="/org/gnome/gnome-flashback/">
+    <key name="a11y-keyboard" type="b">
+      <default>true</default>
+      <summary>a11y-keyboard</summary>
+    </key>
+
     <key name="audio-device-selection" type="b">
       <default>true</default>
       <summary>Audio device selection</summary>
diff --git a/gnome-flashback/Makefile.am b/gnome-flashback/Makefile.am
index 580c131..7fc52aa 100644
--- a/gnome-flashback/Makefile.am
+++ b/gnome-flashback/Makefile.am
@@ -1,6 +1,7 @@
 NULL =
 
 SUBDIRS = \
+       liba11y-keyboard \
        libaudio-device-selection \
        libautomount-manager \
        libbluetooth-applet \
@@ -60,6 +61,7 @@ gnome_flashback_LDFLAGS = \
 gnome_flashback_LDADD = \
        $(GNOME_FLASHBACK_LIBS) \
        $(top_builddir)/backends/libbackends.la \
+       $(top_builddir)/gnome-flashback/liba11y-keyboard/liba11y-keyboard.la \
        $(top_builddir)/gnome-flashback/libaudio-device-selection/libaudio-device-selection.la \
        $(top_builddir)/gnome-flashback/libautomount-manager/libautomount-manager.la \
        $(top_builddir)/gnome-flashback/libbluetooth-applet/libbluetooth-applet.la \
diff --git a/gnome-flashback/gf-application.c b/gnome-flashback/gf-application.c
index 51d841b..ab9dd5e 100644
--- a/gnome-flashback/gf-application.c
+++ b/gnome-flashback/gf-application.c
@@ -23,6 +23,7 @@
 #include "gf-application.h"
 #include "gf-confirm-display-change-dialog.h"
 #include "backends/gf-backend.h"
+#include "liba11y-keyboard/gf-a11y-keyboard.h"
 #include "libaudio-device-selection/gf-audio-device-selection.h"
 #include "libautomount-manager/gsd-automount-manager.h"
 #include "libbluetooth-applet/gf-bluetooth-applet.h"
@@ -58,6 +59,7 @@ struct _GfApplication
   FlashbackIdleMonitor    *idle_monitor;
   FlashbackPolkit         *polkit;
   FlashbackShell          *shell;
+  GfA11yKeyboard          *a11y_keyboard;
   GfAudioDeviceSelection  *audio_device_selection;
   GfBluetoothApplet       *bluetooth;
   GfDesktopBackground     *background;
@@ -157,6 +159,7 @@ settings_changed (GSettings   *settings,
   SETTING_CHANGED (idle_monitor, "idle-monitor", flashback_idle_monitor_new)
   SETTING_CHANGED (polkit, "polkit", flashback_polkit_new)
   SETTING_CHANGED (shell, "shell", flashback_shell_new)
+  SETTING_CHANGED (a11y_keyboard, "a11y-keyboard", gf_a11y_keyboard_new)
   SETTING_CHANGED (audio_device_selection, "audio-device-selection", gf_audio_device_selection_new)
   SETTING_CHANGED (bluetooth, "bluetooth-applet", gf_bluetooth_applet_new)
   SETTING_CHANGED (background, "desktop-background", gf_desktop_background_new)
@@ -203,6 +206,7 @@ gf_application_dispose (GObject *object)
   g_clear_object (&application->idle_monitor);
   g_clear_object (&application->polkit);
   g_clear_object (&application->shell);
+  g_clear_object (&application->a11y_keyboard);
   g_clear_object (&application->audio_device_selection);
   g_clear_object (&application->bluetooth);
   g_clear_object (&application->background);
diff --git a/gnome-flashback/liba11y-keyboard/Makefile.am b/gnome-flashback/liba11y-keyboard/Makefile.am
new file mode 100644
index 0000000..ff3385d
--- /dev/null
+++ b/gnome-flashback/liba11y-keyboard/Makefile.am
@@ -0,0 +1,50 @@
+NULL =
+
+noinst_LTLIBRARIES = \
+       liba11y-keyboard.la \
+       $(NULL)
+
+liba11y_keyboard_la_CPPFLAGS = \
+       $(NULL)
+
+liba11y_keyboard_la_CFLAGS = \
+       $(A11Y_KEYBOARD_CFLAGS) \
+       $(WARN_CFLAGS) \
+       $(AM_CFLAGS) \
+       $(NULL)
+
+liba11y_keyboard_la_SOURCES = \
+       gf-a11y-keyboard.c \
+       gf-a11y-keyboard.h \
+       $(BUILT_SOURCES) \
+       $(NULL)
+
+liba11y_keyboard_la_LDFLAGS = \
+       $(WARN_LDFLAGS) \
+       $(AM_LDFLAGS) \
+       $(NULL)
+
+liba11y_keyboard_la_LIBADD = \
+       $(A11Y_KEYBOARD_LIBS) \
+       $(NULL)
+
+gf-notifications-gen.h:
+gf-notifications-gen.c: org.freedesktop.Notifications.xml
+       $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Gf \
+               --generate-c-code gf-notifications-gen \
+               $(srcdir)/org.freedesktop.Notifications.xml
+
+BUILT_SOURCES = \
+       gf-notifications-gen.c \
+       gf-notifications-gen.h \
+       $(NULL)
+
+EXTRA_DIST = \
+       org.freedesktop.Notifications.xml \
+       $(NULL)
+
+CLEANFILES = \
+       $(BUILT_SOURCES) \
+       $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.c 
b/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.c
new file mode 100644
index 0000000..36a4f47
--- /dev/null
+++ b/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.c
@@ -0,0 +1,941 @@
+/*
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2018 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "gf-a11y-keyboard.h"
+
+#include "gf-notifications-gen.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBstr.h>
+
+#define KEYBOARD_A11Y_SCHEMA "org.gnome.desktop.a11y.keyboard"
+
+#define DEFAULT_XKB_SET_CONTROLS_MASK XkbSlowKeysMask        | \
+                                      XkbBounceKeysMask      | \
+                                      XkbStickyKeysMask      | \
+                                      XkbMouseKeysMask       | \
+                                      XkbMouseKeysAccelMask  | \
+                                      XkbAccessXKeysMask     | \
+                                      XkbAccessXTimeoutMask  | \
+                                      XkbAccessXFeedbackMask | \
+                                      XkbControlsEnabledMask
+
+struct _GfA11yKeyboard
+{
+  GObject             parent;
+
+  guint               start_idle_id;
+  int                 xkbEventBase;
+  guint               device_added_id;
+  gboolean            stickykeys_shortcut_val;
+  gboolean            slowkeys_shortcut_val;
+
+  XkbDescRec         *desc;
+
+  GSettings          *settings;
+
+  GfNotificationsGen *notifications;
+  guint               slowkeys_id;
+  guint               stickykeys_id;
+};
+
+G_DEFINE_TYPE (GfA11yKeyboard, gf_a11y_keyboard, G_TYPE_OBJECT)
+
+static int
+get_int (GSettings  *settings,
+         const char *key)
+{
+  int res;
+
+  res = g_settings_get_int (settings, key);
+  if (res <= 0)
+    res = 1;
+
+  return res;
+}
+
+static unsigned long
+set_clear (gboolean      flag,
+           unsigned long value,
+           unsigned long mask)
+{
+  if (flag)
+    return value | mask;
+
+  return value & ~mask;
+}
+
+static gboolean
+set_ctrl_from_gsettings (XkbDescRec    *desc,
+                         GSettings     *settings,
+                         char const    *key,
+                         unsigned long  mask)
+{
+  gboolean result;
+
+  result = g_settings_get_boolean (settings, key);
+  desc->ctrls->enabled_ctrls = set_clear (result, desc->ctrls->enabled_ctrls, mask);
+
+  return result;
+}
+
+static XkbDescRec *
+get_xkb_desc_rec (void)
+{
+  GdkDisplay *display;
+  Display *xdisplay;
+  XkbDescRec *desc;
+  Status status;
+
+  display = gdk_display_get_default ();
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  gdk_x11_display_error_trap_push (display);
+
+  desc = XkbGetMap (xdisplay, XkbAllMapComponentsMask, XkbUseCoreKbd);
+  status = Success;
+
+  if (desc != NULL)
+    {
+      desc->ctrls = NULL;
+      status = XkbGetControls (xdisplay, XkbAllControlsMask, desc);
+    }
+
+  gdk_x11_display_error_trap_pop_ignored (display);
+
+  g_return_val_if_fail (desc != NULL, NULL);
+  g_return_val_if_fail (desc->ctrls != NULL, NULL);
+  g_return_val_if_fail (status == Success, NULL);
+
+  return desc;
+}
+
+static void
+set_server_from_gsettings (GfA11yKeyboard *a11y_keyboard)
+{
+  XkbDescRec *desc;
+  GSettings *settings;
+  GdkDisplay *display;
+  Display *xdisplay;
+
+  desc = get_xkb_desc_rec ();
+  if (!desc)
+    return;
+
+  settings = a11y_keyboard->settings;
+
+  /* general */
+  desc->ctrls->enabled_ctrls = set_clear (g_settings_get_boolean (settings, "enable"),
+                                          desc->ctrls->enabled_ctrls,
+                                          XkbAccessXKeysMask);
+
+  if (set_ctrl_from_gsettings (desc, settings, "timeout-enable",
+                               XkbAccessXTimeoutMask))
+    {
+      desc->ctrls->ax_timeout = get_int (settings, "disable-timeout");
+      /* disable only the master flag via the server we will disable
+       * the rest on the rebound without affecting GSettings state
+       * don't change the option flags at all.
+       */
+      desc->ctrls->axt_ctrls_mask = XkbAccessXKeysMask | XkbAccessXFeedbackMask;
+      desc->ctrls->axt_ctrls_values = 0;
+      desc->ctrls->axt_opts_mask = 0;
+    }
+
+  desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "feature-state-change-beep"),
+                                       desc->ctrls->ax_options,
+                                       XkbAccessXFeedbackMask | XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask);
+
+  /* bounce keys */
+  if (set_ctrl_from_gsettings (desc, settings, "bouncekeys-enable", XkbBounceKeysMask))
+    {
+      desc->ctrls->debounce_delay = get_int (settings, "bouncekeys-delay");
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "bouncekeys-beep-reject"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_BKRejectFBMask);
+    }
+
+  /* mouse keys */
+  if (set_ctrl_from_gsettings (desc, settings, "mousekeys-enable", XkbMouseKeysMask | XkbMouseKeysAccelMask))
+    {
+      desc->ctrls->mk_interval = 100; /* msec between mousekey events */
+      desc->ctrls->mk_curve = 50;
+
+      /* We store pixels / sec, XKB wants pixels / event */
+      desc->ctrls->mk_max_speed = get_int (settings, "mousekeys-max-speed") / (1000 / 
desc->ctrls->mk_interval);
+      if (desc->ctrls->mk_max_speed <= 0)
+        desc->ctrls->mk_max_speed = 1;
+
+      desc->ctrls->mk_time_to_max = get_int (settings, /* events before max */
+                                             "mousekeys-accel-time") / desc->ctrls->mk_interval;
+      if (desc->ctrls->mk_time_to_max <= 0)
+        desc->ctrls->mk_time_to_max = 1;
+
+      desc->ctrls->mk_delay = get_int (settings, /* ms before 1st event */
+                                       "mousekeys-init-delay");
+    }
+
+
+  /* slow keys */
+  if (set_ctrl_from_gsettings (desc, settings, "slowkeys-enable", XkbSlowKeysMask))
+    {
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "slowkeys-beep-press"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_SKPressFBMask);
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "slowkeys-beep-accept"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_SKAcceptFBMask);
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "slowkeys-beep-reject"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_SKRejectFBMask);
+      desc->ctrls->slow_keys_delay = get_int (settings, "slowkeys-delay");
+      /* anything larger than 500 seems to loose all keyboard input */
+      if (desc->ctrls->slow_keys_delay > 500)
+        desc->ctrls->slow_keys_delay = 500;
+    }
+
+  /* sticky keys */
+  if (set_ctrl_from_gsettings (desc, settings, "stickykeys-enable", XkbStickyKeysMask))
+    {
+      desc->ctrls->ax_options |= XkbAX_LatchToLockMask;
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "stickykeys-two-key-off"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_TwoKeysMask);
+      desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "stickykeys-modifier-beep"),
+                                           desc->ctrls->ax_options,
+                                           XkbAccessXFeedbackMask | XkbAX_StickyKeysFBMask);
+    }
+
+  /* toggle keys */
+  desc->ctrls->ax_options = set_clear (g_settings_get_boolean (settings, "togglekeys-enable"),
+                                       desc->ctrls->ax_options,
+                                       XkbAccessXFeedbackMask | XkbAX_IndicatorFBMask);
+
+  /*
+  g_debug ("CHANGE to : 0x%x", desc->ctrls->enabled_ctrls);
+  g_debug ("CHANGE to : 0x%x (2)", desc->ctrls->ax_options);
+  */
+
+  display = gdk_display_get_default ();
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  gdk_x11_display_error_trap_push (display);
+
+  XkbSetControls (xdisplay, DEFAULT_XKB_SET_CONTROLS_MASK, desc);
+  XkbFreeKeyboard (desc, XkbAllComponentsMask, True);
+  XSync (xdisplay, False);
+
+  gdk_x11_display_error_trap_pop_ignored (display);
+}
+
+static void
+slowkeys_notify_cb (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  GError *error;
+  guint id;
+  GfA11yKeyboard *a11y_keyboard;
+
+  error = NULL;
+  gf_notifications_gen_call_notify_finish (GF_NOTIFICATIONS_GEN (source_object),
+                                           &id, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+  else if (error != NULL)
+    {
+      g_warning ("a11y-keyboard: unable to show notification: %s", error->message);
+      g_error_free (error);
+
+      return;
+    }
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (user_data);
+  a11y_keyboard->slowkeys_id = id;
+}
+
+static void
+ax_slowkeys_warning_post_bubble (GfA11yKeyboard *a11y_keyboard,
+                                 gboolean        enabled)
+{
+  const char *icon;
+  const char *title;
+  const char *message;
+  const char **actions;
+  GVariantBuilder hints;
+
+  icon = "preferences-desktop-accessibility-symbolic";
+
+  title = enabled ?
+          _("Slow Keys Turned On") :
+          _("Slow Keys Turned Off");
+
+  message = _("You just held down the Shift key for 8 seconds. This is the shortcut "
+              "for the Slow Keys feature, which affects the way your keyboard works.");
+
+  actions = g_new0 (const char *, 5);
+  actions[0] = "reject";
+  actions[1] = enabled ? _("Turn Off") : _("Turn On");
+  actions[2] = "accept";
+  actions[3] = enabled ? _("Leave On") : _("Leave Off");
+  actions[4] = NULL;
+
+  g_variant_builder_init (&hints, G_VARIANT_TYPE ("a{sv}"));
+  g_variant_builder_add (&hints, "{sv}", "urgency",
+                         g_variant_new_byte (0x02 /* critical */));
+
+  gf_notifications_gen_call_notify (a11y_keyboard->notifications,
+                                    _("Universal Access"),
+                                    0,
+                                    icon,
+                                    title,
+                                    message,
+                                    actions,
+                                    g_variant_builder_end (&hints),
+                                    0,
+                                    NULL,
+                                    slowkeys_notify_cb,
+                                    a11y_keyboard);
+
+  g_free (actions);
+}
+
+static void
+ax_slowkeys_warning_post (GfA11yKeyboard *a11y_keyboard,
+                          gboolean        enabled)
+{
+  a11y_keyboard->slowkeys_shortcut_val = enabled;
+  ax_slowkeys_warning_post_bubble (a11y_keyboard, enabled);
+}
+
+static void
+stickykeys_notify_cb (GObject      *source_object,
+                      GAsyncResult *res,
+                      gpointer      user_data)
+{
+  GError *error;
+  guint id;
+  GfA11yKeyboard *a11y_keyboard;
+
+  error = NULL;
+  gf_notifications_gen_call_notify_finish (GF_NOTIFICATIONS_GEN (source_object),
+                                           &id, res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+  else if (error != NULL)
+    {
+      g_warning ("a11y-keyboard: unable to show notification: %s", error->message);
+      g_error_free (error);
+
+      return;
+    }
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (user_data);
+  a11y_keyboard->stickykeys_id = id;
+}
+
+static void
+ax_stickykeys_warning_post_bubble (GfA11yKeyboard *a11y_keyboard,
+                                   gboolean        enabled)
+{
+  const char *icon;
+  const char *title;
+  const char *message;
+  const char **actions;
+  GVariantBuilder hints;
+
+  icon = "preferences-desktop-accessibility-symbolic";
+
+  title = enabled ?
+          _("Sticky Keys Turned On") :
+          _("Sticky Keys Turned Off");
+
+  message = enabled ?
+            _("You just pressed the Shift key 5 times in a row.  This is the shortcut "
+              "for the Sticky Keys feature, which affects the way your keyboard works.") :
+            _("You just pressed two keys at once, or pressed the Shift key 5 times in a row. "
+              "This turns off the Sticky Keys feature, which affects the way your keyboard works.");
+
+  actions = g_new0 (const char *, 5);
+  actions[0] = "reject";
+  actions[1] = enabled ? _("Turn Off") : _("Turn On");
+  actions[2] = "accept";
+  actions[3] = enabled ? _("Leave On") : _("Leave Off");
+  actions[4] = NULL;
+
+  g_variant_builder_init (&hints, G_VARIANT_TYPE ("a{sv}"));
+  g_variant_builder_add (&hints, "{sv}", "urgency",
+                         g_variant_new_byte (0x02 /* critical */));
+
+  gf_notifications_gen_call_notify (a11y_keyboard->notifications,
+                                    _("Universal Access"),
+                                    0,
+                                    icon,
+                                    title,
+                                    message,
+                                    actions,
+                                    g_variant_builder_end (&hints),
+                                    0,
+                                    NULL,
+                                    stickykeys_notify_cb,
+                                    a11y_keyboard);
+
+  g_free (actions);
+}
+
+static void
+ax_stickykeys_warning_post (GfA11yKeyboard *a11y_keyboard,
+                            gboolean        enabled)
+{
+
+  a11y_keyboard->stickykeys_shortcut_val = enabled;
+  ax_stickykeys_warning_post_bubble (a11y_keyboard, enabled);
+}
+
+static gboolean
+set_bool (GSettings  *settings,
+          const char *key,
+          int         val)
+{
+  gboolean bval;
+  gboolean prev_val;
+
+  bval = (val != 0);
+  prev_val = g_settings_get_boolean (settings, key);
+
+  g_settings_set_boolean (settings, key, bval ? TRUE : FALSE);
+
+  if (bval != prev_val)
+    {
+      g_debug ("%s changed", key);
+      return TRUE;
+    }
+
+  return bval != prev_val;
+}
+
+static gboolean
+set_int (GSettings  *settings,
+         const char *key,
+         int         val)
+{
+  int prev_val;
+
+  prev_val = g_settings_get_int (settings, key);
+
+  g_settings_set_int (settings, key, val);
+
+  if (val != prev_val)
+    g_debug ("%s changed", key);
+
+  return val != prev_val;
+}
+
+static void
+set_gsettings_from_server (GfA11yKeyboard *a11y_keyboard)
+{
+  XkbDescRec *desc;
+  GSettings *settings;
+  gboolean changed;
+  gboolean slowkeys_changed;
+  gboolean stickykeys_changed;
+
+  desc = get_xkb_desc_rec ();
+  if (!desc)
+    return;
+
+  /* Create a new one, so that only those settings are delayed */
+  settings = g_settings_new (KEYBOARD_A11Y_SCHEMA);
+  g_settings_delay (settings);
+
+  /*
+  fprintf (stderr, "changed to : 0x%x\n", desc->ctrls->enabled_ctrls);
+  fprintf (stderr, "changed to : 0x%x (2)\n", desc->ctrls->ax_options);
+  */
+
+  changed = FALSE;
+
+  changed |= set_bool (settings, "enable",
+                       desc->ctrls->enabled_ctrls & XkbAccessXKeysMask);
+
+  changed |= set_bool (settings, "feature-state-change-beep",
+                       desc->ctrls->ax_options & (XkbAX_FeatureFBMask | XkbAX_SlowWarnFBMask));
+
+  changed |= set_bool (settings, "timeout-enable",
+                       desc->ctrls->enabled_ctrls & XkbAccessXTimeoutMask);
+
+  changed |= set_int (settings, "disable-timeout",
+                      desc->ctrls->ax_timeout);
+
+  changed |= set_bool (settings, "bouncekeys-enable",
+                       desc->ctrls->enabled_ctrls & XkbBounceKeysMask);
+
+  changed |= set_int (settings, "bouncekeys-delay",
+                      desc->ctrls->debounce_delay);
+
+  changed |= set_bool (settings, "bouncekeys-beep-reject",
+                       desc->ctrls->ax_options & XkbAX_BKRejectFBMask);
+
+  changed |= set_bool (settings, "mousekeys-enable",
+                       desc->ctrls->enabled_ctrls & XkbMouseKeysMask);
+
+  changed |= set_int (settings, "mousekeys-max-speed",
+                      desc->ctrls->mk_max_speed * (1000 / desc->ctrls->mk_interval));
+
+  /* NOTE : mk_time_to_max is measured in events not time */
+  changed |= set_int (settings, "mousekeys-accel-time",
+                      desc->ctrls->mk_time_to_max * desc->ctrls->mk_interval);
+
+  changed |= set_int (settings, "mousekeys-init-delay",
+                      desc->ctrls->mk_delay);
+
+  slowkeys_changed = set_bool (settings, "slowkeys-enable",
+                               desc->ctrls->enabled_ctrls & XkbSlowKeysMask);
+
+  changed |= set_bool (settings, "slowkeys-beep-press",
+                       desc->ctrls->ax_options & XkbAX_SKPressFBMask);
+
+  changed |= set_bool (settings, "slowkeys-beep-accept",
+                       desc->ctrls->ax_options & XkbAX_SKAcceptFBMask);
+
+  changed |= set_bool (settings, "slowkeys-beep-reject",
+                       desc->ctrls->ax_options & XkbAX_SKRejectFBMask);
+
+  changed |= set_int (settings, "slowkeys-delay",
+                      desc->ctrls->slow_keys_delay);
+
+  stickykeys_changed = set_bool (settings, "stickykeys-enable",
+                                 desc->ctrls->enabled_ctrls & XkbStickyKeysMask);
+
+  changed |= set_bool (settings, "stickykeys-two-key-off",
+                       desc->ctrls->ax_options & XkbAX_TwoKeysMask);
+
+  changed |= set_bool (settings, "stickykeys-modifier-beep",
+                       desc->ctrls->ax_options & XkbAX_StickyKeysFBMask);
+
+  changed |= set_bool (settings, "togglekeys-enable",
+                       desc->ctrls->ax_options & XkbAX_IndicatorFBMask);
+
+  if (!changed && stickykeys_changed ^ slowkeys_changed)
+    {
+      /*
+       * sticky or slowkeys has changed, singly, without our intervention.
+       * 99% chance this is due to a keyboard shortcut being used.
+       * we need to detect via this hack until we get
+       * XkbAXN_AXKWarning notifications working (probable XKB bug),
+       * at which time we can directly intercept such shortcuts instead.
+       * See xkb_event_filter_cb () below.
+       */
+
+      /* sanity check: are keyboard shortcuts available? */
+      if (desc->ctrls->enabled_ctrls & XkbAccessXKeysMask)
+        {
+          gboolean enabled;
+
+          if (slowkeys_changed)
+            {
+              enabled = desc->ctrls->enabled_ctrls & XkbSlowKeysMask;
+              ax_slowkeys_warning_post (a11y_keyboard, enabled);
+            }
+          else
+            {
+              enabled = desc->ctrls->enabled_ctrls & XkbStickyKeysMask;
+              ax_stickykeys_warning_post (a11y_keyboard, enabled);
+            }
+        }
+    }
+
+  XkbFreeKeyboard (desc, XkbAllComponentsMask, True);
+
+  g_settings_apply (settings);
+  g_object_unref (settings);
+}
+
+static GdkFilterReturn
+xkb_event_filter_cb (GdkXEvent *xevent,
+                     GdkEvent  *event,
+                     gpointer   user_data)
+{
+  GfA11yKeyboard *a11y_keyboard;
+  XEvent *xev;
+  XkbEvent *xkbEv;
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (user_data);
+  xev = (XEvent *) xevent;
+  xkbEv = (XkbEvent *) xevent;
+
+  /* 'event_type' is set to zero on notifying us of updates in
+   * response to client requests (including our own) and non-zero
+   * to notify us of key/mouse events causing changes (like
+   * pressing shift 5 times to enable sticky keys).
+   *
+   * We only want to update GSettings when it's in response to an
+   * explicit user input event, so require a non-zero event_type.
+   */
+  if (xev->xany.type == (a11y_keyboard->xkbEventBase + XkbEventCode) &&
+      xkbEv->any.xkb_type == XkbControlsNotify &&
+      xkbEv->ctrls.event_type != 0)
+    {
+      g_debug ("XKB state changed");
+      set_gsettings_from_server (a11y_keyboard);
+    }
+  else if (xev->xany.type == (a11y_keyboard->xkbEventBase + XkbEventCode) &&
+           xkbEv->any.xkb_type == XkbAccessXNotify)
+    {
+      if (xkbEv->accessx.detail == XkbAXN_AXKWarning)
+        {
+          g_debug ("About to turn on an AccessX feature from the keyboard!");
+
+          /*
+           * TODO: when XkbAXN_AXKWarnings start working, we need to
+           * invoke ax_keys_warning_dialog_run here instead of in
+           * set_gsettings_from_server().
+           */
+        }
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+
+static gboolean
+xkb_enabled (GfA11yKeyboard *a11y_keyboard)
+{
+  GdkDisplay *display;
+  Display *xdisplay;
+  int opcode, errorBase, major, minor;
+
+  display = gdk_display_get_default ();
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  if (!XkbQueryExtension (xdisplay,
+                          &opcode,
+                          &a11y_keyboard->xkbEventBase,
+                          &errorBase,
+                          &major,
+                          &minor))
+    return FALSE;
+
+  if (!XkbUseExtension (xdisplay, &major, &minor))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+keyboard_settings_changed_cb (GSettings      *settings,
+                              const char     *key,
+                              GfA11yKeyboard *a11y_keyboard)
+{
+  set_server_from_gsettings (a11y_keyboard);
+}
+
+static void
+device_added_cb (GdkSeat        *seat,
+                 GdkDevice      *device,
+                 GfA11yKeyboard *a11y_keyboard)
+{
+  if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    set_server_from_gsettings (a11y_keyboard);
+}
+
+static void
+set_devicepresence_handler (GfA11yKeyboard *a11y_keyboard)
+{
+  GdkDisplay *display;
+  GdkSeat *seat;
+
+  display = gdk_display_get_default ();
+  seat = gdk_display_get_default_seat (display);
+
+  a11y_keyboard->device_added_id = g_signal_connect (seat, "device-added",
+                                                     G_CALLBACK (device_added_cb),
+                                                     a11y_keyboard);
+}
+
+static gboolean
+start_a11y_keyboard_idle_cb (gpointer user_data)
+{
+  GfA11yKeyboard *a11y_keyboard;
+  GdkDisplay *display;
+  Display *xdisplay;
+  guint event_mask;
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (user_data);
+
+  if (!xkb_enabled (a11y_keyboard))
+    {
+      a11y_keyboard->start_idle_id = 0;
+      return G_SOURCE_REMOVE;
+    }
+
+  a11y_keyboard->settings = g_settings_new (KEYBOARD_A11Y_SCHEMA);
+  g_signal_connect (a11y_keyboard->settings, "changed",
+                    G_CALLBACK (keyboard_settings_changed_cb),
+                    a11y_keyboard);
+
+  set_devicepresence_handler (a11y_keyboard);
+
+  /* Get the original configuration from the server */
+  a11y_keyboard->desc = get_xkb_desc_rec ();
+
+  display = gdk_display_get_default ();
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  event_mask = XkbControlsNotifyMask;
+  event_mask |= XkbAccessXNotifyMask; /* make default when AXN_AXKWarning works */
+
+  /* be sure to init before starting to monitor the server */
+  set_server_from_gsettings (a11y_keyboard);
+
+  XkbSelectEvents (xdisplay, XkbUseCoreKbd, event_mask, event_mask);
+
+  gdk_window_add_filter (NULL, xkb_event_filter_cb, a11y_keyboard);
+
+  a11y_keyboard->start_idle_id = 0;
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ax_response_callback (GfA11yKeyboard *a11y_keyboard,
+                      const char     *action,
+                      guint           revert_controls_mask,
+                      gboolean        enabled)
+{
+  if (g_strcmp0 (action, "reject") != 0)
+    return;
+
+  /* we're reverting, so we invert sense of 'enabled' flag */
+  g_debug ("cancelling AccessX request");
+
+  if (revert_controls_mask == XkbStickyKeysMask)
+    {
+      g_settings_set_boolean (a11y_keyboard->settings,
+                              "stickykeys-enable",
+                              !enabled);
+    }
+  else if (revert_controls_mask == XkbSlowKeysMask)
+    {
+      g_settings_set_boolean (a11y_keyboard->settings,
+                              "slowkeys-enable",
+                              !enabled);
+    }
+
+  set_server_from_gsettings (a11y_keyboard);
+}
+
+static void
+action_invoked_cb (GfNotificationsGen *notifications,
+                   guint               id,
+                   const gchar        *action_key,
+                   GfA11yKeyboard     *a11y_keyboard)
+{
+  if (id == a11y_keyboard->slowkeys_id)
+    {
+      g_assert (action_key != NULL);
+
+      ax_response_callback (a11y_keyboard, action_key, XkbSlowKeysMask,
+                            a11y_keyboard->slowkeys_shortcut_val);
+    }
+  else if (id == a11y_keyboard->stickykeys_id)
+    {
+      g_assert (action_key != NULL);
+
+      ax_response_callback (a11y_keyboard, action_key, XkbStickyKeysMask,
+                            a11y_keyboard->stickykeys_shortcut_val);
+    }
+}
+
+static void
+notification_closed_cb (GfNotificationsGen *notifications,
+                        guint               id,
+                        guint               reason,
+                        GfA11yKeyboard     *a11y_keyboard)
+{
+  if (id == a11y_keyboard->slowkeys_id)
+    a11y_keyboard->slowkeys_id = 0;
+  else if (id == a11y_keyboard->stickykeys_id)
+    a11y_keyboard->stickykeys_id = 0;
+}
+
+static void
+notifications_proxy_ready_cb (GObject      *source_object,
+                              GAsyncResult *res,
+                              gpointer      user_data)
+{
+  GError *error;
+  GfNotificationsGen *notifications;
+  GfA11yKeyboard *a11y_keyboard;
+
+  error = NULL;
+  notifications = gf_notifications_gen_proxy_new_for_bus_finish (res, &error);
+
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_error_free (error);
+      return;
+    }
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (user_data);
+  a11y_keyboard->notifications = notifications;
+
+  if (error)
+    {
+      g_warning ("%s", error->message);
+      g_error_free (error);
+
+      return;
+    }
+
+  g_signal_connect (a11y_keyboard->notifications, "action-invoked",
+                    G_CALLBACK (action_invoked_cb), a11y_keyboard);
+
+  g_signal_connect (a11y_keyboard->notifications, "notification-closed",
+                    G_CALLBACK (notification_closed_cb), a11y_keyboard);
+}
+
+static void
+gf_a11y_keyboard_finalize (GObject *object)
+{
+  GfA11yKeyboard *a11y_keyboard;
+
+  a11y_keyboard = GF_A11Y_KEYBOARD (object);
+
+  if (a11y_keyboard->desc != NULL)
+    {
+      XkbDescRec *desc;
+
+      desc = get_xkb_desc_rec ();
+
+      if (desc != NULL)
+        {
+          if (a11y_keyboard->desc->ctrls->enabled_ctrls != desc->ctrls->enabled_ctrls)
+            {
+              GdkDisplay *display;
+              Display *xdisplay;
+
+              display = gdk_display_get_default ();
+              xdisplay = gdk_x11_display_get_xdisplay (display);
+
+              gdk_x11_display_error_trap_push (display);
+
+              XkbSetControls (xdisplay,
+                              DEFAULT_XKB_SET_CONTROLS_MASK,
+                              a11y_keyboard->desc);
+
+              XSync (xdisplay, False);
+
+              gdk_x11_display_error_trap_pop_ignored (display);
+            }
+
+          XkbFreeKeyboard (desc, XkbAllComponentsMask, True);
+        }
+
+      XkbFreeKeyboard (a11y_keyboard->desc, XkbAllComponentsMask, True);
+      a11y_keyboard->desc = NULL;
+    }
+
+  if (a11y_keyboard->start_idle_id != 0)
+    {
+      g_source_remove (a11y_keyboard->start_idle_id);
+      a11y_keyboard->start_idle_id = 0;
+    }
+
+  if (a11y_keyboard->device_added_id != 0)
+    {
+      GdkDisplay *display;
+      GdkSeat *seat;
+
+      display = gdk_display_get_default ();
+      seat = gdk_display_get_default_seat (display);
+
+      g_signal_handler_disconnect (seat, a11y_keyboard->device_added_id);
+      a11y_keyboard->device_added_id = 0;
+    }
+
+  g_clear_object (&a11y_keyboard->settings);
+
+  gdk_window_remove_filter (NULL, xkb_event_filter_cb, a11y_keyboard);
+
+  a11y_keyboard->slowkeys_shortcut_val = FALSE;
+  a11y_keyboard->stickykeys_shortcut_val = FALSE;
+
+  if (a11y_keyboard->slowkeys_id != 0)
+    {
+      gf_notifications_gen_call_close_notification (a11y_keyboard->notifications,
+                                                    a11y_keyboard->slowkeys_id,
+                                                    NULL, NULL, NULL);
+      a11y_keyboard->slowkeys_id = 0;
+    }
+
+  if (a11y_keyboard->stickykeys_id != 0)
+    {
+      gf_notifications_gen_call_close_notification (a11y_keyboard->notifications,
+                                                    a11y_keyboard->stickykeys_id,
+                                                    NULL, NULL, NULL);
+      a11y_keyboard->stickykeys_id = 0;
+    }
+
+  g_clear_object (&a11y_keyboard->notifications);
+
+  G_OBJECT_CLASS (gf_a11y_keyboard_parent_class)->finalize (object);
+}
+
+static void
+gf_a11y_keyboard_class_init (GfA11yKeyboardClass *a11y_keyboard_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (a11y_keyboard_class);
+
+  object_class->finalize = gf_a11y_keyboard_finalize;
+}
+
+static void
+gf_a11y_keyboard_init (GfA11yKeyboard *a11y_keyboard)
+{
+  a11y_keyboard->start_idle_id = g_idle_add (start_a11y_keyboard_idle_cb, a11y_keyboard);
+  g_source_set_name_by_id (a11y_keyboard->start_idle_id, "[gnome-flashback] start_a11y_keyboard_idle_cb");
+
+  gf_notifications_gen_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                          G_DBUS_PROXY_FLAGS_NONE,
+                                          "org.freedesktop.Notifications",
+                                          "/org/freedesktop/Notifications",
+                                          NULL,
+                                          notifications_proxy_ready_cb,
+                                          a11y_keyboard);
+}
+
+GfA11yKeyboard *
+gf_a11y_keyboard_new (void)
+{
+  return g_object_new (GF_TYPE_A11Y_KEYBOARD, NULL);
+}
diff --git a/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.h 
b/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.h
new file mode 100644
index 0000000..a8f1276
--- /dev/null
+++ b/gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GF_A11Y_KEYBOARD_H
+#define GF_A11Y_KEYBOARD_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GF_TYPE_A11Y_KEYBOARD (gf_a11y_keyboard_get_type ())
+G_DECLARE_FINAL_TYPE (GfA11yKeyboard, gf_a11y_keyboard,
+                      GF, A11Y_KEYBOARD, GObject)
+
+GfA11yKeyboard *gf_a11y_keyboard_new (void);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-flashback/liba11y-keyboard/org.freedesktop.Notifications.xml 
b/gnome-flashback/liba11y-keyboard/org.freedesktop.Notifications.xml
new file mode 100644
index 0000000..dc82267
--- /dev/null
+++ b/gnome-flashback/liba11y-keyboard/org.freedesktop.Notifications.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node>
+  <interface name="org.freedesktop.Notifications">
+    <annotation name="org.gtk.GDBus.C.Name" value="NotificationsGen" />
+
+    <method name="CloseNotification">
+      <arg type="u" name="id" direction="in" />
+    </method>
+
+    <method name="GetCapabilities">
+      <arg type="as" name="capabilities" direction="out" />
+    </method>
+
+    <method name="GetServerInformation">
+      <arg type="s" name="name" direction="out" />
+      <arg type="s" name="vendor" direction="out" />
+      <arg type="s" name="version" direction="out" />
+      <arg type="s" name="spec_version" direction="out" />
+    </method>
+
+    <method name="Notify">
+      <arg type="s" name="app_name" direction="in" />
+      <arg type="u" name="replaces_id" direction="in" />
+      <arg type="s" name="app_icon" direction="in" />
+      <arg type="s" name="summary" direction="in" />
+      <arg type="s" name="body" direction="in" />
+      <arg type="as" name="actions" direction="in" />
+      <arg type="a{sv}" name="hints" direction="in" />
+      <arg type="i" name="expire_timeout" direction="in" />
+      <arg type="u" name="id" direction="out" />
+    </method>
+
+    <signal name="ActionInvoked">
+      <arg type="u" name="id" />
+      <arg type="s" name="action_key" />
+    </signal>
+
+    <signal name="NotificationClosed">
+      <arg type="u" name="id" />
+      <arg type="u" name="reason" />
+    </signal>
+  </interface>
+</node>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6bd42c3..3367e17 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ data/xsessions/gnome-flashback-compiz.desktop.in.in
 data/xsessions/gnome-flashback-metacity.desktop.in.in
 gnome-flashback/gf-confirm-display-change-dialog.c
 gnome-flashback/gf-main.c
+gnome-flashback/liba11y-keyboard/gf-a11y-keyboard.c
 gnome-flashback/libaudio-device-selection/gf-audio-device-selection-dialog.c
 gnome-flashback/libautomount-manager/gsd-automount-manager.c
 gnome-flashback/libautomount-manager/gsd-autorun.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]