diff options
-rw-r--r-- | .build.yml | 2 | ||||
-rw-r--r-- | INSTALL.md | 2 | ||||
-rw-r--r-- | src/application-service-appstore.c | 63 | ||||
-rw-r--r-- | src/ayatana-application-service.xml | 13 | ||||
-rw-r--r-- | src/indicator-application.c | 137 |
5 files changed, 194 insertions, 23 deletions
@@ -134,7 +134,7 @@ before_scripts: - - cd ${START_DIR} - if [ ! -d libayatana-appindicator-build ]; then - - git clone --depth 1 https://github.com/AyatanaIndicators/libayatana-appindicator.git libayatana-appindicator-build + - git clone --depth 1 https://github.com/AyatanaIndicators/libayatana-appindicator-glib.git libayatana-appindicator-build - fi - cd libayatana-appindicator-build - cmake . -DCMAKE_INSTALL_PREFIX=/usr @@ -7,7 +7,7 @@ - glib-2.0 (>= 2.58) - ayatana-indicator3-0.4 (>= 0.6.2) - gtk+-3.0 (>= 3.24) - - ayatana-appindicator3-0.1 (>= 0.5.5) + - ayatana-appindicator-glib - dbus-glib-1 (>=0.110) - dbusmenu-gtk3-0.4 - systemd diff --git a/src/application-service-appstore.c b/src/application-service-appstore.c index 31aa989..45acbce 100644 --- a/src/application-service-appstore.c +++ b/src/application-service-appstore.c @@ -54,6 +54,7 @@ static void props_cb (GObject * object, GAsyncResult * res, gpointer user_data); #define NOTIFICATION_ITEM_PROP_LABEL_GUIDE "XAyatanaLabelGuide" #define NOTIFICATION_ITEM_PROP_TITLE "Title" #define NOTIFICATION_ITEM_PROP_ORDERING_INDEX "XAyatanaOrderingIndex" +#define NOTIFICATION_ITEM_PROP_TOOLTIP "ToolTip" #define NOTIFICATION_ITEM_SIG_NEW_ICON "NewIcon" #define NOTIFICATION_ITEM_SIG_NEW_AICON "NewAttentionIcon" @@ -61,6 +62,7 @@ static void props_cb (GObject * object, GAsyncResult * res, gpointer user_data); #define NOTIFICATION_ITEM_SIG_NEW_LABEL "XAyatanaNewLabel" #define NOTIFICATION_ITEM_SIG_NEW_ICON_THEME_PATH "NewIconThemePath" #define NOTIFICATION_ITEM_SIG_NEW_TITLE "NewTitle" +#define NOTIFICATION_ITEM_SIG_NEW_TOOLTIP "NewToolTip" #define OVERRIDE_GROUP_NAME "Ordering Index Overrides" #define OVERRIDE_FILE_NAME "ordering-override.keyfile" @@ -108,6 +110,9 @@ struct _Application { guint ordering_index; visible_state_t visible_state; guint name_watcher; + gchar *sTooltipIcon; + gchar *sTooltipTitle; + gchar *sTooltipDescription; }; /* GDBus Stuff */ @@ -427,6 +432,7 @@ got_all_properties (GObject * source_object, GAsyncResult * res, * icon_desc = NULL, * aicon_desc = NULL, * icon_theme_path = NULL, * index = NULL, * label = NULL, * guide = NULL, * title = NULL; + GVariant *pTooltip = NULL; GVariant * properties = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, &error); @@ -482,7 +488,12 @@ got_all_properties (GObject * source_object, GAsyncResult * res, guide = g_variant_ref(value); } else if (g_strcmp0(name, NOTIFICATION_ITEM_PROP_TITLE) == 0) { title = g_variant_ref(value); - } /* else ignore */ + } + else if (g_strcmp0 (name, NOTIFICATION_ITEM_PROP_TOOLTIP) == 0) + { + pTooltip = g_variant_ref (value); + } + /* else ignore */ } g_variant_iter_free (iter); g_variant_unref(properties); @@ -576,6 +587,21 @@ got_all_properties (GObject * source_object, GAsyncResult * res, app->title = g_strdup(""); } + g_free (app->sTooltipIcon); + g_free (app->sTooltipTitle); + g_free (app->sTooltipDescription); + + if (pTooltip != NULL) + { + g_variant_get (pTooltip, "(sa(iiay)ss)", &app->sTooltipIcon, NULL, &app->sTooltipTitle, &app->sTooltipDescription); + } + else + { + app->sTooltipIcon = g_strdup (""); + app->sTooltipTitle = g_strdup (""); + app->sTooltipDescription = g_strdup (""); + } + apply_status(app); if (app->queued_props) { @@ -598,6 +624,11 @@ got_all_properties (GObject * source_object, GAsyncResult * res, if (guide) g_variant_unref (guide); if (title) g_variant_unref (title); + if (pTooltip) + { + g_variant_unref (pTooltip); + } + return; } @@ -774,6 +805,9 @@ application_free (Application * app) g_free(app->title); } + g_free (app->sTooltipIcon); + g_free (app->sTooltipTitle); + g_free (app->sTooltipDescription); g_free(app); return; } @@ -886,12 +920,12 @@ apply_status (Application * app) if (app->visible_state == VISIBLE_STATE_HIDDEN) { /* Put on panel */ emit_signal (appstore, "ApplicationAdded", - g_variant_new ("(sisossssss)", newicon, + g_variant_new ("(sisosssssssss)", newicon, get_position(app), app->dbus_name, app->menu, app->icon_theme_path, app->label, app->guide, - newdesc, app->id, app->title)); + newdesc, app->id, app->title, app->sTooltipIcon != NULL ? app->sTooltipIcon : "", app->sTooltipTitle != NULL ? app->sTooltipTitle : "", app->sTooltipDescription != NULL ? app->sTooltipDescription : "")); } else { /* Icon update */ gint position = get_position(app); @@ -906,6 +940,9 @@ apply_status (Application * app) emit_signal (appstore, "ApplicationTitleChanged", g_variant_new ("(is)", position, app->title != NULL ? app->title : "")); + + GVariant *pParams = g_variant_new ("(isss)", position, app->sTooltipIcon != NULL ? app->sTooltipIcon : "", app->sTooltipTitle != NULL ? app->sTooltipTitle : "", app->sTooltipDescription != NULL ? app->sTooltipDescription : ""); + emit_signal (appstore, "ApplicationTooltipChanged", pParams); } } @@ -1030,6 +1067,9 @@ application_service_appstore_application_add (ApplicationServiceAppstore * appst app->props_cancel = NULL; app->props = NULL; app->queued_props = FALSE; + app->sTooltipIcon = NULL; + app->sTooltipTitle = NULL; + app->sTooltipDescription = NULL; /* Get the DBus proxy for the NotificationItem interface */ app->dbus_proxy_cancel = g_cancellable_new(); @@ -1206,8 +1246,11 @@ app_receive_signal (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name g_free(label); g_free(guide); } - - return; + else if (g_strcmp0 (signal_name, NOTIFICATION_ITEM_SIG_NEW_TOOLTIP) == 0) + { + // The tooltip data isn't provided by the signal, so look it up + get_all_properties (app); + } } /* Looks for an application in the list of applications */ @@ -1312,7 +1355,7 @@ get_applications (ApplicationServiceAppstore * appstore) GList * listpntr; gint position = 0; - g_variant_builder_init(&builder, G_VARIANT_TYPE ("a(sisossssss)")); + g_variant_builder_init(&builder, G_VARIANT_TYPE ("a(sisosssssssss)")); for (listpntr = priv->applications; listpntr != NULL; listpntr = g_list_next(listpntr)) { Application * app = (Application *)listpntr->data; @@ -1320,20 +1363,20 @@ get_applications (ApplicationServiceAppstore * appstore) continue; } - g_variant_builder_add (&builder, "(sisossssss)", app->icon, + g_variant_builder_add (&builder, "(sisosssssssss)", app->icon, position++, app->dbus_name, app->menu, app->icon_theme_path, app->label, app->guide, (app->icon_desc != NULL) ? app->icon_desc : "", - app->id, app->title); + app->id, app->title, app->sTooltipIcon != NULL ? app->sTooltipIcon : "", app->sTooltipTitle != NULL ? app->sTooltipTitle : "", app->sTooltipDescription != NULL ? app->sTooltipDescription : ""); } out = g_variant_builder_end(&builder); } else { GError * error = NULL; - out = g_variant_parse(g_variant_type_new("a(sisossssss)"), "[]", NULL, NULL, &error); + out = g_variant_parse(g_variant_type_new("a(sisosssssssss)"), "[]", NULL, NULL, &error); if (error != NULL) { - g_warning("Unable to parse '[]' as a 'a(sisossssss)': %s", error->message); + g_warning("Unable to parse '[]' as a 'a(sisosssssssss)': %s", error->message); out = NULL; g_error_free(error); } diff --git a/src/ayatana-application-service.xml b/src/ayatana-application-service.xml index ef9c0d0..867b17b 100644 --- a/src/ayatana-application-service.xml +++ b/src/ayatana-application-service.xml @@ -4,10 +4,12 @@ An interface for communication between the service and indicator. Copyright 2009 Canonical Ltd. Copyright 2015 Arctica Project +Copyright 2024 Robert Tari Authors: Ted Gould <ted@canonical.com> Mike Gabriel <mike.gabriel@das-netzwerkteam.de> + Robert Tari <robert@tari.in> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published @@ -28,7 +30,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. <!-- Methods --> <method name="GetApplications"> - <arg type="a(sisossssss)" name="applications" direction="out" /> + <arg type="a(sisosssssssss)" name="applications" direction="out" /> </method> <method name="ApplicationScrollEvent"> <arg type="s" name="dbusaddress" direction="in" /> @@ -54,6 +56,9 @@ with this program. If not, see <http://www.gnu.org/licenses/>. <arg type="s" name="accessibledesc" direction="out" /> <arg type="s" name="hint" direction="out" /> <arg type="s" name="title" direction="out" /> + <arg type="s" name="tooltipicon" direction="out" /> + <arg type="s" name="tooltiptitle" direction="out" /> + <arg type="s" name="tooltipdescription" direction="out" /> </signal> <signal name="ApplicationRemoved"> <arg type="i" name="position" direction="out" /> @@ -76,5 +81,11 @@ with this program. If not, see <http://www.gnu.org/licenses/>. <arg type="i" name="position" direction="out" /> <arg type="s" name="title" direction="out" /> </signal> + <signal name="ApplicationTooltipChanged"> + <arg type="i" name="position" direction="out" /> + <arg type="s" name="icon" direction="out" /> + <arg type="s" name="title" direction="out" /> + <arg type="s" name="description" direction="out" /> + </signal> </interface> </node> diff --git a/src/indicator-application.c b/src/indicator-application.c index 1fb13b7..9cf0f31 100644 --- a/src/indicator-application.c +++ b/src/indicator-application.c @@ -4,7 +4,7 @@ given by the service and turns it into real-world pixels that users can actually use. Well, GTK does that, but this asks nicely. Copyright 2009 Canonical Ltd. -Copyright 2024 Robert Tari +Copyright 2024-2025 Robert Tari Authors: Ted Gould <ted@canonical.com> @@ -98,6 +98,9 @@ struct _ApplicationEntry { gint nPosition; GMenuModel *pModel; GActionGroup *pActions; + gboolean bMenuShown; + gchar *sTooltipIcon; + gchar *sTooltipMarkup; }; static void indicator_application_class_init (IndicatorApplicationClass *klass); @@ -113,7 +116,7 @@ static void disconnected (GDBusConnection * con, const gchar * name, gpointer us static void disconnected_helper (gpointer data, gpointer user_data); static gboolean disconnected_kill (gpointer user_data); static void disconnected_kill_helper (gpointer data, gpointer user_data); -static void application_added (IndicatorApplication * application, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_theme_path, const gchar * label, const gchar * guide, const gchar * accessible_desc, const gchar * hint); +static void application_added (IndicatorApplication * application, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_theme_path, const gchar * label, const gchar * guide, const gchar * accessible_desc, const gchar * hint, const gchar *sTooltipIcon, const gchar *sTooltipTitle, const gchar *sTooltipDescription); static void application_removed (IndicatorApplication * application, gint position); static void application_label_changed (IndicatorApplication * application, gint position, const gchar * label, const gchar * guide); static void application_icon_changed (IndicatorApplication * application, gint position, const gchar * iconname, const gchar * icondesc); @@ -492,6 +495,56 @@ guess_label_size (ApplicationEntry * app) return; } +static void onMenuPoppedUp (GtkWidget *pWidget, gpointer pFlippedRect, gpointer pFinalRect, gboolean bFlippedX, gboolean bFlippedY, gpointer pData) +{ + ApplicationEntry *pEntry = (ApplicationEntry*) pData; + pEntry->bMenuShown = TRUE; +} + +static void onMenuHide (GtkWidget *pWidget, gpointer pData) +{ + ApplicationEntry *pEntry = (ApplicationEntry*) pData; + pEntry->bMenuShown = FALSE; +} + +static gboolean onQueryTooltip (GtkWidget *pWidget, gint nX, gint nY, gboolean bKeyboardMode, GtkTooltip *pTooltip, gpointer pData) +{ + ApplicationEntry *pEntry = (ApplicationEntry*) pData; + + if (!pEntry->bMenuShown && pEntry->sTooltipMarkup) + { + gtk_tooltip_set_markup (pTooltip, pEntry->sTooltipMarkup); + + if (pEntry->sTooltipIcon) + { + gtk_tooltip_set_icon_from_icon_name (pTooltip, pEntry->sTooltipIcon, GTK_ICON_SIZE_LARGE_TOOLBAR); + } + + return TRUE; + } + + return FALSE; +} + +static gboolean onTooltipConnect (gpointer pData) +{ + ApplicationEntry *pEntry = (ApplicationEntry*) pData; + + if (pEntry->entry.label) + { + gtk_widget_set_has_tooltip (GTK_WIDGET (pEntry->entry.label), TRUE); + g_signal_connect (pEntry->entry.label, "query-tooltip", G_CALLBACK (onQueryTooltip), pEntry); + } + + if (pEntry->entry.image) + { + gtk_widget_set_has_tooltip (GTK_WIDGET (pEntry->entry.image), TRUE); + g_signal_connect (pEntry->entry.image, "query-tooltip", G_CALLBACK (onQueryTooltip), pEntry); + } + + return G_SOURCE_REMOVE; +} + static void applicationAddedFinish (ApplicationEntry *pEntry) { /* Keep copies of these for ourself, just in case. */ @@ -499,9 +552,15 @@ static void applicationAddedFinish (ApplicationEntry *pEntry) g_object_ref (pEntry->entry.menu); gtk_widget_show (GTK_WIDGET (pEntry->entry.image)); + gtk_widget_hide (GTK_WIDGET (pEntry->entry.menu)); IndicatorApplicationPrivate * pPrivate = indicator_application_get_instance_private (INDICATOR_APPLICATION (pEntry->entry.parent_object)); pPrivate->applications = g_list_insert (pPrivate->applications, pEntry, pEntry->nPosition); g_signal_emit (G_OBJECT (pEntry->entry.parent_object), INDICATOR_OBJECT_SIGNAL_ENTRY_ADDED_ID, 0, &(pEntry->entry), TRUE); + g_signal_connect (pEntry->entry.menu, "popped-up", G_CALLBACK (onMenuPoppedUp), pEntry); + g_signal_connect (pEntry->entry.menu, "hide", G_CALLBACK (onMenuHide), pEntry); + + // Make sure our widgets are constructed and displayed + g_timeout_add_seconds (2, onTooltipConnect, pEntry); } static void onMenuModelChanged (GMenuModel *pModel, gint nPosition, gint nRemoved, gint nAdded, gpointer pData) @@ -516,16 +575,43 @@ static void onMenuModelChanged (GMenuModel *pModel, gint nPosition, gint nRemove applicationAddedFinish (pEntry); } +static void setTooltip (ApplicationEntry *pEntry, const gchar *sTooltipIcon, const gchar *sTooltipTitle, const gchar *sTooltipDescription) +{ + g_free (pEntry->sTooltipIcon); + pEntry->sTooltipIcon = NULL; + g_free (pEntry->sTooltipMarkup); + pEntry->sTooltipMarkup = NULL; + + if (sTooltipTitle && sTooltipTitle[0] != '\0' && sTooltipDescription && sTooltipDescription[0] != '\0') + { + pEntry->sTooltipMarkup = g_strdup_printf ("<b>%s</b>\n\n%s", sTooltipTitle, sTooltipDescription); + } + else if (sTooltipTitle && sTooltipTitle[0] != '\0') + { + pEntry->sTooltipMarkup = g_strdup (sTooltipTitle); + } + else if (sTooltipDescription && sTooltipDescription[0] != '\0') + { + pEntry->sTooltipMarkup = g_strdup (sTooltipDescription); + } + + if (sTooltipIcon && sTooltipIcon[0] != '\0') + { + pEntry->sTooltipIcon = g_strdup (sTooltipIcon); + } +} + /* Here we respond to new applications by building up the ApplicationEntry and signaling the indicator host that we've got a new indicator. */ static void -application_added (IndicatorApplication * application, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_theme_path, const gchar * label, const gchar * guide, const gchar * accessible_desc, const gchar * hint) +application_added (IndicatorApplication * application, const gchar * iconname, gint position, const gchar * dbusaddress, const gchar * dbusobject, const gchar * icon_theme_path, const gchar * label, const gchar * guide, const gchar * accessible_desc, const gchar * hint, const gchar *sTooltipIcon, const gchar *sTooltipTitle, const gchar *sTooltipDescription) { g_return_if_fail(IS_INDICATOR_APPLICATION(application)); g_debug("Building new application entry: %s with icon: %s at position %i", dbusaddress, iconname, position); ApplicationEntry * app = g_new0(ApplicationEntry, 1); + app->bMenuShown = FALSE; app->entry.parent_object = INDICATOR_OBJECT(application); app->nPosition = position; app->old_service = FALSE; @@ -581,6 +667,7 @@ application_added (IndicatorApplication * application, const gchar * iconname, g app->entry.name_hint = g_strdup(hint); } + setTooltip (app, sTooltipIcon, sTooltipTitle, sTooltipDescription); gboolean bGLibMenu = g_str_has_prefix (dbusobject, "/org/ayatana/appindicator/"); if (bGLibMenu) @@ -650,6 +737,8 @@ application_removed (IndicatorApplication * application, gint position) g_free((gchar *)app->entry.name_hint); } + g_free (app->sTooltipIcon); + g_free (app->sTooltipMarkup); g_clear_object (&app->pModel); g_clear_object (&app->pActions); g_free(app); @@ -686,6 +775,8 @@ application_label_changed (IndicatorApplication * application, gint position, co gtk_label_set_text(app->entry.label, label); } else { app->entry.label = GTK_LABEL(gtk_label_new(label)); + gtk_widget_set_has_tooltip (GTK_WIDGET (app->entry.label), TRUE); + g_signal_connect (app->entry.label, "query-tooltip", G_CALLBACK (onQueryTooltip), app); g_object_ref(G_OBJECT(app->entry.label)); gtk_widget_show(GTK_WIDGET(app->entry.label)); @@ -849,13 +940,17 @@ receive_signal (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name, gchar * accessible_desc = NULL; gchar * hint = NULL; gchar * title = NULL; - g_variant_get (parameters, "(sisossssss)", &iconname, + gchar *sTooltipIcon = NULL; + gchar *sTooltipTitle = NULL; + gchar *sTooltipDescription = NULL; + g_variant_get (parameters, "(sisosssssssss)", &iconname, &position, &dbusaddress, &dbusobject, &icon_theme_path, &label, &guide, - &accessible_desc, &hint, &title); + &accessible_desc, &hint, &title, &sTooltipIcon, &sTooltipTitle, &sTooltipDescription); + application_added(self, iconname, position, dbusaddress, dbusobject, icon_theme_path, label, guide, - accessible_desc, hint); + accessible_desc, hint, sTooltipIcon, sTooltipTitle, sTooltipDescription); g_free(iconname); g_free(dbusaddress); g_free(dbusobject); @@ -865,6 +960,9 @@ receive_signal (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name, g_free(accessible_desc); g_free(hint); g_free(title); + g_free (sTooltipIcon); + g_free (sTooltipTitle); + g_free (sTooltipDescription); } else if (g_strcmp0(signal_name, "ApplicationRemoved") == 0) { gint position; @@ -896,6 +994,19 @@ receive_signal (GDBusProxy * proxy, gchar * sender_name, gchar * signal_name, g_free(label); g_free(guide); } + else if (g_strcmp0 (signal_name, "ApplicationTooltipChanged") == 0) + { + gint nPosition = 0; + gchar *sIcon = NULL; + gchar *sTitle = NULL; + gchar *sDescription = NULL; + g_variant_get (parameters, "(isss)", &nPosition, &sIcon, &sTitle, &sDescription); + ApplicationEntry *pEntry = (ApplicationEntry*) g_list_nth_data (priv->applications, nPosition); + setTooltip (pEntry, sIcon, sTitle, sDescription); + g_free (sIcon); + g_free (sTitle); + g_free (sDescription); + } return; } @@ -936,7 +1047,7 @@ get_applications (GObject * obj, GAsyncResult * res, gpointer user_data) } /* Get our new applications that we got in the request */ - g_variant_get(result, "(a(sisossssss))", &iter); + g_variant_get(result, "(a(sisosssssssss))", &iter); while ((child = g_variant_iter_next_value (iter))) { get_applications_helper(self, child); g_variant_unref(child); @@ -962,11 +1073,14 @@ get_applications_helper (IndicatorApplication * self, GVariant * variant) gchar * accessible_desc = NULL; gchar * hint = NULL; gchar * title = NULL; - g_variant_get(variant, "(sisossssss)", &icon_name, &position, + gchar *sTooltipIcon = NULL; + gchar *sTooltipTitle = NULL; + gchar *sTooltipDescription = NULL; + g_variant_get(variant, "(sisosssssssss)", &icon_name, &position, &dbus_address, &dbus_object, &icon_theme_path, &label, - &guide, &accessible_desc, &hint, &title); + &guide, &accessible_desc, &hint, &title, &sTooltipIcon, &sTooltipTitle, &sTooltipDescription); - application_added(self, icon_name, position, dbus_address, dbus_object, icon_theme_path, label, guide, accessible_desc, hint); + application_added(self, icon_name, position, dbus_address, dbus_object, icon_theme_path, label, guide, accessible_desc, hint, sTooltipIcon, sTooltipTitle, sTooltipDescription); g_free(icon_name); g_free(dbus_address); @@ -977,6 +1091,9 @@ get_applications_helper (IndicatorApplication * self, GVariant * variant) g_free(accessible_desc); g_free(hint); g_free(title); + g_free (sTooltipIcon); + g_free (sTooltipTitle); + g_free (sTooltipDescription); return; } |