From 3921398e662d0209ebe00d178c0f33e5d164aa77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Thu, 5 Oct 2023 11:11:23 +0200 Subject: [PATCH 1/6] a11y: Introduce an API for requesting speech from a screen reader Sometimes, say, for some status changes communicated otherwise only visually, you want a way how to send a message to a screen reader. This patch implements such API, along with a message priority modeled similarly to ARIA's live region politeness values. We are intentionally not copying the AtkLive enum, as the value of ATK_NONE makes no sense for announcements. --- gtk/a11y/gtkatspicontext.c | 25 +++++++++++++++++++++++++ gtk/gtkaccessible.c | 26 ++++++++++++++++++++++++++ gtk/gtkaccessible.h | 5 +++++ gtk/gtkatcontext.c | 13 ++++++++++++- gtk/gtkatcontextprivate.h | 8 ++++++++ gtk/gtkenums.h | 14 ++++++++++++++ 6 files changed, 90 insertions(+), 1 deletion(-) diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c index d694163a5d..55bfbb5dd6 100644 --- a/gtk/a11y/gtkatspicontext.c +++ b/gtk/a11y/gtkatspicontext.c @@ -1531,6 +1531,30 @@ gtk_at_spi_context_unrealize (GtkATContext *context) g_clear_object (&self->root); } +static void +gtk_at_spi_context_announce (GtkATContext *context, + const char *message, + GtkAccessibleAnnouncementPriority priority) +{ + GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); + + if (self->connection == NULL) + return; + + guint atk_live = priority == GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE ? 2 : 1; + + g_dbus_connection_emit_signal (self->connection, + NULL, + self->context_path, + "org.a11y.atspi.Event.Object", + "Announcement", + g_variant_new ("(siiva{sv})", + "", atk_live, 0, + g_variant_new_string (message), + NULL), + NULL); +} + static void gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass) { @@ -1545,6 +1569,7 @@ gtk_at_spi_context_class_init (GtkAtSpiContextClass *klass) context_class->platform_change = gtk_at_spi_context_platform_change; context_class->bounds_change = gtk_at_spi_context_bounds_change; context_class->child_change = gtk_at_spi_context_child_change; + context_class->announce = gtk_at_spi_context_announce; } static void diff --git a/gtk/gtkaccessible.c b/gtk/gtkaccessible.c index 4c58c69032..0fc86d9a9d 100644 --- a/gtk/gtkaccessible.c +++ b/gtk/gtkaccessible.c @@ -751,6 +751,32 @@ gtk_accessible_reset_relation (GtkAccessible *self, g_object_unref (context); } +/** + * gtk_accessible_announce: + * @self: a `GtkAccessible` + * @message: the string to announce + * @priority: the priority of the announcement + * Requests the user's screen reader to announce the given message. + * + * Since: 4.14 + */ +void +gtk_accessible_announce (GtkAccessible *self, + const char *message, + GtkAccessibleAnnouncementPriority priority) +{ + GtkATContext *context; + + g_return_if_fail (GTK_IS_ACCESSIBLE (self)); + + context = gtk_accessible_get_at_context (self); + if (context == NULL) + return; + + gtk_at_context_announce (context, message, priority); + g_object_unref (context); +} + static const char *role_names[] = { [GTK_ACCESSIBLE_ROLE_ALERT] = NC_("accessibility", "alert"), [GTK_ACCESSIBLE_ROLE_ALERT_DIALOG] = NC_("accessibility", "alert dialog"), diff --git a/gtk/gtkaccessible.h b/gtk/gtkaccessible.h index 5327f48303..657ddd4c9d 100644 --- a/gtk/gtkaccessible.h +++ b/gtk/gtkaccessible.h @@ -260,4 +260,9 @@ GDK_AVAILABLE_IN_4_14 GtkAccessibleList * gtk_accessible_list_new_from_array (GtkAccessible **accessibles, gsize n_accessibles); +GDK_AVAILABLE_IN_4_14 +void gtk_accessible_announce (GtkAccessible *self, + const char *message, + GtkAccessibleAnnouncementPriority priority); + G_END_DECLS diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index b32ef858e9..4611ee3060 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -1,5 +1,5 @@ /* gtkatcontext.c: Assistive technology context - * + * * Copyright 2020 GNOME Foundation * * SPDX-License-Identifier: LGPL-2.1-or-later @@ -1473,3 +1473,14 @@ gtk_at_context_child_changed (GtkATContext *self, GTK_AT_CONTEXT_GET_CLASS (self)->child_change (self, change, child); } + +void +gtk_at_context_announce (GtkATContext *self, + const char *message, + GtkAccessibleAnnouncementPriority priority) +{ + if (!self->realized) + return; + + GTK_AT_CONTEXT_GET_CLASS (self)->announce (self, message, priority); +} \ No newline at end of file diff --git a/gtk/gtkatcontextprivate.h b/gtk/gtkatcontextprivate.h index d9489fa3c9..e65b5f8279 100644 --- a/gtk/gtkatcontextprivate.h +++ b/gtk/gtkatcontextprivate.h @@ -127,6 +127,10 @@ struct _GtkATContextClass void (* realize) (GtkATContext *self); void (* unrealize) (GtkATContext *self); + + void (* announce) (GtkATContext *self, + const char *message, + GtkAccessibleAnnouncementPriority priority); }; GtkATContext * gtk_at_context_clone (GtkATContext *self, @@ -193,4 +197,8 @@ void gtk_at_context_set_next_accessible_sibling (GtkATContext *self, GtkAccessible *sibling); +void gtk_at_context_announce (GtkATContext *self, + const char *message, + GtkAccessibleAnnouncementPriority priority); + G_END_DECLS diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 52a734572e..8af4bcd0c6 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1801,6 +1801,20 @@ typedef enum { /*< prefix=GTK_ACCESSIBLE_SORT >*/ GTK_ACCESSIBLE_SORT_OTHER } GtkAccessibleSort; +/** + * GtkAccessibleAnnouncementPriority: + * @GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_POLITE: The announcement is not + * important enough to interrupt the user. + * @GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE: The announcement is + * important enough to interrupt the user. + * + * The possible values for an accessible announcement priority. + */ +typedef enum { + GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_POLITE, + GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE +} GtkAccessibleAnnouncementPriority; + /** * GtkPopoverMenuFlags: * @GTK_POPOVER_MENU_SLIDING: Submenus are presented as sliding submenus that From bff31f58a80b40dffd15a994de6bc3707f7fed51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Fri, 6 Oct 2023 16:25:04 +0200 Subject: [PATCH 2/6] Expand the docs on when you would want to use this API --- gtk/gtkaccessible.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gtk/gtkaccessible.c b/gtk/gtkaccessible.c index 0fc86d9a9d..15daa06cff 100644 --- a/gtk/gtkaccessible.c +++ b/gtk/gtkaccessible.c @@ -756,7 +756,15 @@ gtk_accessible_reset_relation (GtkAccessible *self, * @self: a `GtkAccessible` * @message: the string to announce * @priority: the priority of the announcement + * * Requests the user's screen reader to announce the given message. + * This kind of notification is useful for messages that + * either have only a visual representation or that are not + * exposed visually at all, e.g. a notification about a + * successful operation. + * + * Also, by using this API, you can ensure that the message + * does not interrupts the user's current screen reader output. * * Since: 4.14 */ From f6c84a435e470d584f284ffdde6473aa08c0fcbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Wed, 24 Jan 2024 13:46:58 +0100 Subject: [PATCH 3/6] Don't use ARIA terminology --- gtk/a11y/gtkatspicontext.c | 2 +- gtk/gtkenums.h | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c index 55bfbb5dd6..9d1a1fb0a4 100644 --- a/gtk/a11y/gtkatspicontext.c +++ b/gtk/a11y/gtkatspicontext.c @@ -1541,7 +1541,7 @@ gtk_at_spi_context_announce (GtkATContext *context, if (self->connection == NULL) return; - guint atk_live = priority == GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE ? 2 : 1; + guint atk_live = priority == GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH ? 2 : 1; g_dbus_connection_emit_signal (self->connection, NULL, diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 8af4bcd0c6..ef5859b7da 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1802,19 +1802,20 @@ typedef enum { /*< prefix=GTK_ACCESSIBLE_SORT >*/ } GtkAccessibleSort; /** - * GtkAccessibleAnnouncementPriority: - * @GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_POLITE: The announcement is not - * important enough to interrupt the user. - * @GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE: The announcement is - * important enough to interrupt the user. - * - * The possible values for an accessible announcement priority. + * GTK_ANNOUNCEMENT_PRIORITY: + * + * @GTK_ANNOUNCEMENT_PRIORITY_LOW: The announcement is low priority, and might be read only on the user's request. + * @GTK_ANNOUNCEMENT_PRIORITY_MEDIUM: The announcement is of medium priority, and is usually spoken only after announcements. + * @GTK_ANNOUNCEMENT_PRIORITY_HIGH: The announcement is of high priority, and is usually spoken immediately. + * + * The priority of an accessibility announcement. */ typedef enum { - GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_POLITE, - GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_ASSERTIVE -} GtkAccessibleAnnouncementPriority; - + GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_LOW, + GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_MEDIUM, + GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH +} GtkAccessibleAnnouncementPriority; + /** * GtkPopoverMenuFlags: * @GTK_POPOVER_MENU_SLIDING: Submenus are presented as sliding submenus that From 887d5307ca5ea397a4a3b0fdfa8c7d398598be22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Thu, 1 Feb 2024 10:52:08 +0100 Subject: [PATCH 4/6] Apply review feedback, namely: * Add a forgotten since annotation * Map the GTK's announcement priority explicitly --- gtk/a11y/gtkatspiprivate.h | 5 +++++ gtk/gtkenums.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/gtk/a11y/gtkatspiprivate.h b/gtk/a11y/gtkatspiprivate.h index cdde64dd83..2e869ca4d1 100644 --- a/gtk/a11y/gtkatspiprivate.h +++ b/gtk/a11y/gtkatspiprivate.h @@ -277,6 +277,11 @@ typedef enum { ATSPI_SCROLL_ANYWHERE } AtspiScrollType; +typedef enum { + ATSPI_LIVE_POLITE = 1, + ATSPI_LIVE_ASSERTIVE = 2 +} AtspiLive; + typedef struct _GtkAtSpiRoot GtkAtSpiRoot; typedef struct _GtkAtSpiCache GtkAtSpiCache; typedef struct _GtkAtSpiContext GtkAtSpiContext; diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index ef5859b7da..36b343594d 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1809,6 +1809,8 @@ typedef enum { /*< prefix=GTK_ACCESSIBLE_SORT >*/ * @GTK_ANNOUNCEMENT_PRIORITY_HIGH: The announcement is of high priority, and is usually spoken immediately. * * The priority of an accessibility announcement. + * + * Since: 4.14 */ typedef enum { GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_LOW, From 28cec6c9a744d36ae88c111235ef872c808d5854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tyrychtr?= Date: Thu, 1 Feb 2024 11:11:15 +0100 Subject: [PATCH 5/6] Expand documentation for medium and high priority announcements --- gtk/a11y/gtkatspicontext.c | 17 +++++++++++++++-- gtk/gtkenums.h | 6 +++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c index 9d1a1fb0a4..c60130d381 100644 --- a/gtk/a11y/gtkatspicontext.c +++ b/gtk/a11y/gtkatspicontext.c @@ -1541,7 +1541,20 @@ gtk_at_spi_context_announce (GtkATContext *context, if (self->connection == NULL) return; - guint atk_live = priority == GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH ? 2 : 1; + AtspiLive live; + + switch (priority) + { + case GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_LOW: + case GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_MEDIUM: + live = ATSPI_LIVE_POLITE; + break; + case GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH: + live = ATSPI_LIVE_ASSERTIVE; + break; + default: + g_assert_not_reached (); + } g_dbus_connection_emit_signal (self->connection, NULL, @@ -1549,7 +1562,7 @@ gtk_at_spi_context_announce (GtkATContext *context, "org.a11y.atspi.Event.Object", "Announcement", g_variant_new ("(siiva{sv})", - "", atk_live, 0, + "", live, 0, g_variant_new_string (message), NULL), NULL); diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 36b343594d..7d49df62a1 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1805,8 +1805,12 @@ typedef enum { /*< prefix=GTK_ACCESSIBLE_SORT >*/ * GTK_ANNOUNCEMENT_PRIORITY: * * @GTK_ANNOUNCEMENT_PRIORITY_LOW: The announcement is low priority, and might be read only on the user's request. - * @GTK_ANNOUNCEMENT_PRIORITY_MEDIUM: The announcement is of medium priority, and is usually spoken only after announcements. + * @GTK_ANNOUNCEMENT_PRIORITY_MEDIUM: The announcement is of medium priority, and is usually spoken at the next opportunity, + * such as at the end of speaking the current sentence or when the user pauses typing. * @GTK_ANNOUNCEMENT_PRIORITY_HIGH: The announcement is of high priority, and is usually spoken immediately. + * Because an interruption might disorient users or cause them to not complete their current task, + * authors SHOULD NOT use high priority announcements unless the interruption is imperative. + * An example would be a notification about a critical battery power level. * * The priority of an accessibility announcement. * From 7880095fd84648a96ff5dbb5f4f5e77311e61af8 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 8 Feb 2024 22:40:17 -0500 Subject: [PATCH 6/6] Stylistic cleanups --- gtk/a11y/gtkatspicontext.c | 9 ++++----- gtk/gtkaccessible.c | 9 +++++---- gtk/gtkaccessible.h | 6 +++--- gtk/gtkatcontext.c | 10 +++++----- gtk/gtkenums.h | 22 ++++++++++++---------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/gtk/a11y/gtkatspicontext.c b/gtk/a11y/gtkatspicontext.c index c60130d381..9c61b86526 100644 --- a/gtk/a11y/gtkatspicontext.c +++ b/gtk/a11y/gtkatspicontext.c @@ -1532,17 +1532,16 @@ gtk_at_spi_context_unrealize (GtkATContext *context) } static void -gtk_at_spi_context_announce (GtkATContext *context, - const char *message, - GtkAccessibleAnnouncementPriority priority) +gtk_at_spi_context_announce (GtkATContext *context, + const char *message, + GtkAccessibleAnnouncementPriority priority) { GtkAtSpiContext *self = GTK_AT_SPI_CONTEXT (context); + AtspiLive live; if (self->connection == NULL) return; - AtspiLive live; - switch (priority) { case GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_LOW: diff --git a/gtk/gtkaccessible.c b/gtk/gtkaccessible.c index 15daa06cff..2bac2ce960 100644 --- a/gtk/gtkaccessible.c +++ b/gtk/gtkaccessible.c @@ -758,6 +758,7 @@ gtk_accessible_reset_relation (GtkAccessible *self, * @priority: the priority of the announcement * * Requests the user's screen reader to announce the given message. + * * This kind of notification is useful for messages that * either have only a visual representation or that are not * exposed visually at all, e.g. a notification about a @@ -769,9 +770,9 @@ gtk_accessible_reset_relation (GtkAccessible *self, * Since: 4.14 */ void -gtk_accessible_announce (GtkAccessible *self, - const char *message, - GtkAccessibleAnnouncementPriority priority) +gtk_accessible_announce (GtkAccessible *self, + const char *message, + GtkAccessibleAnnouncementPriority priority) { GtkATContext *context; @@ -784,7 +785,7 @@ gtk_accessible_announce (GtkAccessible *self, gtk_at_context_announce (context, message, priority); g_object_unref (context); } - + static const char *role_names[] = { [GTK_ACCESSIBLE_ROLE_ALERT] = NC_("accessibility", "alert"), [GTK_ACCESSIBLE_ROLE_ALERT_DIALOG] = NC_("accessibility", "alert dialog"), diff --git a/gtk/gtkaccessible.h b/gtk/gtkaccessible.h index 657ddd4c9d..9826e95f11 100644 --- a/gtk/gtkaccessible.h +++ b/gtk/gtkaccessible.h @@ -261,8 +261,8 @@ GtkAccessibleList * gtk_accessible_list_new_from_array (GtkAccessible **accessib gsize n_accessibles); GDK_AVAILABLE_IN_4_14 -void gtk_accessible_announce (GtkAccessible *self, - const char *message, - GtkAccessibleAnnouncementPriority priority); +void gtk_accessible_announce (GtkAccessible *self, + const char *message, + GtkAccessibleAnnouncementPriority priority); G_END_DECLS diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index 4611ee3060..f16e7490e4 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -1,5 +1,5 @@ /* gtkatcontext.c: Assistive technology context - * + * * Copyright 2020 GNOME Foundation * * SPDX-License-Identifier: LGPL-2.1-or-later @@ -1475,12 +1475,12 @@ gtk_at_context_child_changed (GtkATContext *self, } void -gtk_at_context_announce (GtkATContext *self, - const char *message, - GtkAccessibleAnnouncementPriority priority) +gtk_at_context_announce (GtkATContext *self, + const char *message, + GtkAccessibleAnnouncementPriority priority) { if (!self->realized) return; GTK_AT_CONTEXT_GET_CLASS (self)->announce (self, message, priority); -} \ No newline at end of file +} diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 7d49df62a1..2fe04619d0 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1803,15 +1803,17 @@ typedef enum { /*< prefix=GTK_ACCESSIBLE_SORT >*/ /** * GTK_ANNOUNCEMENT_PRIORITY: - * - * @GTK_ANNOUNCEMENT_PRIORITY_LOW: The announcement is low priority, and might be read only on the user's request. - * @GTK_ANNOUNCEMENT_PRIORITY_MEDIUM: The announcement is of medium priority, and is usually spoken at the next opportunity, - * such as at the end of speaking the current sentence or when the user pauses typing. - * @GTK_ANNOUNCEMENT_PRIORITY_HIGH: The announcement is of high priority, and is usually spoken immediately. - * Because an interruption might disorient users or cause them to not complete their current task, - * authors SHOULD NOT use high priority announcements unless the interruption is imperative. - * An example would be a notification about a critical battery power level. - * + * @GTK_ANNOUNCEMENT_PRIORITY_LOW: The announcement is low priority, and might be read only + * on the user's request. + * @GTK_ANNOUNCEMENT_PRIORITY_MEDIUM: The announcement is of medium priority, and is usually + * spoken at the next opportunity, such as at the end of speaking the current sentence + * or when the user pauses typing. + * @GTK_ANNOUNCEMENT_PRIORITY_HIGH: The announcement is of high priority, and is usually + * spoken immediately. Because an interruption might disorient users or cause them to + * not complete their current task, authors SHOULD NOT use high priority announcements + * unless the interruption is imperative. An example would be a notification about a + * critical battery power level. + * * The priority of an accessibility announcement. * * Since: 4.14 @@ -1820,7 +1822,7 @@ typedef enum { GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_LOW, GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_MEDIUM, GTK_ACCESSIBLE_ANNOUNCEMENT_PRIORITY_HIGH -} GtkAccessibleAnnouncementPriority; +} GtkAccessibleAnnouncementPriority; /** * GtkPopoverMenuFlags: