summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/a11y-keyboard/Makefile.am4
-rw-r--r--plugins/a11y-keyboard/msd-a11y-keyboard-atspi.c208
-rw-r--r--plugins/a11y-keyboard/msd-a11y-keyboard-atspi.h55
-rw-r--r--plugins/a11y-keyboard/msd-a11y-keyboard-manager.c25
-rw-r--r--plugins/a11y-keyboard/msd-a11y-preferences-dialog.c50
-rw-r--r--plugins/a11y-keyboard/msd-a11y-preferences-dialog.ui16
6 files changed, 357 insertions, 1 deletions
diff --git a/plugins/a11y-keyboard/Makefile.am b/plugins/a11y-keyboard/Makefile.am
index 0881088..92072e2 100644
--- a/plugins/a11y-keyboard/Makefile.am
+++ b/plugins/a11y-keyboard/Makefile.am
@@ -42,6 +42,8 @@ liba11y_keyboard_la_SOURCES = \
msd-a11y-keyboard-plugin.c \
msd-a11y-keyboard-manager.h \
msd-a11y-keyboard-manager.c \
+ msd-a11y-keyboard-atspi.h \
+ msd-a11y-keyboard-atspi.c \
msd-a11y-preferences-dialog.h \
msd-a11y-preferences-dialog.c \
$(NULL)
@@ -55,6 +57,7 @@ liba11y_keyboard_la_CPPFLAGS = \
liba11y_keyboard_la_CFLAGS = \
$(SETTINGS_PLUGIN_CFLAGS) \
$(LIBNOTIFY_CFLAGS) \
+ $(LIBATSPI_CFLAGS) \
$(AM_CFLAGS) \
$(WARN_CFLAGS) \
$(NULL)
@@ -66,6 +69,7 @@ liba11y_keyboard_la_LDFLAGS = \
liba11y_keyboard_la_LIBADD = \
$(SETTINGS_PLUGIN_LIBS) \
$(LIBNOTIFY_LIBS) \
+ $(LIBATSPI_LIBS) \
$(NULL)
plugin_in_files = \
diff --git a/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.c b/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.c
new file mode 100644
index 0000000..c6cafed
--- /dev/null
+++ b/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.c
@@ -0,0 +1,208 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Colomban Wendling <[email protected]>
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "msd-a11y-keyboard-atspi.h"
+
+#include <glib-object.h>
+
+#ifdef HAVE_LIBATSPI
+#include <gdk/gdk.h>
+#include <atspi/atspi.h>
+
+
+/* ugly workaround https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/22 */
+static AtspiDeviceListener *
+WORKAROUND_atspi_device_listener_new (AtspiDeviceListenerCB callback,
+ void *user_data,
+ GDestroyNotify callback_destroyed)
+{
+ AtspiDeviceListener *listener;
+
+ listener = atspi_device_listener_new (callback, user_data,
+ callback_destroyed);
+
+ /* Yes, we do leak a reference. But we have to, because there's a
+ * nasty bug in libatspi [1] where if a listener is destroyed it leads
+ * to invalid memory access and potentially a crash.
+ * [1] https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/22 */
+ return g_object_ref (listener);
+}
+#define atspi_device_listener_new WORKAROUND_atspi_device_listener_new
+
+
+struct MsdA11yKeyboardAtspiPrivate
+{
+ AtspiDeviceListener *listener;
+ gboolean listening;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MsdA11yKeyboardAtspi, msd_a11y_keyboard_atspi, G_TYPE_OBJECT)
+
+static void
+msd_a11y_keyboard_atspi_finalize (GObject *obj)
+{
+ MsdA11yKeyboardAtspi *self = MSD_A11Y_KEYBOARD_ATSPI (obj);
+
+ g_clear_object (&self->priv->listener);
+ self->priv->listening = FALSE;
+
+ G_OBJECT_CLASS (msd_a11y_keyboard_atspi_parent_class)->finalize (obj);
+}
+
+static void
+msd_a11y_keyboard_atspi_class_init (MsdA11yKeyboardAtspiClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = msd_a11y_keyboard_atspi_finalize;
+}
+
+static gboolean
+on_key_press_event (const AtspiDeviceEvent *event,
+ void *user_data G_GNUC_UNUSED)
+{
+ /* don't ring when disabling capslock */
+ if (event->id == GDK_KEY_Caps_Lock &&
+ event->modifiers & (1 << ATSPI_MODIFIER_SHIFTLOCK))
+ return FALSE;
+
+ gdk_display_beep (gdk_display_get_default ());
+
+ return FALSE;
+}
+
+static void
+msd_a11y_keyboard_atspi_init (MsdA11yKeyboardAtspi *self)
+{
+ self->priv = msd_a11y_keyboard_atspi_get_instance_private (self);
+
+ /* init AT-SPI if needed */
+ atspi_init ();
+
+ /* Ideally we'd create the listener in start() only -- we don't need
+ * it otherwise. But there's a bug [1] in libatspi where destroying a
+ * listener leads to a crash, so we have a workaround keeping the
+ * listener alive, and thus try and avoid creating (and not destroying)
+ * listener more often than absolutely necessary.
+ * [1] https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/22 */
+ self->priv->listener = atspi_device_listener_new (on_key_press_event,
+ self, NULL);
+ self->priv->listening = FALSE;
+}
+
+static void
+register_deregister_events (MsdA11yKeyboardAtspi *self,
+ gboolean do_register)
+{
+ AtspiKeyDefinition shiftlock_key;
+ GArray *shiftlock_key_set;
+
+ g_return_if_fail (MSD_IS_A11Y_KEYBOARD_ATSPI (self));
+ g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (self->priv->listener));
+
+ /* register listeners for CAPS_LOCK with any modifier but CAPS_LOCK,
+ * and all keys with CAPS_LOCK modifier, so we grab when CAPS_LOCK is
+ * active or gets activated */
+ shiftlock_key.keycode = 0;
+ shiftlock_key.keysym = GDK_KEY_Caps_Lock;
+ shiftlock_key.keystring = NULL;
+
+ shiftlock_key_set = g_array_new (FALSE, FALSE, sizeof shiftlock_key);
+ g_array_append_val (shiftlock_key_set, shiftlock_key);
+
+ for (AtspiKeyMaskType mod_mask = 0; mod_mask < 256; mod_mask++)
+ {
+ GArray *key_set;
+
+ if (mod_mask & (1 << ATSPI_MODIFIER_SHIFTLOCK))
+ key_set = NULL;
+ else
+ key_set = shiftlock_key_set;
+
+ if (do_register)
+ atspi_register_keystroke_listener (self->priv->listener,
+ key_set,
+ mod_mask,
+ 1 << ATSPI_KEY_PRESSED_EVENT,
+ ATSPI_KEYLISTENER_NOSYNC,
+ NULL);
+ else
+ atspi_deregister_keystroke_listener (self->priv->listener,
+ key_set,
+ mod_mask,
+ 1 << ATSPI_KEY_PRESSED_EVENT,
+ NULL);
+ }
+
+ g_array_unref (shiftlock_key_set);
+
+ self->priv->listening = do_register;
+}
+
+void
+msd_a11y_keyboard_atspi_start (MsdA11yKeyboardAtspi *self)
+{
+ g_return_if_fail (MSD_IS_A11Y_KEYBOARD_ATSPI (self));
+
+ if (! self->priv->listening)
+ register_deregister_events (self, TRUE);
+}
+
+void
+msd_a11y_keyboard_atspi_stop (MsdA11yKeyboardAtspi *self)
+{
+ g_return_if_fail (MSD_IS_A11Y_KEYBOARD_ATSPI (self));
+
+ if (self->priv->listening)
+ register_deregister_events (self, FALSE);
+}
+
+#else /* ! defined(HAVE_LIBATSPI): AT-SPI is not available, provide stubs */
+
+G_DEFINE_TYPE (MsdA11yKeyboardAtspi, msd_a11y_keyboard_atspi, G_TYPE_OBJECT)
+
+static void
+msd_a11y_keyboard_atspi_class_init (MsdA11yKeyboardAtspiClass *klass G_GNUC_UNUSED)
+{
+}
+
+static void
+msd_a11y_keyboard_atspi_init (MsdA11yKeyboardAtspi *self G_GNUC_UNUSED)
+{
+}
+
+void
+msd_a11y_keyboard_atspi_start (MsdA11yKeyboardAtspi *self G_GNUC_UNUSED)
+{
+}
+
+void
+msd_a11y_keyboard_atspi_stop (MsdA11yKeyboardAtspi *self G_GNUC_UNUSED)
+{
+}
+
+#endif /* ! defined(HAVE_LIBATSPI) */
+
+MsdA11yKeyboardAtspi *
+msd_a11y_keyboard_atspi_new ()
+{
+ return g_object_new (MSD_TYPE_A11Y_KEYBOARD_ATSPI, NULL);
+}
diff --git a/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.h b/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.h
new file mode 100644
index 0000000..32fc0e7
--- /dev/null
+++ b/plugins/a11y-keyboard/msd-a11y-keyboard-atspi.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2020 Colomban Wendling <[email protected]>
+ *
+ * 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 2, 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __MSD_A11Y_KEYBOARD_ATSPI_H
+#define __MSD_A11Y_KEYBOARD_ATSPI_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MSD_TYPE_A11Y_KEYBOARD_ATSPI (msd_a11y_keyboard_atspi_get_type ())
+#define MSD_A11Y_KEYBOARD_ATSPI(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MSD_TYPE_A11Y_KEYBOARD_ATSPI, MsdA11yKeyboardAtspi))
+#define MSD_A11Y_KEYBOARD_ATSPI_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), MSD_TYPE_A11Y_KEYBOARD_ATSPI, MsdA11yKeyboardAtspiClass))
+#define MSD_IS_A11Y_KEYBOARD_ATSPI(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MSD_TYPE_A11Y_KEYBOARD_ATSPI))
+#define MSD_IS_A11Y_KEYBOARD_ATSPI_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), MSD_TYPE_A11Y_KEYBOARD_ATSPI))
+#define MSD_A11Y_KEYBOARD_ATSPI_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MSD_TYPE_A11Y_KEYBOARD_ATSPI, MsdA11yKeyboardAtspiClass))
+
+typedef struct MsdA11yKeyboardAtspiPrivate MsdA11yKeyboardAtspiPrivate;
+
+typedef struct
+{
+ GObject parent;
+ MsdA11yKeyboardAtspiPrivate *priv;
+} MsdA11yKeyboardAtspi;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} MsdA11yKeyboardAtspiClass;
+
+GType msd_a11y_keyboard_atspi_get_type (void);
+
+MsdA11yKeyboardAtspi *msd_a11y_keyboard_atspi_new (void);
+void msd_a11y_keyboard_atspi_start (MsdA11yKeyboardAtspi *self);
+void msd_a11y_keyboard_atspi_stop (MsdA11yKeyboardAtspi *self);
+
+G_END_DECLS
+
+#endif
diff --git a/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c b/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
index 57ce388..67a690b 100644
--- a/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
+++ b/plugins/a11y-keyboard/msd-a11y-keyboard-manager.c
@@ -49,9 +49,11 @@
#include "mate-settings-profile.h"
#include "msd-a11y-keyboard-manager.h"
+#include "msd-a11y-keyboard-atspi.h"
#include "msd-a11y-preferences-dialog.h"
#define CONFIG_SCHEMA "org.mate.accessibility-keyboard"
+#define KEY_CAPSLOCK_BEEP_ENABLED "capslock-beep-enable"
#define NOTIFICATION_TIMEOUT 30
struct MsdA11yKeyboardManagerPrivate
@@ -64,6 +66,7 @@ struct MsdA11yKeyboardManagerPrivate
GtkWidget *preferences_dialog;
GtkStatusIcon *status_icon;
XkbDescRec *original_xkb_desc;
+ MsdA11yKeyboardAtspi *capslock_beep;
GSettings *settings;
@@ -984,6 +987,17 @@ keyboard_callback (GSettings *settings,
maybe_show_status_icon (manager);
}
+static void
+capslock_beep_callback (GSettings *settings,
+ gchar *key G_GNUC_UNUSED,
+ MsdA11yKeyboardManager *manager)
+{
+ if (g_settings_get_boolean (settings, KEY_CAPSLOCK_BEEP_ENABLED))
+ msd_a11y_keyboard_atspi_start (manager->priv->capslock_beep);
+ else
+ msd_a11y_keyboard_atspi_stop (manager->priv->capslock_beep);
+}
+
static gboolean
start_a11y_keyboard_idle_cb (MsdA11yKeyboardManager *manager)
{
@@ -992,10 +1006,17 @@ start_a11y_keyboard_idle_cb (MsdA11yKeyboardManager *manager)
g_debug ("Starting a11y_keyboard manager");
mate_settings_profile_start (NULL);
+ manager->priv->settings = g_settings_new (CONFIG_SCHEMA);
+
+ manager->priv->capslock_beep = msd_a11y_keyboard_atspi_new ();
+ if (g_settings_get_boolean (manager->priv->settings, KEY_CAPSLOCK_BEEP_ENABLED))
+ msd_a11y_keyboard_atspi_start (manager->priv->capslock_beep);
+ g_signal_connect (manager->priv->settings, "changed::"KEY_CAPSLOCK_BEEP_ENABLED,
+ G_CALLBACK (capslock_beep_callback), manager);
+
if (!xkb_enabled (manager))
goto out;
- manager->priv->settings = g_settings_new (CONFIG_SCHEMA);
g_signal_connect (manager->priv->settings, "changed", G_CALLBACK (keyboard_callback), manager);
set_devicepresence_handler (manager);
@@ -1104,6 +1125,8 @@ msd_a11y_keyboard_manager_stop (MsdA11yKeyboardManager *manager)
p->slowkeys_shortcut_val = FALSE;
p->stickykeys_shortcut_val = FALSE;
+
+ g_clear_object (&p->capslock_beep);
}
static void
diff --git a/plugins/a11y-keyboard/msd-a11y-preferences-dialog.c b/plugins/a11y-keyboard/msd-a11y-preferences-dialog.c
index fa6d426..69cf418 100644
--- a/plugins/a11y-keyboard/msd-a11y-preferences-dialog.c
+++ b/plugins/a11y-keyboard/msd-a11y-preferences-dialog.c
@@ -49,6 +49,7 @@
#define KEY_BOUNCE_KEYS_ENABLED "bouncekeys-enable"
#define KEY_SLOW_KEYS_ENABLED "slowkeys-enable"
#define KEY_MOUSE_KEYS_ENABLED "mousekeys-enable"
+#define KEY_CAPSLOCK_BEEP_ENABLED "capslock-beep-enable"
#define KEY_AT_SCHEMA "org.mate.applications-at"
#define KEY_AT_SCREEN_KEYBOARD_ENABLED "screen-keyboard-enabled"
@@ -91,6 +92,7 @@ struct MsdA11yPreferencesDialogPrivate
GtkWidget *sticky_keys_checkbutton;
GtkWidget *slow_keys_checkbutton;
GtkWidget *bounce_keys_checkbutton;
+ GtkWidget *capslock_beep_checkbutton;
GtkWidget *large_print_checkbutton;
GtkWidget *high_contrast_checkbutton;
@@ -323,6 +325,18 @@ config_set_slow_keys (MsdA11yPreferencesDialog *dialog, gboolean enabled)
}
static gboolean
+config_get_capslock_beep (MsdA11yPreferencesDialog *dialog, gboolean *is_writable)
+{
+ return config_get_bool (dialog->priv->settings_a11y, KEY_CAPSLOCK_BEEP_ENABLED, is_writable);
+}
+
+static void
+config_set_capslock_beep (MsdA11yPreferencesDialog *dialog, gboolean enabled)
+{
+ g_settings_set_boolean (dialog->priv->settings_a11y, KEY_CAPSLOCK_BEEP_ENABLED, enabled);
+}
+
+static gboolean
config_have_at_gsettings_condition (const char *condition)
{
DBusGProxy *sm_proxy;
@@ -422,6 +436,13 @@ on_slow_keys_checkbutton_toggled (GtkToggleButton *button,
}
static void
+on_capslock_beep_checkbutton_toggled (GtkToggleButton *button,
+ MsdA11yPreferencesDialog *dialog)
+{
+ config_set_capslock_beep (dialog, gtk_toggle_button_get_active (button));
+}
+
+static void
on_high_contrast_checkbutton_toggled (GtkToggleButton *button,
MsdA11yPreferencesDialog *dialog)
{
@@ -493,6 +514,18 @@ ui_set_slow_keys (MsdA11yPreferencesDialog *dialog,
}
static void
+ui_set_capslock_beep (MsdA11yPreferencesDialog *dialog,
+ gboolean enabled)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->priv->capslock_beep_checkbutton));
+ if (active != enabled) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->capslock_beep_checkbutton), enabled);
+ }
+}
+
+static void
ui_set_high_contrast (MsdA11yPreferencesDialog *dialog,
gboolean enabled)
{
@@ -569,6 +602,10 @@ key_changed_cb (GSettings *settings,
gboolean enabled;
enabled = g_settings_get_boolean (settings, key);
ui_set_slow_keys (dialog, enabled);
+ } else if (g_strcmp0 (key, KEY_CAPSLOCK_BEEP_ENABLED) == 0) {
+ gboolean enabled;
+ enabled = g_settings_get_boolean (settings, key);
+ ui_set_capslock_beep (dialog, enabled);
} else if (g_strcmp0 (key, KEY_AT_SCREEN_READER_ENABLED) == 0) {
gboolean enabled;
enabled = g_settings_get_boolean (settings, key);
@@ -634,6 +671,19 @@ setup_dialog (MsdA11yPreferencesDialog *dialog,
}
widget = GTK_WIDGET (gtk_builder_get_object (builder,
+ "capslock_beep_checkbutton"));
+ dialog->priv->capslock_beep_checkbutton = widget;
+ g_signal_connect (widget,
+ "toggled",
+ G_CALLBACK (on_capslock_beep_checkbutton_toggled),
+ dialog);
+ enabled = config_get_capslock_beep (dialog, &is_writable);
+ ui_set_capslock_beep (dialog, enabled);
+ if (! is_writable) {
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+
+ widget = GTK_WIDGET (gtk_builder_get_object (builder,
"high_contrast_checkbutton"));
dialog->priv->high_contrast_checkbutton = widget;
g_signal_connect (widget,
diff --git a/plugins/a11y-keyboard/msd-a11y-preferences-dialog.ui b/plugins/a11y-keyboard/msd-a11y-preferences-dialog.ui
index 29b8015..5ed30ff 100644
--- a/plugins/a11y-keyboard/msd-a11y-preferences-dialog.ui
+++ b/plugins/a11y-keyboard/msd-a11y-preferences-dialog.ui
@@ -209,6 +209,22 @@
<property name="position">7</property>
</packing>
</child>
+ <child>
+ <object class="GtkCheckButton" id="capslock_beep_checkbutton">
+ <property name="label" translatable="yes">_Beep when pressing a key while CapsLock is active</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">False</property>