diff options
Diffstat (limited to 'src/app-indicator.c')
-rw-r--r-- | src/app-indicator.c | 487 |
1 files changed, 466 insertions, 21 deletions
diff --git a/src/app-indicator.c b/src/app-indicator.c index a52a063..98663c1 100644 --- a/src/app-indicator.c +++ b/src/app-indicator.c @@ -32,6 +32,8 @@ License version 3 and version 2.1 along with this program. If not, see #endif #include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-bindings.h> + #include <libdbusmenu-glib/server.h> #ifdef HAVE_GTK3 #include <libdbusmenu-gtk3/client.h> @@ -41,11 +43,13 @@ License version 3 and version 2.1 along with this program. If not, see #include "app-indicator.h" #include "app-indicator-enum-types.h" +#include "application-service-marshal.h" #include "notification-item-server.h" #include "notification-watcher-client.h" #include "dbus-shared.h" +#include "generate-id.h" #define PANEL_ICON_SUFFIX "panel" @@ -73,9 +77,13 @@ struct _AppIndicatorPrivate { AppIndicatorStatus status; gchar *icon_name; gchar *attention_icon_name; - gchar * icon_path; + gchar *icon_theme_path; DbusmenuServer *menuservice; GtkWidget *menu; + guint32 ordering_index; + gchar * label; + gchar * label_guide; + guint label_change_idle; GtkStatusIcon * status_icon; gint fallback_timer; @@ -91,7 +99,10 @@ enum { NEW_ICON, NEW_ATTENTION_ICON, NEW_STATUS, + NEW_LABEL, + X_NEW_LABEL, CONNECTION_CHANGED, + NEW_ICON_THEME_PATH, LAST_SIGNAL }; @@ -108,7 +119,13 @@ enum { PROP_ATTENTION_ICON_NAME, PROP_ICON_THEME_PATH, PROP_MENU, - PROP_CONNECTED + PROP_CONNECTED, + PROP_LABEL, + PROP_LABEL_GUIDE, + PROP_X_LABEL, + PROP_X_LABEL_GUIDE, + PROP_ORDERING_INDEX, + PROP_X_ORDERING_INDEX }; /* The strings so that they can be slowly looked up. */ @@ -120,11 +137,20 @@ enum { #define PROP_ICON_THEME_PATH_S "icon-theme-path" #define PROP_MENU_S "menu" #define PROP_CONNECTED_S "connected" +#define PROP_LABEL_S "label" +#define PROP_LABEL_GUIDE_S "label-guide" +#define PROP_X_LABEL_S ("x-ayatana-" PROP_LABEL_S) +#define PROP_X_LABEL_GUIDE_S ("x-ayatana-" PROP_LABEL_GUIDE_S) +#define PROP_ORDERING_INDEX_S "ordering-index" +#define PROP_X_ORDERING_INDEX_S ("x-ayatana-" PROP_ORDERING_INDEX_S) /* Private macro, shhhh! */ #define APP_INDICATOR_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), APP_INDICATOR_TYPE, AppIndicatorPrivate)) +/* Signal wrapper */ +#define APP_INDICATOR_SIGNAL_X_NEW_LABEL ("x-ayatana-" APP_INDICATOR_SIGNAL_NEW_LABEL) + /* Default Path */ #define DEFAULT_ITEM_PATH "/org/ayatana/NotificationItem" @@ -140,6 +166,7 @@ static void app_indicator_finalize (GObject *object); static void app_indicator_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); 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 check_connect (AppIndicator * self); static void register_service_cb (DBusGProxy * proxy, GError * error, gpointer data); static void start_fallback_timer (AppIndicator * self, gboolean disable_timeout); @@ -233,7 +260,7 @@ app_indicator_class_init (AppIndicatorClass *klass) "An icon for the indicator", "The default icon that is shown for the indicator.", NULL, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); /** AppIndicator:attention-icon-name: @@ -260,7 +287,7 @@ app_indicator_class_init (AppIndicatorClass *klass) "An additional path for custom icons.", "An additional place to look for icon names that may be installed by the application.", NULL, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); /** AppIndicator:menu: @@ -288,7 +315,101 @@ app_indicator_class_init (AppIndicatorClass *klass) "Pretty simple, true if we have a reasonable expectation of being displayed through this object. You should hide your TrayIcon if so.", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:label: + + A label that can be shown next to the string in the application + indicator. The label will not be shown unless there is an icon + as well. The label is useful for numerical and other frequently + updated information. In general, it shouldn't be shown unless a + user requests it as it can take up a significant amount of space + on the user's panel. This may not be shown in all visualizations. + */ + g_object_class_install_property(object_class, + PROP_LABEL, + g_param_spec_string (PROP_LABEL_S, + "A label next to the icon", + "A label to provide dynamic information.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:label-guide: + + An optional string to provide guidance to the panel on how big + the #AppIndicator:label string could get. If this is set correctly + then the panel should never 'jiggle' as the string adjusts through + out the range of options. For instance, if you were providing a + percentage like "54% thrust" in #AppIndicator:label you'd want to + set this string to "100% thrust" to ensure space when Scotty can + get you enough power. + */ + g_object_class_install_property(object_class, + PROP_LABEL_GUIDE, + g_param_spec_string (PROP_LABEL_GUIDE_S, + "A string to size the space available for the label.", + "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:ordering-index: + + The ordering index is an odd parameter, and if you think you don't need + it you're probably right. In general, the application indicator try + to place the applications in a recreatable place taking into account + which category they're in to try and group them. But, there are some + cases where you'd want to ensure indicators are next to each other. + To do that you can override the generated ordering index and replace it + with a new one. Again, you probably don't want to be doing this, but + in case you do, this is the way. + */ + g_object_class_install_property(object_class, + PROP_ORDERING_INDEX, + g_param_spec_uint (PROP_ORDERING_INDEX_S, + "The location that this app indicator should be in the list.", + "A way to override the default ordering of the applications by providing a very specific idea of where this entry should be placed.", + 0, G_MAXUINT32, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:x-ayatana-ordering-index: + + A wrapper for #AppIndicator:ordering-index so that it can match the + dbus interface currently. It will hopefully be retired, please don't + use it anywhere. + */ + g_object_class_install_property(object_class, + PROP_X_ORDERING_INDEX, + g_param_spec_uint (PROP_X_ORDERING_INDEX_S, + "A wrapper, please don't use.", + "A wrapper, please don't use.", + 0, G_MAXUINT32, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:x-ayatana-label: + + Wrapper for #AppIndicator:label. Please use that in all of your + code. + */ + g_object_class_install_property(object_class, + PROP_X_LABEL, + g_param_spec_string (PROP_X_LABEL_S, + "A wrapper, please don't use.", + "A wrapper, please don't use.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + AppIndicator:x-ayatana-label-guide: + + Wrapper for #AppIndicator:label-guide. Please use that in all of your + code. + */ + g_object_class_install_property(object_class, + PROP_X_LABEL_GUIDE, + g_param_spec_string (PROP_X_LABEL_GUIDE_S, + "A wrapper, please don't use.", + "A wrapper, please don't use.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /* Signals */ @@ -337,6 +458,40 @@ app_indicator_class_init (AppIndicatorClass *klass) G_TYPE_STRING); /** + AppIndicator::new-label: + @arg0: The #AppIndicator object + @arg1: The string for the label + @arg1: The string for the guide + + Emitted when either #AppIndicator:label or #AppIndicator:label-guide are + changed. + */ + signals[NEW_LABEL] = g_signal_new (APP_INDICATOR_SIGNAL_NEW_LABEL, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AppIndicatorClass, new_label), + NULL, NULL, + _application_service_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + /** + AppIndicator::x-ayatana-new-label: + @arg0: The #AppIndicator object + @arg1: The string for the label + @arg1: The string for the guide + + Wrapper for #AppIndicator::new-label, please don't use this signal + use the other one. + */ + signals[X_NEW_LABEL] = g_signal_new (APP_INDICATOR_SIGNAL_X_NEW_LABEL, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AppIndicatorClass, new_label), + NULL, NULL, + _application_service_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); + + /** AppIndicator::connection-changed: @arg0: The #AppIndicator object @arg1: Whether we're connected or not @@ -351,6 +506,21 @@ app_indicator_class_init (AppIndicatorClass *klass) g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN, G_TYPE_NONE); + /** + AppIndicator::new-icon-theme-path: + @arg0: The #AppIndicator object + + Signaled when there is a new icon set for the + object. + */ + signals[NEW_ICON_THEME_PATH] = g_signal_new (APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH, + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (AppIndicatorClass, new_icon_theme_path), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + /* Initialize the object as a DBus type */ dbus_g_object_type_install_info(APP_INDICATOR_TYPE, &dbus_glib__notification_item_server_object_info); @@ -369,9 +539,13 @@ app_indicator_init (AppIndicator *self) priv->status = APP_INDICATOR_STATUS_PASSIVE; priv->icon_name = NULL; priv->attention_icon_name = NULL; - priv->icon_path = NULL; + priv->icon_theme_path = NULL; priv->menu = NULL; priv->menuservice = NULL; + priv->ordering_index = 0; + priv->label = NULL; + priv->label_guide = NULL; + priv->label_change_idle = 0; priv->watcher_proxy = NULL; priv->connection = NULL; @@ -423,6 +597,11 @@ app_indicator_dispose (GObject *object) priv->fallback_timer = 0; } + if (priv->label_change_idle != 0) { + g_source_remove(priv->label_change_idle); + priv->label_change_idle = 0; + } + if (priv->menu != NULL) { g_signal_handlers_disconnect_by_func (G_OBJECT (priv->menu), client_menu_changed, @@ -491,9 +670,19 @@ app_indicator_finalize (GObject *object) priv->attention_icon_name = NULL; } - if (priv->icon_path != NULL) { - g_free(priv->icon_path); - priv->icon_path = NULL; + if (priv->icon_theme_path != NULL) { + g_free(priv->icon_theme_path); + priv->icon_theme_path = NULL; + } + + if (priv->label != NULL) { + g_free(priv->label); + priv->label = NULL; + } + + if (priv->label_guide != NULL) { + g_free(priv->label_guide); + priv->label_guide = NULL; } G_OBJECT_CLASS (app_indicator_parent_class)->finalize (object); @@ -561,12 +750,54 @@ app_indicator_set_property (GObject * object, guint prop_id, const GValue * valu break; case PROP_ICON_THEME_PATH: - if (priv->icon_path != NULL) { - g_free(priv->icon_path); - } - priv->icon_path = g_value_dup_string(value); + app_indicator_set_icon_theme_path (APP_INDICATOR (object), + g_value_get_string (value)); + check_connect (self); break; + case PROP_X_LABEL: + case PROP_LABEL: { + gchar * oldlabel = priv->label; + priv->label = g_value_dup_string(value); + + if (g_strcmp0(oldlabel, priv->label) != 0) { + signal_label_change(APP_INDICATOR(object)); + } + + if (priv->label != NULL && priv->label[0] == '\0') { + g_free(priv->label); + priv->label = NULL; + } + + if (oldlabel != NULL) { + g_free(oldlabel); + } + break; + } + case PROP_X_LABEL_GUIDE: + case PROP_LABEL_GUIDE: { + gchar * oldguide = priv->label_guide; + priv->label_guide = g_value_dup_string(value); + + if (g_strcmp0(oldguide, priv->label_guide) != 0) { + signal_label_change(APP_INDICATOR(object)); + } + + if (priv->label_guide != NULL && priv->label_guide[0] == '\0') { + g_free(priv->label_guide); + priv->label_guide = NULL; + } + + if (oldguide != NULL) { + g_free(oldguide); + } + break; + } + case PROP_X_ORDERING_INDEX: + case PROP_ORDERING_INDEX: + priv->ordering_index = g_value_get_uint(value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -607,7 +838,7 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa break; case PROP_ICON_THEME_PATH: - g_value_set_string (value, priv->icon_path); + g_value_set_string (value, priv->icon_theme_path); break; case PROP_MENU: @@ -624,6 +855,21 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa g_value_set_boolean (value, priv->watcher_proxy != NULL ? TRUE : FALSE); break; + case PROP_X_LABEL: + case PROP_LABEL: + g_value_set_string (value, priv->label); + break; + + case PROP_X_LABEL_GUIDE: + case PROP_LABEL_GUIDE: + g_value_set_string (value, priv->label_guide); + break; + + case PROP_X_ORDERING_INDEX: + case PROP_ORDERING_INDEX: + g_value_set_uint(value, priv->ordering_index); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -632,6 +878,43 @@ app_indicator_get_property (GObject * object, guint prop_id, GValue * value, GPa return; } +/* Sends the label changed signal and resets the source ID */ +static gboolean +signal_label_change_idle (gpointer user_data) +{ + AppIndicator * self = (AppIndicator *)user_data; + AppIndicatorPrivate *priv = self->priv; + + g_signal_emit(G_OBJECT(self), signals[NEW_LABEL], 0, + priv->label != NULL ? priv->label : "", + priv->label_guide != NULL ? priv->label_guide : "", + TRUE); + g_signal_emit(G_OBJECT(self), signals[X_NEW_LABEL], 0, + priv->label != NULL ? priv->label : "", + priv->label_guide != NULL ? priv->label_guide : "", + TRUE); + + priv->label_change_idle = 0; + + return FALSE; +} + +/* Sets up an idle function to send the label changed signal + so that we don't send it too many times. */ +static void +signal_label_change (AppIndicator * self) +{ + AppIndicatorPrivate *priv = self->priv; + + /* don't set it twice */ + if (priv->label_change_idle != 0) { + return; + } + + priv->label_change_idle = g_idle_add(signal_label_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. */ @@ -775,6 +1058,23 @@ dbus_owner_change (DBusGProxy * proxy, const gchar * name, const gchar * prev, c return; } +/* Checking to see if someone already has the name we're looking for */ +static void +check_owner_cb (DBusGProxy *proxy, gboolean exists, GError *error, gpointer userdata) +{ + if (error != NULL) { + g_warning("Unable to check for '" NOTIFICATION_WATCHER_DBUS_ADDR "' on DBus. No worries, but concerning."); + return; + } + + if (exists) { + g_debug("Woah, we actually has a race condition with dbus"); + dbus_owner_change(proxy, NOTIFICATION_WATCHER_DBUS_ADDR, NULL, "Non NULL", userdata); + } + + return; +} + /* This is an idle function to create the proxy. This is mostly because start_fallback_timer can get called in the distruction of a proxy and thus the proxy manager gets confused when creating @@ -797,6 +1097,11 @@ setup_name_owner_proxy (gpointer data) G_TYPE_INVALID); dbus_g_proxy_connect_signal(priv->dbus_proxy, "NameOwnerChanged", G_CALLBACK(dbus_owner_change), data, NULL); + + /* Check to see if anyone has the name we're looking for + just incase we missed it changing. */ + + org_freedesktop_DBus_name_has_owner_async(priv->dbus_proxy, NOTIFICATION_WATCHER_DBUS_ADDR, check_owner_cb, data); } return FALSE; @@ -1029,12 +1334,12 @@ app_indicator_new (const gchar *id, @id: The unique id of the indicator to create. @icon_name: The icon name for this indicator @category: The category of indicator. - @icon_path: A custom path for finding icons. + @icon_theme_path: A custom path for finding icons. Creates a new #AppIndicator setting the properties: #AppIndicator:id with @id, #AppIndicator:category with @category, #AppIndicator:icon-name with - @icon_name and #AppIndicator:icon-theme-path with @icon_path. + @icon_name and #AppIndicator:icon-theme-path with @icon_theme_path. Return value: A pointer to a new #AppIndicator object. */ @@ -1042,13 +1347,13 @@ AppIndicator * app_indicator_new_with_path (const gchar *id, const gchar *icon_name, AppIndicatorCategory category, - const gchar *icon_path) + const gchar *icon_theme_path) { AppIndicator *indicator = g_object_new (APP_INDICATOR_TYPE, PROP_ID_S, id, PROP_CATEGORY_S, category_from_enum (category), PROP_ICON_NAME_S, icon_name, - PROP_ICON_THEME_PATH_S, icon_path, + PROP_ICON_THEME_PATH_S, icon_theme_path, NULL); return indicator; @@ -1138,6 +1443,56 @@ app_indicator_set_icon (AppIndicator *self, const gchar *icon_name) return; } +/** + app_indicator_set_label: + @self: The #AppIndicator object to use + @label: The label to show next to the icon. + @guide: A guide to size the label correctly. + + This is a wrapper function for the #AppIndicator:label and + #AppIndicator:guide properties. This function can take #NULL + as either @label or @guide and will clear the entries. +*/ +void +app_indicator_set_label (AppIndicator *self, const gchar * label, const gchar * guide) +{ + g_return_if_fail (IS_APP_INDICATOR (self)); + /* Note: The label can be NULL, it's okay */ + /* Note: The guide can be NULL, it's okay */ + + g_object_set(G_OBJECT(self), + PROP_LABEL_S, label == NULL ? "" : label, + PROP_LABEL_GUIDE_S, guide == NULL ? "" : guide, + NULL); + + return; +} + +/** + app_indicator_set_icon_theme_path: + @self: The #AppIndicator object to use + @icon_theme_path: The icon theme path to set. + + Sets the path to use when searching for icons. +**/ +void +app_indicator_set_icon_theme_path (AppIndicator *self, const gchar *icon_theme_path) +{ + g_return_if_fail (IS_APP_INDICATOR (self)); + + if (g_strcmp0 (self->priv->icon_theme_path, icon_theme_path) != 0) + { + if (self->priv->icon_theme_path != NULL) + g_free(self->priv->icon_theme_path); + + self->priv->icon_theme_path = g_strdup(icon_theme_path); + + g_signal_emit (self, signals[NEW_ICON_THEME_PATH], 0, g_strdup(self->priv->icon_theme_path)); + } + + return; +} + static void activate_menuitem (DbusmenuMenuitem *mi, guint timestamp, gpointer user_data) { @@ -1418,12 +1773,12 @@ container_iterate (GtkWidget *widget, container_iterate, child); g_signal_connect_object (submenu, - "add", + "child-added", G_CALLBACK (submenu_changed), child, 0); g_signal_connect_object (submenu, - "remove", + "child-removed", G_CALLBACK (submenu_changed), child, 0); @@ -1562,16 +1917,37 @@ app_indicator_set_menu (AppIndicator *self, GtkMenu *menu) check_connect (self); g_signal_connect (menu, - "add", + "child-added", G_CALLBACK (client_menu_changed), self); g_signal_connect (menu, - "remove", + "child-removed", G_CALLBACK (client_menu_changed), self); } /** + app_indicator_set_ordering_index: + @self: The #AppIndicator + @ordering_index: A value for the ordering of this app indicator + + Sets the ordering index for the app indicator which effects the + placement of it on the panel. For almost all app indicator + this is not the function you're looking for. + + Wrapper function for property #AppIndicator:ordering-index. +**/ +void +app_indicator_set_ordering_index (AppIndicator *self, guint32 ordering_index) +{ + g_return_if_fail (IS_APP_INDICATOR (self)); + + self->priv->ordering_index = ordering_index; + + return; +} + +/** app_indicator_get_id: @self: The #AppIndicator object to use @@ -1636,6 +2012,22 @@ app_indicator_get_icon (AppIndicator *self) } /** + app_indicator_get_icon_theme_path: + @self: The #AppIndicator object to use + + Wrapper function for property #AppIndicator:icon-theme-path. + + Return value: The current icon theme path. +*/ +const gchar * +app_indicator_get_icon_theme_path (AppIndicator *self) +{ + g_return_val_if_fail (IS_APP_INDICATOR (self), NULL); + + return self->priv->icon_theme_path; +} + +/** app_indicator_get_attention_icon: @self: The #AppIndicator object to use @@ -1671,3 +2063,56 @@ app_indicator_get_menu (AppIndicator *self) return GTK_MENU(priv->menu); } + +/** + app_indicator_get_label: + @self: The #AppIndicator object to use + + Wrapper function for property #AppIndicator:label. + + Return value: The current label. +*/ +const gchar * +app_indicator_get_label (AppIndicator *self) +{ + g_return_val_if_fail (IS_APP_INDICATOR (self), NULL); + + return self->priv->label; +} + +/** + app_indicator_get_label_guide: + @self: The #AppIndicator object to use + + Wrapper function for property #AppIndicator:label-guide. + + Return value: The current label guide. +*/ +const gchar * +app_indicator_get_label_guide (AppIndicator *self) +{ + g_return_val_if_fail (IS_APP_INDICATOR (self), NULL); + + return self->priv->label_guide; +} + +/** + app_indicator_get_ordering_index: + @self: The #AppIndicator object to use + + Wrapper function for property #AppIndicator:ordering-index. + + Return value: The current ordering index. +*/ +guint32 +app_indicator_get_ordering_index (AppIndicator *self) +{ + g_return_val_if_fail (IS_APP_INDICATOR (self), 0); + + if (self->priv->ordering_index == 0) { + return generate_id(self->priv->category, self->priv->id); + } else { + return self->priv->ordering_index; + } +} + |