From b13fa490fb368b779a99543d18d5c68c01f8eb31 Mon Sep 17 00:00:00 2001 From: Luke Yelavich Date: Fri, 4 Feb 2011 13:57:23 +1100 Subject: Add accessible_name support --- bindings/mono/Makefile.am | 1 + bindings/mono/libappindicator-api.metadata | 6 ++ bindings/python/appindicator.defs | 15 +++ docs/reference/libappindicator-sections.txt | 3 + example/simple-client.c | 29 +++++ src/app-indicator.c | 161 +++++++++++++++++++++++++++- src/app-indicator.h | 13 +++ src/notification-item.xml | 4 + tests/test-libappindicator.c | 110 +++++++++++++++++++ 9 files changed, 341 insertions(+), 1 deletion(-) diff --git a/bindings/mono/Makefile.am b/bindings/mono/Makefile.am index dfaa41f..1f23742 100644 --- a/bindings/mono/Makefile.am +++ b/bindings/mono/Makefile.am @@ -94,6 +94,7 @@ $(API): $(MIDDLE_API) Makefile.am -e "s|PROP_LABEL_S|label|" \ -e "s|PROP_LABEL_GUIDE_S|label-guide|" \ -e "s|PROP_ORDERING_INDEX_S|ordering-index|" \ + -e "s|PROP_ACCESSIBLE_NAME_S|accessible-name|" \ $< > $@ api_includes = $(GTK_SHARP_CFLAGS) diff --git a/bindings/mono/libappindicator-api.metadata b/bindings/mono/libappindicator-api.metadata index ccf58f1..d818e60 100644 --- a/bindings/mono/libappindicator-api.metadata +++ b/bindings/mono/libappindicator-api.metadata @@ -7,6 +7,8 @@ new-status NewLabel new-label + NewAccessibleName + new-accessible-name ConnectionChanged connection-changed NewIcon @@ -24,6 +26,7 @@ Connected Label LabelGuide + AccessibleName OrderingIndex SetMenu @@ -38,6 +41,7 @@ + @@ -48,6 +52,7 @@ + @@ -55,4 +60,5 @@ + diff --git a/bindings/python/appindicator.defs b/bindings/python/appindicator.defs index 5027a6d..8c4efb3 100644 --- a/bindings/python/appindicator.defs +++ b/bindings/python/appindicator.defs @@ -72,6 +72,15 @@ ) ) +(define-method set_accessible_name + (of-object "AppIndicator") + (c-name "app_indicator_set_accessible_name") + (return-type "none") + (parameters + '("const-gchar*" "accessible_name" (null-ok)) + ) +) + (define-method set_ordering_index (of-object "AppIndicator") (c-name "app_indicator_set_ordering_index") @@ -146,6 +155,12 @@ (return-type "const-gchar*") ) +(define-method get_accessible_name + (of-object "AppIndicator") + (c-name "app_indicator_get_accessible_name") + (return-type "const-gchar*") +) + (define-method get_ordering_index (of-object "AppIndicator") (c-name "app_indicator_get_ordering_index") diff --git a/docs/reference/libappindicator-sections.txt b/docs/reference/libappindicator-sections.txt index 41ff7fa..3434588 100644 --- a/docs/reference/libappindicator-sections.txt +++ b/docs/reference/libappindicator-sections.txt @@ -10,6 +10,7 @@ APP_INDICATOR_SIGNAL_NEW_ICON APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON APP_INDICATOR_SIGNAL_NEW_STATUS APP_INDICATOR_SIGNAL_NEW_LABEL +APP_INDICATOR_SIGNAL_NEW_ACCESSIBLE_NAME APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH APP_INDICATOR_SIGNAL_CONNECTION_CHANGED AppIndicatorCategory @@ -27,6 +28,7 @@ app_indicator_set_menu app_indicator_set_icon app_indicator_set_icon_theme_path app_indicator_set_label +app_indicator_set_accessible_name app_indicator_set_ordering_index app_indicator_get_id app_indicator_get_category @@ -37,6 +39,7 @@ app_indicator_get_attention_icon app_indicator_get_menu app_indicator_get_label app_indicator_get_label_guide +app_indicator_get_accessible_name app_indicator_get_ordering_index app_indicator_build_menu_from_desktop diff --git a/example/simple-client.c b/example/simple-client.c index ac8360f..9c9a14b 100644 --- a/example/simple-client.c +++ b/example/simple-client.c @@ -27,6 +27,7 @@ with this program. If not, see . GMainLoop * mainloop = NULL; static gboolean active = TRUE; static gboolean can_haz_label = TRUE; +static gboolean can_haz_a11yname = TRUE; static void label_toggle_cb (GtkWidget * widget, gpointer data) @@ -42,6 +43,20 @@ label_toggle_cb (GtkWidget * widget, gpointer data) return; } +static void +a11yname_toggle_cb (GtkWidget * widget, gpointer data) +{ + can_haz_a11yname = !can_haz_a11yname; + + if (can_haz_a11yname) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), "Hide accessible name"); + } else { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), "Show accessible name"); + } + + return; +} + static void activate_clicked_cb (GtkWidget *widget, gpointer data) { @@ -140,6 +155,13 @@ percent_change (gpointer user_data) } else { app_indicator_set_label (APP_INDICATOR(user_data), NULL, NULL); } + if (can_haz_a11yname) { + gchar * percentstr = g_strdup_printf("%d%%", percentage + 1); + app_indicator_set_accessible_name (APP_INDICATOR(user_data), percentstr); + g_free(percentstr); + } else { + app_indicator_set_accessible_name (APP_INDICATOR(user_data), NULL); + } return TRUE; } @@ -213,6 +235,13 @@ main (int argc, char ** argv) gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show(item); + item = gtk_menu_item_new_with_label ("Show accessible name"); + a11yname_toggle_cb(item, ci); + g_signal_connect (item, "activate", + G_CALLBACK (a11yname_toggle_cb), ci); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show(item); + app_indicator_set_menu (ci, GTK_MENU (menu)); mainloop = g_main_loop_new(NULL, FALSE); diff --git a/src/app-indicator.c b/src/app-indicator.c index 039b980..f815fac 100644 --- a/src/app-indicator.c +++ b/src/app-indicator.c @@ -80,7 +80,9 @@ struct _AppIndicatorPrivate { guint32 ordering_index; gchar * label; gchar * label_guide; + gchar * accessible_name; guint label_change_idle; + guint accessible_name_change_idle; GtkStatusIcon * status_icon; gint fallback_timer; @@ -103,6 +105,7 @@ enum { NEW_LABEL, CONNECTION_CHANGED, NEW_ICON_THEME_PATH, + NEW_ACCESSIBLE_NAME, LAST_SIGNAL }; @@ -122,7 +125,8 @@ enum { PROP_LABEL, PROP_LABEL_GUIDE, PROP_ORDERING_INDEX, - PROP_DBUS_MENU_SERVER + PROP_DBUS_MENU_SERVER, + PROP_ACCESSIBLE_NAME }; /* The strings so that they can be slowly looked up. */ @@ -137,6 +141,7 @@ enum { #define PROP_LABEL_GUIDE_S "label-guide" #define PROP_ORDERING_INDEX_S "ordering-index" #define PROP_DBUS_MENU_SERVER_S "dbus-menu-server" +#define PROP_ACCESSIBLE_NAME_S "accessible-name" /* Private macro, shhhh! */ #define APP_INDICATOR_GET_PRIVATE(o) \ @@ -164,6 +169,7 @@ static void app_indicator_set_property (GObject * object, guint prop_id, const G static void app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); /* Other stuff */ static void signal_label_change (AppIndicator * self); +static void signal_accessible_name_change (AppIndicator * self); static void check_connect (AppIndicator * self); static void register_service_cb (GObject * obj, GAsyncResult * res, gpointer user_data); static void start_fallback_timer (AppIndicator * self, gboolean disable_timeout); @@ -341,6 +347,22 @@ app_indicator_class_init (AppIndicatorClass *klass) "To ensure that the label does not cause the panel to 'jiggle' this string should provide information on how much space it could take.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:accessible-name: + + A string that describes the indicator in text form. This string is + used to identify the indicator to users of assistive technologies, such + as screen readers. If the indicator has a label, then duplicating the + contents of the label is fine, if the label alone conveys enough + information about the state of the indicator to the user. + */ + g_object_class_install_property(object_class, + PROP_ACCESSIBLE_NAME, + g_param_spec_string (PROP_ACCESSIBLE_NAME_S, + "A string that describes the indicator in text form.", + "This string shoudl convey a description of the indicator's current status, for users who use assistive technologies.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** AppIndicator:ordering-index: @@ -438,6 +460,21 @@ app_indicator_class_init (AppIndicatorClass *klass) _application_service_marshal_VOID__STRING_STRING, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + /** + AppIndicator::new-accessible-name: + @arg0: The #AppIndicator object + @arg1: The string for the accessible name + + Emitted when #AppIndicator:accessible_name changes. + */ + signals[NEW_ACCESSIBLE_NAME] = g_signal_new (APP_INDICATOR_SIGNAL_NEW_ACCESSIBLE_NAME, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AppIndicatorClass, new_accessible_name), + NULL, NULL, + _application_service_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + /** AppIndicator::connection-changed: @@ -527,6 +564,7 @@ app_indicator_init (AppIndicator *self) priv->label = NULL; priv->label_guide = NULL; priv->label_change_idle = 0; + priv->accessible_name_change_idle = 0; priv->watcher_proxy = NULL; priv->connection = NULL; @@ -590,6 +628,11 @@ app_indicator_dispose (GObject *object) priv->label_change_idle = 0; } + if (priv->accessible_name_change_idle != 0) { + g_source_remove(priv->accessible_name_change_idle); + priv->accessible_name_change_idle = 0; + } + if (priv->menu != NULL) { g_signal_handlers_disconnect_by_func (G_OBJECT (priv->menu), client_menu_changed, @@ -667,6 +710,11 @@ app_indicator_finalize (GObject *object) priv->label_guide = NULL; } + if (priv->accessible_name != NULL) { + g_free(priv->accessible_name); + priv->accessible_name = NULL; + } + if (priv->path != NULL) { g_free(priv->path); priv->path = NULL; @@ -778,6 +826,24 @@ app_indicator_set_property (GObject * object, guint prop_id, const GValue * valu } break; } + case PROP_ACCESSIBLE_NAME: { + gchar * olda11yname = priv->accessible_name; + priv->accessible_name = g_value_dup_string(value); + + if (g_strcmp0(olda11yname, priv->accessible_name) != 0) { + signal_accessible_name_change(APP_INDICATOR(object)); + } + + if (priv->accessible_name != NULL && priv->accessible_name[0] == '\0') { + g_free(priv->accessible_name); + priv->accessible_name = NULL; + } + + if (olda11yname != NULL) { + g_free(olda11yname); + } + break; + } case PROP_ORDERING_INDEX: priv->ordering_index = g_value_get_uint(value); break; @@ -856,6 +922,10 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa g_value_set_string (value, priv->label_guide); break; + case PROP_ACCESSIBLE_NAME: + g_value_set_string (value, priv->accessible_name); + break; + case PROP_ORDERING_INDEX: g_value_set_uint(value, priv->ordering_index); break; @@ -941,6 +1011,8 @@ bus_get_prop (GDBusConnection * connection, const gchar * sender, const gchar * return g_variant_new_string(priv->label_guide ? priv->label_guide : ""); } else if (g_strcmp0(property, "XAyatanaOrderingIndex") == 0) { return g_variant_new_uint32(priv->ordering_index); + } else if (g_strcmp0(property, "AccessibleName") == 0) { + return g_variant_new_string(priv->accessible_name ? priv->accessible_name : ""); } *error = g_error_new(0, 0, "Unknown property: %s", property); @@ -997,6 +1069,55 @@ signal_label_change (AppIndicator * self) return; } +/* Sends the accessible name changed signal and resets the source ID */ +static gboolean +signal_accessible_name_change_idle (gpointer user_data) +{ + AppIndicator * self = (AppIndicator *)user_data; + AppIndicatorPrivate *priv = self->priv; + + gchar * accessible_name = priv->accessible_name != NULL ? priv->accessible_name : ""; + + g_signal_emit(G_OBJECT(self), signals[NEW_ACCESSIBLE_NAME], 0, + accessible_name, TRUE); + if (priv->dbus_registration != 0 && priv->connection != NULL) { + GError * error = NULL; + + g_dbus_connection_emit_signal(priv->connection, + NULL, + priv->path, + NOTIFICATION_ITEM_DBUS_IFACE, + "NewAccessibleName", + g_variant_new("(s)", accessible_name), + &error); + + if (error != NULL) { + g_warning("Unable to send signal for NewIcon: %s", error->message); + g_error_free(error); + } + } + + priv->accessible_name_change_idle = 0; + + return FALSE; +} + +/* Sets up an idle function to send the accessible name changed + signal so that we don't send it too many times. */ +static void +signal_accessible_name_change (AppIndicator * self) +{ + AppIndicatorPrivate *priv = self->priv; + + /* don't set it twice */ + if (priv->accessible_name_change_idle != 0) { + return; + } + + priv->accessible_name_change_idle = g_idle_add(signal_accessible_name_change_idle, self); + return; +} + /* This function is used to see if we have enough information to connect to things. If we do, and we're not connected, it connects for us. */ @@ -1638,6 +1759,28 @@ app_indicator_set_label (AppIndicator *self, const gchar * label, const gchar * return; } +/** + app_indicator_set_accessible_name: + @self: The #AppIndicator object to use + @accessible_name: The accessible name used by assistive technologies. + + This is a wrapper function for the #AppIndicator:accessible_name + property. This function can take #NULL as @accessible_name and + will clear the entry. +*/ +void +app_indicator_set_accessible_name (AppIndicator *self, const gchar * accessible_name) +{ + g_return_if_fail (IS_APP_INDICATOR (self)); + /* Note: The accessible name can be NULL, it's okay */ + + g_object_set(G_OBJECT(self), + PROP_ACCESSIBLE_NAME_S, accessible_name == NULL ? "" : accessible_name, + NULL); + + return; +} + /** app_indicator_set_icon_theme_path: @self: The #AppIndicator object to use @@ -1932,6 +2075,22 @@ app_indicator_get_label_guide (AppIndicator *self) return self->priv->label_guide; } +/** + app_indicator_get_accessible_name: + @self: The #AppIndicator object to use + + Wrapper function for property #AppIndicator:accessible_name. + + Return value: The current accessible name. +*/ +const gchar * +app_indicator_get_accessible_name (AppIndicator *self) +{ + g_return_val_if_fail (IS_APP_INDICATOR (self), NULL); + + return self->priv->accessible_name; +} + /** app_indicator_get_ordering_index: @self: The #AppIndicator object to use diff --git a/src/app-indicator.h b/src/app-indicator.h index 3e159db..dac87ec 100644 --- a/src/app-indicator.h +++ b/src/app-indicator.h @@ -107,12 +107,18 @@ G_BEGIN_DECLS String identifier for the #AppIndicator::new-icon-theme-path signal. */ +/** + APP_INDICATOR_SIGNAL_NEW_ACCESSIBLE_NAME: + + String identifier for the #AppIndicator::new-accessible-name signal. +*/ #define APP_INDICATOR_SIGNAL_NEW_ICON "new-icon" #define APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON "new-attention-icon" #define APP_INDICATOR_SIGNAL_NEW_STATUS "new-status" #define APP_INDICATOR_SIGNAL_NEW_LABEL "new-label" #define APP_INDICATOR_SIGNAL_CONNECTION_CHANGED "connection-changed" #define APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH "new-icon-theme-path" +#define APP_INDICATOR_SIGNAL_NEW_ACCESSIBLE_NAME "new-accessible-name" /** AppIndicatorCategory: @@ -162,6 +168,7 @@ typedef struct _AppIndicatorPrivate AppIndicatorPrivate; @new_status: Slot for #AppIndicator::new-status. @new_icon_theme_path: Slot for #AppIndicator::new-icon-theme-path @new_label: Slot for #AppIndicator::new-label. + @new_accessible_name: Slot for #AppIndicator::new-accessible-name. @connection_changed: Slot for #AppIndicator::connection-changed. @app_indicator_reserved_sw: Reserved for future use. @app_indicator_reserved_ats: Reserved for future use. @@ -198,6 +205,9 @@ struct _AppIndicatorClass { const gchar *label, const gchar *guide, gpointer user_data); + void (* new_accessible_name) (AppIndicator *indicator, + const gchar *accessible_name); + /* Local Signals */ void (* connection_changed) (AppIndicator * indicator, @@ -263,6 +273,8 @@ void app_indicator_set_icon (AppIndicator void app_indicator_set_label (AppIndicator *self, const gchar *label, const gchar *guide); +void app_indicator_set_accessible_name(AppIndicator *self, + const gchar *accessible_name); void app_indicator_set_icon_theme_path(AppIndicator *self, const gchar *icon_theme_path); void app_indicator_set_ordering_index (AppIndicator *self, @@ -278,6 +290,7 @@ const gchar * app_indicator_get_attention_icon (AppIndicator * GtkMenu * app_indicator_get_menu (AppIndicator *self); const gchar * app_indicator_get_label (AppIndicator *self); const gchar * app_indicator_get_label_guide (AppIndicator *self); +const gchar * app_indicator_get_accessible_name(AppIndicator *self); guint32 app_indicator_get_ordering_index (AppIndicator *self); /* Helpers */ diff --git a/src/notification-item.xml b/src/notification-item.xml index 05afd83..63f9374 100644 --- a/src/notification-item.xml +++ b/src/notification-item.xml @@ -15,6 +15,7 @@ + @@ -34,6 +35,9 @@ + + + diff --git a/tests/test-libappindicator.c b/tests/test-libappindicator.c index cadf783..0b866f8 100644 --- a/tests/test-libappindicator.c +++ b/tests/test-libappindicator.c @@ -227,6 +227,46 @@ test_libappindicator_set_label (void) return; } +void +test_libappindicator_set_accessible_name (void) +{ + AppIndicator * ci = app_indicator_new ("my-id", + "my-name", + APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + + g_assert(ci != NULL); + g_assert(app_indicator_get_accessible_name(ci) == NULL); + + /* First check all the clearing modes, this is important as + we're going to use them later, we need them to work. */ + app_indicator_set_accessible_name(ci, NULL); + + g_assert(app_indicator_get_accessible_name(ci) == NULL); + + app_indicator_set_accessible_name(ci, ""); + + g_assert(app_indicator_get_accessible_name(ci) == NULL); + + app_indicator_set_accessible_name(ci, "accessible_name"); + + g_assert(g_strcmp0(app_indicator_get_accessible_name(ci), "accessible_name") == 0); + + app_indicator_set_accessible_name(ci, NULL); + + g_assert(app_indicator_get_accessible_name(ci) == NULL); + + app_indicator_set_accessible_name(ci, "accessible_name2"); + + g_assert(g_strcmp0(app_indicator_get_accessible_name(ci), "accessible_name2") == 0); + + app_indicator_set_accessible_name(ci, "trick-accessible_name"); + + g_assert(g_strcmp0(app_indicator_get_accessible_name(ci), "trick-accessible_name") == 0); + + g_object_unref(G_OBJECT(ci)); + return; +} + void test_libappindicator_set_menu (void) { @@ -286,6 +326,14 @@ label_signals_cb (AppIndicator * appindicator, gchar * label, gchar * guide, gpo return; } +void +accessible_name_signals_cb (AppIndicator * appindicator, gchar * accessible_name, gpointer user_data) +{ + gint * accessible_name_signals_count = (gint *)user_data; + (*accessible_name_signals_count)++; + return; +} + void label_signals_check (void) { @@ -296,6 +344,16 @@ label_signals_check (void) return; } +void +accessible_name_signals_check (void) +{ + while (g_main_context_pending(NULL)) { + g_main_context_iteration(NULL, TRUE); + } + + return; +} + void test_libappindicator_label_signals (void) { @@ -347,6 +405,56 @@ test_libappindicator_label_signals (void) return; } +void +test_libappindicator_accessible_name_signals (void) +{ + gint accessible_name_signals_count = 0; + AppIndicator * ci = app_indicator_new ("my-id", + "my-name", + APP_INDICATOR_CATEGORY_APPLICATION_STATUS); + + g_assert(ci != NULL); + g_assert(app_indicator_get_accessible_name(ci) == NULL); + + g_signal_connect(G_OBJECT(ci), APP_INDICATOR_SIGNAL_NEW_ACCESSIBLE_NAME, G_CALLBACK(accessible_name_signals_cb), &accessible_name_signals_count); + + /* Shouldn't be a signal as it should be stuck in idle */ + app_indicator_set_accessible_name(ci, "accessible_name"); + g_assert(accessible_name_signals_count == 0); + + /* Should show up after idle processing */ + accessible_name_signals_check(); + g_assert(accessible_name_signals_count == 1); + + /* Shouldn't signal with no change */ + accessible_name_signals_count = 0; + app_indicator_set_accessible_name(ci, "accessible_name"); + accessible_name_signals_check(); + g_assert(accessible_name_signals_count == 0); + + /* Change one, we should get one signal */ + app_indicator_set_accessible_name(ci, "accessible_name2"); + accessible_name_signals_check(); + g_assert(accessible_name_signals_count == 1); + + /* Change several times, one signal */ + accessible_name_signals_count = 0; + app_indicator_set_accessible_name(ci, "accessible_name1"); + app_indicator_set_accessible_name(ci, "accessible_name1"); + app_indicator_set_accessible_name(ci, "accessible_name2"); + app_indicator_set_accessible_name(ci, "accessible_name3"); + accessible_name_signals_check(); + g_assert(accessible_name_signals_count == 1); + + /* Clear should signal too */ + accessible_name_signals_count = 0; + app_indicator_set_accessible_name(ci, NULL); + accessible_name_signals_check(); + g_assert(accessible_name_signals_count == 1); + + return; +} + void test_libappindicator_desktop_menu (void) { @@ -428,6 +536,8 @@ test_libappindicator_props_suite (void) g_test_add_func ("/indicator-application/libappindicator/label_signals", test_libappindicator_label_signals); g_test_add_func ("/indicator-application/libappindicator/desktop_menu", test_libappindicator_desktop_menu); g_test_add_func ("/indicator-application/libappindicator/desktop_menu_bad",test_libappindicator_desktop_menu_bad); + g_test_add_func ("/indicator-application/libappindicator/set_accessible_name",test_libappindicator_set_accessible_name); + g_test_add_func ("/indicator-application/libappindicator/accessible_name_signals",test_libappindicator_accessible_name_signals); return; } -- cgit v1.2.3