diff options
author | Colomban Wendling <[email protected]> | 2017-01-19 18:46:10 +0100 |
---|---|---|
committer | lukefromdc <[email protected]> | 2017-01-23 13:49:34 -0500 |
commit | 7d39b2e82f46777efa67224f078c1cec9e827654 (patch) | |
tree | 7a415a153f4a3afb52358ed8a34ced08c25bdc5c /applets/notification_area/status-notifier | |
parent | a506150684ad2e71b1f70190ee70fe9eda7a4ba9 (diff) | |
download | mate-panel-7d39b2e82f46777efa67224f078c1cec9e827654.tar.bz2 mate-panel-7d39b2e82f46777efa67224f078c1cec9e827654.tar.xz |
Add StatusNotifier support to the Notification Area applet
The StatusNotifier part of the implementation is based off
gnome-panel's status-notifier applet.
Diffstat (limited to 'applets/notification_area/status-notifier')
18 files changed, 4060 insertions, 0 deletions
diff --git a/applets/notification_area/status-notifier/Makefile.am b/applets/notification_area/status-notifier/Makefile.am new file mode 100644 index 00000000..78d0a7fb --- /dev/null +++ b/applets/notification_area/status-notifier/Makefile.am @@ -0,0 +1,83 @@ +NULL = + +noinst_LTLIBRARIES = libstatus-notifier.la + +AM_CPPFLAGS = \ + $(NOTIFICATION_AREA_CFLAGS) \ + $(LIBMATE_PANEL_APPLET_CFLAGS) \ + -I$(srcdir) \ + -I$(srcdir)/.. \ + -DMATELOCALEDIR=\""$(datadir)/locale"\" \ + -DG_LOG_DOMAIN=\""notification-area-applet"\" \ + $(DISABLE_DEPRECATED_CFLAGS) + +AM_CFLAGS = $(WARN_CFLAGS) + +libstatus_notifier_la_SOURCES = \ + sn-dbus-menu.c \ + sn-dbus-menu.h \ + sn-dbus-menu-item.c \ + sn-dbus-menu-item.h \ + sn-host-v0.c \ + sn-host-v0.h \ + sn-image-menu-item.c \ + sn-image-menu-item.h \ + sn-item.c \ + sn-item.h \ + sn-item-v0.c \ + sn-item-v0.h \ + $(BUILT_SOURCES) \ + $(NULL) + +libstatus_notifier_la_LIBADD = \ + $(LIBM) \ + $(NOTIFICATION_AREA_LIBS) \ + $(NULL) + +sn-dbus-menu-gen.h: +sn-dbus-menu-gen.c: com.canonical.dbusmenu.xml + $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \ + --generate-c-code sn-dbus-menu-gen \ + $(srcdir)/com.canonical.dbusmenu.xml + +sn-host-v0-gen.h: +sn-host-v0-gen.c: org.kde.StatusNotifierHost.xml + $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \ + --generate-c-code sn-host-v0-gen \ + $(srcdir)/org.kde.StatusNotifierHost.xml + +sn-item-v0-gen.h: +sn-item-v0-gen.c: org.kde.StatusNotifierItem.xml + $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \ + --generate-c-code sn-item-v0-gen \ + $(srcdir)/org.kde.StatusNotifierItem.xml + +sn-watcher-v0-gen.h: +sn-watcher-v0-gen.c: org.kde.StatusNotifierWatcher.xml + $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Sn \ + --generate-c-code sn-watcher-v0-gen \ + $(srcdir)/org.kde.StatusNotifierWatcher.xml + +BUILT_SOURCES = \ + sn-dbus-menu-gen.c \ + sn-dbus-menu-gen.h \ + sn-host-v0-gen.c \ + sn-host-v0-gen.h \ + sn-item-v0-gen.c \ + sn-item-v0-gen.h \ + sn-watcher-v0-gen.c \ + sn-watcher-v0-gen.h \ + $(NULL) + +EXTRA_DIST = \ + com.canonical.dbusmenu.xml \ + org.kde.StatusNotifierHost.xml \ + org.kde.StatusNotifierItem.xml \ + org.kde.StatusNotifierWatcher.xml \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/applets/notification_area/status-notifier/README b/applets/notification_area/status-notifier/README new file mode 100644 index 00000000..8b89ae98 --- /dev/null +++ b/applets/notification_area/status-notifier/README @@ -0,0 +1,2 @@ +Based off gnome-panel's status-notifier applet: +https://git.gnome.org/browse/gnome-panel/tree/modules/external/status-notifier diff --git a/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml b/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml new file mode 100644 index 00000000..f8f72d10 --- /dev/null +++ b/applets/notification_area/status-notifier/com.canonical.dbusmenu.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="com.canonical.dbusmenu"> + <annotation name="org.gtk.GDBus.C.Name" value="DBusMenuGen" /> + + <method name="GetLayout"> + <arg type="i" name="parentId" direction="in" /> + <arg type="i" name="recursionDepth" direction="in" /> + <arg type="as" name="propertyNames" direction="in" /> + <arg type="u" name="revision" direction="out" /> + <arg type="(ia{sv}av)" name="layout" direction="out" /> + </method> + + <method name="GetGroupProperties"> + <arg type="ai" name="ids" direction="in" /> + <arg type="as" name="propertyNames" direction="in" /> + <arg type="a(ia{sv})" name="properties" direction="out" /> + </method> + + <method name="GetProperty"> + <arg type="i" name="id" direction="in" /> + <arg type="s" name="name" direction="in" /> + <arg type="v" name="value" direction="out" /> + </method> + + <method name="Event"> + <arg type="i" name="id" direction="in" /> + <arg type="s" name="eventId" direction="in" /> + <arg type="v" name="data" direction="in" /> + <arg type="u" name="timestamp" direction="in" /> + </method> + + <method name="EventGroup"> + <arg type="a(isvu)" name="events" direction="in" /> + <arg type="ai" name="idErrors" direction="out" /> + </method> + + <method name="AboutToShow"> + <arg type="i" name="id" direction="in" /> + <arg type="b" name="needUpdate" direction="out" /> + </method> + + <method name="AboutToShowGroup"> + <arg type="ai" name="ids" direction="in" /> + <arg type="ai" name="updatesNeeded" direction="out" /> + <arg type="ai" name="idErrors" direction="out" /> + </method> + + <property name="Version" type="u" access="read" /> + + <property name="TextDirection" type="s" access="read" /> + + <property name="Status" type="s" access="read" /> + + <property name="IconThemePath" type="as" access="read" /> + + <signal name="ItemsPropertiesUpdated"> + <arg type="a(ia{sv})" name="updatedProps" direction="out" /> + <arg type="a(ias)" name="removedProps" direction="out" /> + </signal> + + <signal name="LayoutUpdated"> + <arg type="u" name="revision" direction="out" /> + <arg type="i" name="parent" direction="out" /> + </signal> + + <signal name="ItemActivationRequested"> + <arg type="i" name="id" direction="out" /> + <arg type="u" name="timestamp" direction="out" /> + </signal> + </interface> +</node> diff --git a/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml new file mode 100644 index 00000000..3e8b5625 --- /dev/null +++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.StatusNotifierHost"> + <annotation name="org.gtk.GDBus.C.Name" value="HostV0Gen" /> + </interface> +</node> diff --git a/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml new file mode 100644 index 00000000..609d3050 --- /dev/null +++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.StatusNotifierItem"> + <annotation name="org.gtk.GDBus.C.Name" value="ItemV0Gen" /> + + <method name="ContextMenu"> + <arg name="x" type="i" direction="in" /> + <arg name="y" type="i" direction="in" /> + </method> + + <method name="Activate"> + <arg name="x" type="i" direction="in" /> + <arg name="y" type="i" direction="in" /> + </method> + + <method name="SecondaryActivate"> + <arg name="x" type="i" direction="in" /> + <arg name="y" type="i" direction="in" /> + </method> + + <method name="Scroll"> + <arg name="delta" type="i" direction="in" /> + <arg name="orientation" type="s" direction="in" /> + </method> + + <property name="Category" type="s" access="read" /> + + <property name="Id" type="s" access="read" /> + + <property name="Title" type="s" access="read" /> + + <property name="Status" type="s" access="read" /> + + <property name="WindowId" type="i" access="read" /> + + <property name="IconName" type="s" access="read"/> + + <property name="IconPixmap" type="a(iiay)" access="read" /> + + <property name="OverlayIconName" type="s" access="read"/> + + <property name="OverlayIconPixmap" type="a(iiay)" access="read" /> + + <property name="AttentionIconName" type="s" access="read"/> + + <property name="AttentionIconPixmap" type="a(iiay)" access="read" /> + + <property name="AttentionMovieName" type="s" access="read" /> + + <property name="ToolTip" type="(sa(iiay)ss)" access="read" /> + + <signal name="NewTitle" /> + + <signal name="NewIcon" /> + + <signal name="NewAttentionIcon" /> + + <signal name="NewOverlayIcon" /> + + <signal name="NewToolTip" /> + + <signal name="NewStatus"> + <arg name="status" type="s" /> + </signal> + + <!-- Properties and signals that is not part of specification --> + + <property name="IconThemePath" type="s" access="read" /> + + <property name="Menu" type="o" access="read" /> + + <property name="ItemIsMenu" type="b" access="read" /> + + <signal name="NewIconThemePath"> + <arg name="icon_theme_path" type="s" /> + </signal> + </interface> +</node> diff --git a/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml b/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml new file mode 100644 index 00000000..3b0f135a --- /dev/null +++ b/applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.StatusNotifierWatcher"> + <annotation name="org.gtk.GDBus.C.Name" value="WatcherV0Gen" /> + + <method name="RegisterStatusNotifierItem"> + <annotation name="org.gtk.GDBus.C.Name" value="RegisterItem" /> + <arg type="s" direction="in" name="service" /> + </method> + + <method name="RegisterStatusNotifierHost"> + <annotation name="org.gtk.GDBus.C.Name" value="RegisterHost" /> + <arg type="s" direction="in" name="service" /> + </method> + + <property name="RegisteredStatusNotifierItems" type="as" access="read"> + <annotation name="org.gtk.GDBus.C.Name" value="RegisteredItems" /> + </property> + + <property name="IsStatusNotifierHostRegistered" type="b" access="read"> + <annotation name="org.gtk.GDBus.C.Name" value="IsHostRegistered" /> + </property> + + <property name="ProtocolVersion" type="i" access="read" /> + + <signal name="StatusNotifierItemRegistered"> + <annotation name="org.gtk.GDBus.C.Name" value="ItemRegistered" /> + <arg type="s" direction="out" name="service" /> + </signal> + + <signal name="StatusNotifierItemUnregistered"> + <annotation name="org.gtk.GDBus.C.Name" value="ItemUnregistered" /> + <arg type="s" direction="out" name="service" /> + </signal> + + <signal name="StatusNotifierHostRegistered"> + <annotation name="org.gtk.GDBus.C.Name" value="HostRegistered" /> + </signal> + </interface> +</node> diff --git a/applets/notification_area/status-notifier/sn-dbus-menu-item.c b/applets/notification_area/status-notifier/sn-dbus-menu-item.c new file mode 100644 index 00000000..3e740b1c --- /dev/null +++ b/applets/notification_area/status-notifier/sn-dbus-menu-item.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "sn-dbus-menu-item.h" +#include "sn-image-menu-item.h" + +static GdkPixbuf * +pxibuf_new (GVariant *variant) +{ + gsize length; + const guchar *data; + GInputStream *stream; + GdkPixbuf *pixbuf; + GError *error; + + data = g_variant_get_fixed_array (variant, &length, sizeof (guchar)); + + if (length == 0) + return NULL; + + stream = g_memory_input_stream_new_from_data (data, length, NULL); + + if (stream == NULL) + return NULL; + + error = NULL; + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error); + g_object_unref (stream); + + if (error != NULL) + { + g_warning ("Unable to build GdkPixbuf from icon data: %s", error->message); + g_error_free (error); + } + + return pixbuf; +} + +static SnShortcut * +sn_shortcut_new (guint key, + GdkModifierType mask) +{ + SnShortcut *shortcut; + + shortcut = g_new0 (SnShortcut, 1); + + shortcut->key = key; + shortcut->mask = mask; + + return shortcut; +} + +static SnShortcut ** +sn_shortcuts_new (GVariant *variant) +{ + GPtrArray *array; + GVariantIter shortcuts; + GVariantIter *shortcut; + + if (variant == NULL || g_variant_iter_init (&shortcuts, variant) == 0) + return NULL; + + array = g_ptr_array_new (); + while (g_variant_iter_next (&shortcuts, "as", &shortcut)) + { + guint key; + GdkModifierType mask; + const gchar *string; + + key = 0; + mask = 0; + + while (g_variant_iter_next (shortcut, "&s", &string)) + { + if (g_strcmp0 (string, "Control") == 0) + mask |= GDK_CONTROL_MASK; + else if (g_strcmp0 (string, "Alt") == 0) + mask |= GDK_MOD1_MASK; + else if (g_strcmp0 (string, "Shift") == 0) + mask |= GDK_SHIFT_MASK; + else if (g_strcmp0 (string, "Super") == 0) + mask |= GDK_SUPER_MASK; + else + gtk_accelerator_parse (string, &key, NULL); + } + + g_ptr_array_add (array,sn_shortcut_new (key, mask)); + g_variant_iter_free (shortcut); + } + + g_ptr_array_add (array, NULL); + return (SnShortcut **) g_ptr_array_free (array, FALSE); +} + +static void +sn_shortcuts_free (SnShortcut **shortcuts) +{ + guint i; + + if (shortcuts == NULL) + return; + + for (i = 0; shortcuts[i] != NULL; i++) + g_free (shortcuts[i]); + + g_free (shortcuts); +} + +SnDBusMenuItem * +sn_dbus_menu_item_new (GVariant *props) +{ + SnDBusMenuItem *item; + GVariantIter iter; + const gchar *prop; + GVariant *value; + + item = g_new0 (SnDBusMenuItem, 1); + + item->enabled = TRUE; + item->toggle_state = -1; + item->visible = TRUE; + + g_variant_iter_init (&iter, props); + while (g_variant_iter_next (&iter, "{&sv}", &prop, &value)) + { + if (g_strcmp0 (prop, "accessible-desc") == 0) + item->accessible_desc = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "children-display") == 0) + item->children_display = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "disposition") == 0) + item->disposition = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "enabled") == 0) + item->enabled = g_variant_get_boolean (value); + else if (g_strcmp0 (prop, "icon-name") == 0) + item->icon_name = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "icon-data") == 0) + item->icon_data = pxibuf_new (value); + else if (g_strcmp0 (prop, "label") == 0) + item->label = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "shortcut") == 0) + item->shortcuts = sn_shortcuts_new (value); + else if (g_strcmp0 (prop, "toggle-type") == 0) + item->toggle_type = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "toggle-state") == 0) + item->toggle_state = g_variant_get_int32 (value); + else if (g_strcmp0 (prop, "type") == 0) + item->type = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (prop, "visible") == 0) + item->visible = g_variant_get_boolean (value); + else + g_debug ("unknown property '%s'", prop); + + g_variant_unref (value); + } + + if (g_strcmp0 (item->type, "separator") == 0) + { + item->item = gtk_separator_menu_item_new (); + } + else + { + if (g_strcmp0 (item->toggle_type, "checkmark") == 0) + { + item->item = gtk_check_menu_item_new (); + gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item->item), TRUE); + } + else if (g_strcmp0 (item->toggle_type, "radio") == 0) + { + item->item = gtk_radio_menu_item_new (NULL); + gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item->item), TRUE); + } + else + { + SnImageMenuItem *image_item; + + item->item = sn_image_menu_item_new (); + image_item = SN_IMAGE_MENU_ITEM (item->item); + + if (item->icon_name) + { + sn_image_menu_item_set_image_from_icon_name (image_item, + item->icon_name); + } + else if (item->icon_data) + { + sn_image_menu_item_set_image_from_icon_pixbuf (image_item, + item->icon_data); + } + } + + if (g_strcmp0 (item->children_display, "submenu") == 0) + { + GtkWidget *submenu; + + submenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item->item), submenu); + + item->submenu = GTK_MENU (submenu); + g_object_ref_sink (item->submenu); + } + + gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label); + + if (item->shortcuts) + { + guint i; + + for (i = 0; item->shortcuts[i] != NULL; i++) + { + } + } + + if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item)) + { + GtkCheckMenuItem *check; + + check = GTK_CHECK_MENU_ITEM (item->item); + + if (item->toggle_state == 1) + gtk_check_menu_item_set_active (check, TRUE); + else if (item->toggle_state == 0) + gtk_check_menu_item_set_active (check, FALSE); + } + } + + gtk_widget_set_sensitive (item->item, item->enabled); + gtk_widget_set_visible (item->item, item->visible); + + g_object_ref_sink (item->item); + return item; +} + +void +sn_dubs_menu_item_free (gpointer data) +{ + SnDBusMenuItem *item; + + item = (SnDBusMenuItem *) data; + if (item == NULL) + return; + + g_clear_pointer (&item->accessible_desc, g_free); + g_clear_pointer (&item->children_display, g_free); + g_clear_pointer (&item->disposition, g_free); + g_clear_pointer (&item->icon_name, g_free); + g_clear_object (&item->icon_data); + g_clear_pointer (&item->label, g_free); + g_clear_pointer (&item->shortcuts, sn_shortcuts_free); + g_clear_pointer (&item->toggle_type, g_free); + g_clear_pointer (&item->type, g_free); + + gtk_widget_destroy (item->item); + g_clear_object (&item->item); + g_clear_object (&item->submenu); + + g_free (item); +} + +void +sn_dbus_menu_item_update_props (SnDBusMenuItem *item, + GVariant *props) +{ + GVariantIter iter; + const gchar *prop; + GVariant *value; + + g_variant_iter_init (&iter, props); + while (g_variant_iter_next (&iter, "{&sv}", &prop, &value)) + { + if (g_strcmp0 (prop, "accessible-desc") == 0) + { + g_free (item->accessible_desc); + item->accessible_desc = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (prop, "children-display") == 0) + { + g_free (item->children_display); + item->children_display = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (prop, "disposition") == 0) + { + g_free (item->disposition); + item->disposition = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (prop, "enabled") == 0) + { + item->enabled = g_variant_get_boolean (value); + gtk_widget_set_sensitive (item->item, item->enabled); + } + else if (g_strcmp0 (prop, "icon-name") == 0) + { + SnImageMenuItem *image_item; + + g_free (item->icon_name); + item->icon_name = g_variant_dup_string (value, NULL); + + image_item = SN_IMAGE_MENU_ITEM (item->item); + + if (item->icon_name) + { + sn_image_menu_item_set_image_from_icon_name (image_item, + item->icon_name); + } + else + { + sn_image_menu_item_unset_image (image_item); + } + } + else if (g_strcmp0 (prop, "icon-data") == 0) + { + SnImageMenuItem *image_item; + + g_clear_object (&item->icon_data); + item->icon_data = pxibuf_new (value); + + image_item = SN_IMAGE_MENU_ITEM (item->item); + + if (item->icon_data) + { + sn_image_menu_item_set_image_from_icon_pixbuf (image_item, + item->icon_data); + } + else + { + sn_image_menu_item_unset_image (image_item); + } + } + else if (g_strcmp0 (prop, "label") == 0) + { + g_free (item->label); + item->label = g_variant_dup_string (value, NULL); + + if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item)) + gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label); + } + else if (g_strcmp0 (prop, "shortcut") == 0) + { + sn_shortcuts_free (item->shortcuts); + item->shortcuts = sn_shortcuts_new (value); + } + else if (g_strcmp0 (prop, "toggle-type") == 0) + { + g_free (item->toggle_type); + item->toggle_type = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (prop, "toggle-state") == 0) + { + item->toggle_state = g_variant_get_int32 (value); + + if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item)) + { + GtkCheckMenuItem *check; + + check = GTK_CHECK_MENU_ITEM (item->item); + + g_signal_handler_block (item->item, item->activate_id); + + if (item->toggle_state == 1) + gtk_check_menu_item_set_active (check, TRUE); + else if (item->toggle_state == 0) + gtk_check_menu_item_set_active (check, FALSE); + + g_signal_handler_unblock (item->item, item->activate_id); + } + } + else if (g_strcmp0 (prop, "type") == 0) + { + g_free (item->type); + item->type = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (prop, "visible") == 0) + { + item->visible = g_variant_get_boolean (value); + gtk_widget_set_visible (item->item, item->visible); + } + else + { + g_debug ("updating unknown property - '%s'", prop); + } + + g_variant_unref (value); + } +} + +void +sn_dbus_menu_item_remove_props (SnDBusMenuItem *item, + GVariant *props) +{ + GVariantIter iter; + const gchar *prop; + + g_variant_iter_init (&iter, props); + while (g_variant_iter_next (&iter, "&s", &prop)) + { + if (g_strcmp0 (prop, "accessible-desc") == 0) + { + g_clear_pointer (&item->accessible_desc, g_free); + } + else if (g_strcmp0 (prop, "children-display") == 0) + { + g_clear_pointer (&item->children_display, g_free); + } + else if (g_strcmp0 (prop, "disposition") == 0) + { + g_clear_pointer (&item->disposition, g_free); + } + else if (g_strcmp0 (prop, "enabled") == 0) + { + item->enabled = TRUE; + gtk_widget_set_sensitive (item->item, item->enabled); + } + else if (g_strcmp0 (prop, "icon-name") == 0) + { + g_clear_pointer (&item->icon_name, g_free); + if (SN_IS_IMAGE_MENU_ITEM (item->item)) + sn_image_menu_item_unset_image (SN_IMAGE_MENU_ITEM (item->item)); + } + else if (g_strcmp0 (prop, "icon-data") == 0) + { + g_clear_object (&item->icon_data); + if (SN_IS_IMAGE_MENU_ITEM (item->item)) + sn_image_menu_item_unset_image (SN_IMAGE_MENU_ITEM (item->item)); + } + else if (g_strcmp0 (prop, "label") == 0) + { + g_clear_pointer (&item->label, g_free); + if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item)) + gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label); + } + else if (g_strcmp0 (prop, "shortcut") == 0) + { + g_clear_pointer (&item->shortcuts, sn_shortcuts_free); + } + else if (g_strcmp0 (prop, "toggle-type") == 0) + { + g_clear_pointer (&item->toggle_type, g_free); + } + else if (g_strcmp0 (prop, "toggle-state") == 0) + { + item->toggle_state = -1; + } + else if (g_strcmp0 (prop, "type") == 0) + { + g_clear_pointer (&item->type, g_free); + } + else if (g_strcmp0 (prop, "visible") == 0) + { + item->visible = TRUE; + gtk_widget_set_visible (item->item, item->visible); + } + else + { + g_debug ("removing unknown property - '%s'", prop); + } + } +} diff --git a/applets/notification_area/status-notifier/sn-dbus-menu-item.h b/applets/notification_area/status-notifier/sn-dbus-menu-item.h new file mode 100644 index 00000000..c89de5c3 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-dbus-menu-item.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_DBUS_MENU_ITEM_H +#define SN_DUBS_MENU_ITEM_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +typedef struct +{ + guint key; + GdkModifierType mask; +} SnShortcut; + +typedef struct +{ + gchar *accessible_desc; + gchar *children_display; + gchar *disposition; + gboolean enabled; + gchar *icon_name; + GdkPixbuf *icon_data; + gchar *label; + SnShortcut **shortcuts; + gchar *toggle_type; + gint32 toggle_state; + gchar *type; + gboolean visible; + + GtkWidget *item; + GtkMenu *submenu; + + gulong activate_id; +} SnDBusMenuItem; + +SnDBusMenuItem *sn_dbus_menu_item_new (GVariant *props); + +void sn_dubs_menu_item_free (gpointer data); + +void sn_dbus_menu_item_update_props (SnDBusMenuItem *item, + GVariant *props); + +void sn_dbus_menu_item_remove_props (SnDBusMenuItem *item, + GVariant *props); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/status-notifier/sn-dbus-menu.c b/applets/notification_area/status-notifier/sn-dbus-menu.c new file mode 100644 index 00000000..9cbab345 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-dbus-menu.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "sn-dbus-menu.h" +#include "sn-dbus-menu-gen.h" +#include "sn-dbus-menu-item.h" + +struct _SnDBusMenu +{ + GtkMenu parent; + + GHashTable *items; + + GCancellable *cancellable; + + gchar *bus_name; + gchar *object_path; + guint name_id; + + SnDBusMenuGen *proxy; +}; + +enum +{ + PROP_0, + + PROP_BUS_NAME, + PROP_OBJECT_PATH, + + LAST_PROP +}; + +static GParamSpec *properties[LAST_PROP] = { NULL }; + +static const gchar *property_names[] = +{ + "accessible-desc", + "children-display", + "disposition", + "enabled", + "icon-data", + "icon-name", + "label", + "shortcut", + "toggle-type", + "toggle-state", + "type", + "visible", + NULL +}; + +G_DEFINE_TYPE (SnDBusMenu, sn_dbus_menu, GTK_TYPE_MENU) + +static void +activate_cb (GtkWidget *widget, + SnDBusMenu *menu) +{ + guint id; + + if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)) != NULL) + return; + + id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "item-id")); + sn_dbus_menu_gen_call_event_sync (menu->proxy, id, "clicked", + g_variant_new ("v", g_variant_new_int32 (0)), + gtk_get_current_event_time (), NULL, NULL); +} + +static GtkMenu * +layout_update_item (SnDBusMenu *menu, + GtkMenu *gtk_menu, + guint id, + GVariant *props) +{ + SnDBusMenuItem *item; + + if (id == 0) + return gtk_menu; + + item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id)); + + if (item == NULL) + { + item = sn_dbus_menu_item_new (props); + + g_object_set_data (G_OBJECT (item->item), "item-id", GUINT_TO_POINTER (id)); + gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), item->item); + + item->activate_id = g_signal_connect (item->item, "activate", + G_CALLBACK (activate_cb), menu); + + g_hash_table_replace (menu->items, GUINT_TO_POINTER (id), item); + } + else + { + sn_dbus_menu_item_update_props (item, props); + } + + return item->submenu; +} + +static void +layout_parse (SnDBusMenu *menu, + GVariant *layout, + GtkMenu *gtk_menu) +{ + guint id; + GVariant *props; + GVariant *items; + GtkMenu *submenu; + GVariantIter iter; + GVariant *child; + + g_variant_get (layout, "(i@a{sv}@av)", &id, &props, &items); + + submenu = layout_update_item (menu, gtk_menu, id, props); + g_variant_unref (props); + + g_variant_iter_init (&iter, items); + while ((child = g_variant_iter_next_value (&iter))) + { + GVariant *value; + + value = g_variant_get_variant (child); + + layout_parse (menu, value, submenu); + g_variant_unref (value); + + g_variant_unref (child); + } + + g_variant_unref (items); +} + +static void +get_layout_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GVariant *layout; + guint revision; + GError *error; + SnDBusMenu *menu; + + error = NULL; + sn_dbus_menu_gen_call_get_layout_finish (SN_DBUS_MENU_GEN (source_object), + &revision, &layout, res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + menu = SN_DBUS_MENU (user_data); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + g_hash_table_remove_all (menu->items); + layout_parse (menu, layout, GTK_MENU (menu)); + g_variant_unref (layout); +} + +static void +update_layout (SnDBusMenu *menu, + gint parent) +{ + gint depth; + + parent = 0; + depth = -1; + + sn_dbus_menu_gen_call_get_layout (menu->proxy, parent, depth, + property_names, menu->cancellable, + get_layout_cb, menu); +} + +static void +items_properties_updated_cb (SnDBusMenuGen *proxy, + GVariant *updated_props, + GVariant *removed_props, + SnDBusMenu *menu) +{ + GVariantIter iter; + guint id; + GVariant *props; + SnDBusMenuItem *item; + + g_variant_iter_init (&iter, updated_props); + while (g_variant_iter_next (&iter, "(i@a{sv})", &id, &props)) + { + item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id)); + + if (item != NULL) + sn_dbus_menu_item_update_props (item, props); + + g_variant_unref (props); + } + + g_variant_iter_init (&iter, removed_props); + while (g_variant_iter_next (&iter, "(i@as)", &id, &props)) + { + item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id)); + + if (item != NULL) + sn_dbus_menu_item_remove_props (item, props); + + g_variant_unref (props); + } +} + +static void +layout_updated_cb (SnDBusMenuGen *proxy, + guint revision, + gint parent, + SnDBusMenu *menu) +{ + update_layout (menu, parent); +} + +static void +item_activation_requested_cb (SnDBusMenuGen *proxy, + gint id, + guint timestamp, + SnDBusMenu *menu) +{ + g_debug ("activation requested: id - %d, timestamp - %d", id, timestamp); +} + +static void +map_cb (GtkWidget *widget, + SnDBusMenu *menu) +{ + gboolean need_update; + + sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "opened", + g_variant_new ("v", g_variant_new_int32 (0)), + gtk_get_current_event_time (), + NULL, NULL); + + sn_dbus_menu_gen_call_about_to_show_sync (menu->proxy, 0, &need_update, + NULL, NULL); + + if (need_update) + { + update_layout (menu, 0); + } +} + +static void +unmap_cb (GtkWidget *widget, + SnDBusMenu *menu) +{ + sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "closed", + g_variant_new ("v", g_variant_new_int32 (0)), + gtk_get_current_event_time (), + NULL, NULL); +} + +static void +proxy_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnDBusMenuGen *proxy; + GError *error; + SnDBusMenu *menu; + + error = NULL; + proxy = sn_dbus_menu_gen_proxy_new_finish (res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + menu = SN_DBUS_MENU (user_data); + menu->proxy = proxy; + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (proxy, "items-properties-updated", + G_CALLBACK (items_properties_updated_cb), menu); + + g_signal_connect (proxy, "layout-updated", + G_CALLBACK (layout_updated_cb), menu); + + g_signal_connect (proxy, "item-activation-requested", + G_CALLBACK (item_activation_requested_cb), menu); + + g_signal_connect (menu, "map", G_CALLBACK (map_cb), menu); + g_signal_connect (menu, "unmap", G_CALLBACK (unmap_cb), menu); + + update_layout (menu, 0); +} + +static void +name_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + SnDBusMenu *menu; + + menu = SN_DBUS_MENU (user_data); + + sn_dbus_menu_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, + menu->bus_name, menu->object_path, + menu->cancellable, proxy_ready_cb, menu); +} + +static void +name_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + SnDBusMenu *menu; + + menu = SN_DBUS_MENU (user_data); + + g_clear_object (&menu->proxy); +} + +static void +sn_dbus_menu_constructed (GObject *object) +{ + SnDBusMenu *menu; + + G_OBJECT_CLASS (sn_dbus_menu_parent_class)->constructed (object); + menu = SN_DBUS_MENU (object); + + menu->name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, menu->bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_cb, name_vanished_cb, + menu, NULL); +} + +static void +sn_dbus_menu_dispose (GObject *object) +{ + SnDBusMenu *menu; + + menu = SN_DBUS_MENU (object); + + if (menu->name_id > 0) + { + g_bus_unwatch_name (menu->name_id); + menu->name_id = 0; + } + + g_clear_pointer (&menu->items, g_hash_table_destroy); + + g_cancellable_cancel (menu->cancellable); + g_clear_object (&menu->cancellable); + g_clear_object (&menu->proxy); + + G_OBJECT_CLASS (sn_dbus_menu_parent_class)->dispose (object); +} + +static void +sn_dbus_menu_finalize (GObject *object) +{ + SnDBusMenu *menu; + + menu = SN_DBUS_MENU (object); + + g_free (menu->bus_name); + g_free (menu->object_path); + + G_OBJECT_CLASS (sn_dbus_menu_parent_class)->finalize (object); +} + +static void +sn_dbus_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + SnDBusMenu *menu; + + menu = SN_DBUS_MENU (object); + + switch (property_id) + { + case PROP_BUS_NAME: + menu->bus_name = g_value_dup_string (value); + break; + + case PROP_OBJECT_PATH: + menu->object_path = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +install_properties (GObjectClass *object_class) +{ + properties[PROP_BUS_NAME] = + g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_OBJECT_PATH] = + g_param_spec_string ("object-path", "object-path", "object-path", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); +} + +static void +sn_dbus_menu_class_init (SnDBusMenuClass *menu_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (menu_class); + + object_class->constructed = sn_dbus_menu_constructed; + object_class->dispose = sn_dbus_menu_dispose; + object_class->finalize = sn_dbus_menu_finalize; + object_class->set_property = sn_dbus_menu_set_property; + + install_properties (object_class); +} + +static void +sn_dbus_menu_init (SnDBusMenu *menu) +{ + menu->items = g_hash_table_new_full (NULL, NULL, NULL, sn_dubs_menu_item_free); + menu->cancellable = g_cancellable_new (); +} + +GtkMenu * +sn_dbus_menu_new (const gchar *bus_name, + const gchar *object_path) +{ + return g_object_new (SN_TYPE_DBUS_MENU, + "bus-name", bus_name, + "object-path", object_path, + NULL); +} diff --git a/applets/notification_area/status-notifier/sn-dbus-menu.h b/applets/notification_area/status-notifier/sn-dbus-menu.h new file mode 100644 index 00000000..39907d07 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-dbus-menu.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_DBUS_MENU_H +#define SN_DBUS_MENU_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define SN_TYPE_DBUS_MENU sn_dbus_menu_get_type () +G_DECLARE_FINAL_TYPE (SnDBusMenu, sn_dbus_menu, SN, DBUS_MENU, GtkMenu) + +GtkMenu *sn_dbus_menu_new (const gchar *bus_name, + const gchar *object_path); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/status-notifier/sn-host-v0.c b/applets/notification_area/status-notifier/sn-host-v0.c new file mode 100644 index 00000000..eeab0254 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-host-v0.c @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "sn-host-v0.h" +#include "sn-item-v0.h" +#include "sn-watcher-v0-gen.h" + +#define SN_HOST_BUS_NAME "org.kde.StatusNotifierHost" +#define SN_HOST_OBJECT_PATH "/StatusNotifierHost" +#define SN_ITEM_OBJECT_PATH "/StatusNotifierItem" +#define SN_WATCHER_BUS_NAME "org.kde.StatusNotifierWatcher" +#define SN_WATCHER_OBJECT_PATH "/StatusNotifierWatcher" + +struct _SnHostV0 +{ + SnHostV0GenSkeleton parent; + + gchar *bus_name; + gchar *object_path; + guint bus_name_id; + + GCancellable *cancellable; + + guint watcher_id; + SnWatcherV0Gen *watcher; + + GSList *items; + + gint icon_padding; + gint icon_size; +}; + +enum +{ + PROP_0, + + PROP_ICON_PADDING, + PROP_ICON_SIZE, + + LAST_PROP +}; + +static void sn_host_v0_gen_init (SnHostV0GenIface *iface); +static void na_host_init (NaHostInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (SnHostV0, sn_host_v0, SN_TYPE_HOST_V0_GEN_SKELETON, + G_IMPLEMENT_INTERFACE (SN_TYPE_HOST_V0_GEN, sn_host_v0_gen_init) + G_IMPLEMENT_INTERFACE (NA_TYPE_HOST, na_host_init)) + +static void +sn_host_v0_gen_init (SnHostV0GenIface *iface) +{ +} + +static void +na_host_init (NaHostInterface *iface) +{ +} + +static void +get_bus_name_and_object_path (const gchar *service, + gchar **bus_name, + gchar **object_path) +{ + gchar *tmp; + + g_assert (*bus_name == NULL); + g_assert (*object_path == NULL); + + tmp = g_strstr_len (service, -1, "/"); + if (tmp != NULL) + { + gchar **strings; + + strings = g_strsplit (service, "/", 2); + + *bus_name = g_strdup (strings[0]); + *object_path = g_strdup (tmp); + + g_strfreev (strings); + } + else + { + *bus_name = g_strdup (service); + *object_path = g_strdup (SN_ITEM_OBJECT_PATH); + } +} + +static void +ready_cb (SnItem *item, + SnHostV0 *v0) +{ + na_host_emit_item_added (NA_HOST (v0), NA_ITEM (item)); +} + +static void +add_registered_item (SnHostV0 *v0, + const gchar *service) +{ + gchar *bus_name; + gchar *object_path; + SnItem *item; + + bus_name = NULL; + object_path = NULL; + + get_bus_name_and_object_path (service, &bus_name, &object_path); + + item = sn_item_v0_new (bus_name, object_path); + g_object_ref_sink (item); + + g_object_bind_property (v0, "icon-padding", item, "margin", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + g_object_bind_property (v0, "icon-size", item, "icon-size", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + v0->items = g_slist_prepend (v0->items, item); + g_signal_connect (item, "ready", G_CALLBACK (ready_cb), v0); + + g_free (bus_name); + g_free (object_path); +} + +static void +item_registered_cb (SnWatcherV0Gen *watcher, + const gchar *service, + SnHostV0 *v0) +{ + add_registered_item (v0, service); +} + +static void +item_unregistered_cb (SnWatcherV0Gen *watcher, + const gchar *service, + SnHostV0 *v0) +{ + GSList *l; + + for (l = v0->items; l != NULL; l = g_slist_next (l)) + { + SnItem *item; + gboolean found; + gchar *bus_name; + gchar *object_path; + + item = SN_ITEM (l->data); + + found = FALSE; + bus_name = NULL; + object_path = NULL; + + get_bus_name_and_object_path (service, &bus_name, &object_path); + + if (g_strcmp0 (sn_item_get_bus_name (item), bus_name) == 0 && + g_strcmp0 (sn_item_get_object_path (item), object_path) == 0) + { + v0->items = g_slist_remove (v0->items, item); + na_host_emit_item_removed (NA_HOST (v0), NA_ITEM (item)); + g_object_unref (item); + + found = TRUE; + } + + g_free (bus_name); + g_free (object_path); + + if (found) + break; + } +} + +static void +register_host_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + SnHostV0 *v0; + gchar **items; + gint i; + + error = NULL; + sn_watcher_v0_gen_call_register_host_finish (SN_WATCHER_V0_GEN (source_object), + res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + v0 = SN_HOST_V0 (user_data); + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + + return; + } + + g_signal_connect (v0->watcher, "item-registered", + G_CALLBACK (item_registered_cb), v0); + + g_signal_connect (v0->watcher, "item-unregistered", + G_CALLBACK (item_unregistered_cb), v0); + + items = sn_watcher_v0_gen_dup_registered_items (v0->watcher); + + for (i = 0; items[i] != NULL; i++) + add_registered_item (v0, items[i]); + + g_strfreev (items); +} + +static void +proxy_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + SnWatcherV0Gen *watcher; + SnHostV0 *v0; + + error = NULL; + watcher = sn_watcher_v0_gen_proxy_new_finish (res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + v0 = SN_HOST_V0 (user_data); + v0->watcher = watcher; + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + + return; + } + + sn_watcher_v0_gen_call_register_host (v0->watcher, v0->object_path, + v0->cancellable, register_host_cb, v0); +} + +static void +name_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (user_data); + + g_assert (v0->cancellable == NULL); + v0->cancellable = g_cancellable_new (); + + sn_watcher_v0_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, + SN_WATCHER_BUS_NAME, SN_WATCHER_OBJECT_PATH, + v0->cancellable, proxy_ready_cb, user_data); +} + +static void +emit_item_removed_signal (gpointer data, + gpointer user_data) +{ + na_host_emit_item_removed (NA_HOST (user_data), NA_ITEM (data)); +} + +static void +name_vanished_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (user_data); + + g_cancellable_cancel (v0->cancellable); + g_clear_object (&v0->cancellable); + + g_clear_object (&v0->watcher); + + if (v0->items) + { + g_slist_foreach (v0->items, emit_item_removed_signal, v0); + g_slist_free_full (v0->items, g_object_unref); + v0->items = NULL; + } +} + +static void +bus_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + SnHostV0 *v0; + GDBusInterfaceSkeleton *skeleton; + GError *error; + + v0 = SN_HOST_V0 (user_data); + skeleton = G_DBUS_INTERFACE_SKELETON (v0); + + error = NULL; + g_dbus_interface_skeleton_export (skeleton, connection, + v0->object_path, &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + + return; + } + + v0->watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION, SN_WATCHER_BUS_NAME, + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_cb, name_vanished_cb, + v0, NULL); +} + +static void +sn_host_v0_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (object); + + switch (property_id) + { + case PROP_ICON_PADDING: + g_value_set_int (value, v0->icon_padding); + break; + + case PROP_ICON_SIZE: + g_value_set_int (value, v0->icon_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +sn_host_v0_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (object); + + switch (property_id) + { + case PROP_ICON_PADDING: + v0->icon_padding = g_value_get_int (value); + break; + + case PROP_ICON_SIZE: + v0->icon_size = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +install_properties (GObjectClass *object_class) +{ + /* NaHost properties */ + g_object_class_override_property (object_class, PROP_ICON_PADDING, "icon-padding"); + g_object_class_override_property (object_class, PROP_ICON_SIZE, "icon-size"); +} + +static void +sn_host_v0_dispose (GObject *object) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (object); + + if (v0->bus_name_id > 0) + { + g_bus_unown_name (v0->bus_name_id); + v0->bus_name_id = 0; + } + + if (v0->watcher_id > 0) + { + g_bus_unwatch_name (v0->watcher_id); + v0->watcher_id = 0; + } + + g_cancellable_cancel (v0->cancellable); + g_clear_object (&v0->cancellable); + + g_clear_object (&v0->watcher); + + if (v0->items) + { + g_slist_foreach (v0->items, emit_item_removed_signal, v0); + g_slist_free_full (v0->items, g_object_unref); + v0->items = NULL; + } + + G_OBJECT_CLASS (sn_host_v0_parent_class)->dispose (object); +} + +static void +sn_host_v0_finalize (GObject *object) +{ + SnHostV0 *v0; + + v0 = SN_HOST_V0 (object); + + g_clear_pointer (&v0->bus_name, g_free); + g_clear_pointer (&v0->object_path, g_free); + + G_OBJECT_CLASS (sn_host_v0_parent_class)->finalize (object); +} + +static void +sn_host_v0_class_init (SnHostV0Class *v0_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (v0_class); + + object_class->dispose = sn_host_v0_dispose; + object_class->finalize = sn_host_v0_finalize; + object_class->get_property = sn_host_v0_get_property; + object_class->set_property = sn_host_v0_set_property; + + install_properties (object_class); +} + +static void +sn_host_v0_init (SnHostV0 *v0) +{ + GBusNameOwnerFlags flags; + static guint id; + + flags = G_BUS_NAME_OWNER_FLAGS_NONE; + id++; + + v0->bus_name = g_strdup_printf ("%s-%d-%d", SN_HOST_BUS_NAME, getpid (), id); + v0->object_path = g_strdup_printf ("%s/%d", SN_HOST_OBJECT_PATH,id); + + v0->bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION, v0->bus_name, flags, + bus_acquired_cb, NULL, NULL, v0, NULL); + + v0->icon_size = 16; + v0->icon_padding = 0; +} + +NaHost * +sn_host_v0_new (void) +{ + return g_object_new (SN_TYPE_HOST_V0, NULL); +} diff --git a/applets/notification_area/status-notifier/sn-host-v0.h b/applets/notification_area/status-notifier/sn-host-v0.h new file mode 100644 index 00000000..61c8b5c3 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-host-v0.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_HOST_V0_H +#define SN_HOST_V0_H + +#include "na-host.h" +#include "sn-host-v0-gen.h" + +G_BEGIN_DECLS + +#define SN_TYPE_HOST_V0 sn_host_v0_get_type () +G_DECLARE_FINAL_TYPE (SnHostV0, sn_host_v0, SN, HOST_V0, SnHostV0GenSkeleton) + +NaHost *sn_host_v0_new (void); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/status-notifier/sn-image-menu-item.c b/applets/notification_area/status-notifier/sn-image-menu-item.c new file mode 100644 index 00000000..2246b961 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-image-menu-item.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "sn-image-menu-item.h" + +#define SPACING 6 + +struct _SnImageMenuItem +{ + GtkMenuItem parent; + + GtkWidget *box; + GtkWidget *image; + GtkWidget *accel_label; + + gchar *label; +}; + +G_DEFINE_TYPE (SnImageMenuItem, sn_image_menu_item, GTK_TYPE_MENU_ITEM) + +static void +sn_image_menu_item_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GtkWidgetClass *widget_class; + SnImageMenuItem *item; + GtkRequisition image_requisition; + + widget_class = GTK_WIDGET_CLASS (sn_image_menu_item_parent_class); + item = SN_IMAGE_MENU_ITEM (widget); + + widget_class->get_preferred_width (widget, minimum, natural); + + if (!gtk_widget_get_visible (item->image)) + return; + + gtk_widget_get_preferred_size (item->image, &image_requisition, NULL); + + if (image_requisition.width > 0) + { + *minimum -= image_requisition.width + SPACING; + *natural -= image_requisition.width + SPACING; + } +} + +static void +sn_image_menu_item_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWidgetClass *widget_class; + SnImageMenuItem *item; + GtkRequisition image_requisition; + GtkAllocation box_allocation; + + widget_class = GTK_WIDGET_CLASS (sn_image_menu_item_parent_class); + item = SN_IMAGE_MENU_ITEM (widget); + + widget_class->size_allocate (widget, allocation); + + if (!gtk_widget_get_visible (item->image)) + return; + + gtk_widget_get_preferred_size (item->image, &image_requisition, NULL); + gtk_widget_get_allocation (item->box, &box_allocation); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + { + if (image_requisition.width > 0) + box_allocation.x -= image_requisition.width + SPACING; + } + else + { + if (image_requisition.width > 0) + box_allocation.x += image_requisition.width + SPACING; + } + + gtk_widget_size_allocate (item->box, &box_allocation); +} + +static const gchar * +sn_image_menu_item_get_label (GtkMenuItem *menu_item) +{ + SnImageMenuItem *item; + + item = SN_IMAGE_MENU_ITEM (menu_item); + + return item->label; +} + +static void +sn_image_menu_item_toggle_size_request (GtkMenuItem *menu_item, + gint *requisition) +{ + SnImageMenuItem *item; + GtkRequisition image_requisition; + + item = SN_IMAGE_MENU_ITEM (menu_item); + + *requisition = 0; + + if (!gtk_widget_get_visible (item->image)) + return; + + gtk_widget_get_preferred_size (item->image, &image_requisition, NULL); + + if (image_requisition.width > 0) + *requisition = image_requisition.width + SPACING; +} + +static void +sn_image_menu_item_set_label (GtkMenuItem *menu_item, + const gchar *label) +{ + SnImageMenuItem *item; + + item = SN_IMAGE_MENU_ITEM (menu_item); + + if (g_strcmp0 (item->label, label) != 0) + { + g_free (item->label); + item->label = g_strdup (label); + + gtk_label_set_text_with_mnemonic (GTK_LABEL (item->accel_label), label); + g_object_notify (G_OBJECT (menu_item), "label"); + } +} + +static void +sn_image_menu_item_class_init (SnImageMenuItemClass *item_class) +{ + GtkWidgetClass *widget_class; + GtkMenuItemClass *menu_item_class; + + widget_class = GTK_WIDGET_CLASS (item_class); + menu_item_class = GTK_MENU_ITEM_CLASS (item_class); + + widget_class->get_preferred_width = sn_image_menu_item_get_preferred_width; + widget_class->size_allocate = sn_image_menu_item_size_allocate; + + menu_item_class->get_label = sn_image_menu_item_get_label; + menu_item_class->toggle_size_request = sn_image_menu_item_toggle_size_request; + menu_item_class->set_label = sn_image_menu_item_set_label; +} + +static void +sn_image_menu_item_init (SnImageMenuItem *item) +{ + GtkAccelLabel *accel_label; + + item->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING); + gtk_container_add (GTK_CONTAINER (item), item->box); + gtk_widget_show (item->box); + + item->image = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (item->box), item->image, FALSE, FALSE, 0); + + item->accel_label = gtk_accel_label_new (""); + gtk_box_pack_end (GTK_BOX (item->box), item->accel_label, TRUE, TRUE, 0); + gtk_label_set_xalign (GTK_LABEL (item->accel_label), 0.0); + gtk_widget_show (item->accel_label); + + accel_label = GTK_ACCEL_LABEL (item->accel_label); + gtk_accel_label_set_accel_widget (accel_label, GTK_WIDGET (item)); + gtk_label_set_use_underline (GTK_LABEL (accel_label), TRUE); +} + +GtkWidget * +sn_image_menu_item_new (void) +{ + return g_object_new (SN_TYPE_IMAGE_MENU_ITEM, NULL); +} + +void +sn_image_menu_item_set_image_from_icon_name (SnImageMenuItem *item, + const gchar *icon_name) +{ + GtkImage *image; + + image = GTK_IMAGE (item->image); + + gtk_image_set_from_icon_name (image, icon_name, GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (image, 16); + gtk_widget_show (item->image); +} + +void +sn_image_menu_item_set_image_from_icon_pixbuf (SnImageMenuItem *item, + GdkPixbuf *pixbuf) +{ + gtk_image_set_from_pixbuf (GTK_IMAGE (item->image), pixbuf); + gtk_widget_show (item->image); +} + +void +sn_image_menu_item_unset_image (SnImageMenuItem *item) +{ + gtk_image_clear (GTK_IMAGE (item->image)); + gtk_widget_hide (item->image); +} diff --git a/applets/notification_area/status-notifier/sn-image-menu-item.h b/applets/notification_area/status-notifier/sn-image-menu-item.h new file mode 100644 index 00000000..b0fe8a90 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-image-menu-item.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_IMAGE_MENU_ITEM_H +#define SN_IMAGE_MENU_ITEM_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define SN_TYPE_IMAGE_MENU_ITEM sn_image_menu_item_get_type () +G_DECLARE_FINAL_TYPE (SnImageMenuItem, sn_image_menu_item, + SN, IMAGE_MENU_ITEM, GtkMenuItem) + +GtkWidget *sn_image_menu_item_new (void); + +void sn_image_menu_item_set_image_from_icon_name (SnImageMenuItem *item, + const gchar *icon_name); + +void sn_image_menu_item_set_image_from_icon_pixbuf (SnImageMenuItem *item, + GdkPixbuf *pixbuf); + +void sn_image_menu_item_unset_image (SnImageMenuItem *item); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/status-notifier/sn-item-v0.c b/applets/notification_area/status-notifier/sn-item-v0.c new file mode 100644 index 00000000..eb5a95c2 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-item-v0.c @@ -0,0 +1,1355 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * Copyright (C) 2017 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <math.h> + +#include "sn-item.h" +#include "sn-item-v0.h" +#include "sn-item-v0-gen.h" + +#define SN_ITEM_INTERFACE "org.kde.StatusNotifierItem" + +typedef struct +{ + cairo_surface_t *surface; + gint width; + gint height; +} SnIconPixmap; + +typedef struct +{ + gchar *icon_name; + SnIconPixmap **icon_pixmap; + gchar *title; + gchar *text; +} SnTooltip; + +struct _SnItemV0 +{ + SnItem parent; + + GtkWidget *image; + gint icon_size; + gint effective_icon_size; + + GCancellable *cancellable; + SnItemV0Gen *proxy; + + gchar *id; + gchar *category; + gchar *status; + + gchar *title; + gint32 window_id; + gchar *icon_name; + SnIconPixmap **icon_pixmap; + gchar *overlay_icon_name; + SnIconPixmap **overlay_icon_pixmap; + gchar *attention_icon_name; + SnIconPixmap **attention_icon_pixmap; + gchar *attention_movie_name; + SnTooltip *tooltip; + gchar *icon_theme_path; + gchar *menu; + gboolean item_is_menu; + + guint update_id; +}; + +enum +{ + PROP_0, + + PROP_ICON_SIZE, + + LAST_PROP +}; + +static GParamSpec *properties[LAST_PROP] = { NULL }; + +G_DEFINE_TYPE (SnItemV0, sn_item_v0, SN_TYPE_ITEM) + +static cairo_surface_t * +scale_surface (SnIconPixmap *pixmap, + GtkOrientation orientation, + gint size) +{ + gdouble ratio; + gdouble new_width; + gdouble new_height; + gdouble scale_x; + gdouble scale_y; + gint width; + gint height; + cairo_content_t content; + cairo_surface_t *scaled; + cairo_t *cr; + + ratio = pixmap->width / (gdouble) pixmap->height; + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + new_height = (gdouble) size; + new_width = new_height * ratio; + } + else + { + new_width = (gdouble) size; + new_height = new_width * ratio; + } + + scale_x = new_width / pixmap->width; + scale_y = new_height / pixmap->height; + + width = ceil (new_width); + height = ceil (new_height); + + content = CAIRO_CONTENT_COLOR_ALPHA; + scaled = cairo_surface_create_similar (pixmap->surface, content, width, height); + cr = cairo_create (scaled); + + cairo_scale (cr, scale_x, scale_y); + cairo_set_source_surface (cr, pixmap->surface, 0, 0); + cairo_paint (cr); + + cairo_destroy (cr); + return scaled; +} + +static gint +compare_size (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SnIconPixmap *p1; + SnIconPixmap *p2; + GtkOrientation orientation; + + p1 = (SnIconPixmap *) a; + p2 = (SnIconPixmap *) b; + orientation = GPOINTER_TO_UINT (user_data); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + return p1->height - p2->height; + else + return p1->width - p2->width; +} + +static GList * +get_pixmaps_sorted (SnItemV0 *v0, + GtkOrientation orientation, + gint size) +{ + GList *pixmaps; + guint i; + + pixmaps = NULL; + for (i = 0; v0->icon_pixmap[i] != NULL; i++) + pixmaps = g_list_prepend (pixmaps, v0->icon_pixmap[i]); + + return g_list_sort_with_data (pixmaps, compare_size, + GUINT_TO_POINTER (orientation)); +} + +static cairo_surface_t * +get_surface (SnItemV0 *v0, + GtkOrientation orientation, + gint size) +{ + GList *pixmaps; + cairo_surface_t *surface; + SnIconPixmap *best; + GList *l; + + g_assert (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL); + + pixmaps = get_pixmaps_sorted (v0, orientation, size); + surface = NULL; + + for (l = pixmaps; l != NULL; l = l->next) + { + SnIconPixmap *pixmap; + + pixmap = (SnIconPixmap *) l->data; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (pixmap->height == size) + { + surface = pixmap->surface; + break; + } + else if (pixmap->height > size) + { + best = pixmap; + break; + } + } + else + { + if (pixmap->width == size) + { + surface = pixmap->surface; + break; + } + else if (pixmap->width > size) + { + best = pixmap; + break; + } + } + + best = pixmap; + } + + g_list_free (pixmaps); + + if (surface != NULL) + return cairo_surface_reference (surface); + + return scale_surface (best, orientation, size); +} + +static void +update (SnItemV0 *v0) +{ + AtkObject *accessible; + GtkImage *image; + SnTooltip *tip; + gboolean visible; + gint icon_size; + + g_return_if_fail (SN_IS_ITEM_V0 (v0)); + + image = GTK_IMAGE (v0->image); + + if (v0->icon_size > 0) + icon_size = v0->icon_size; + else + icon_size = MAX (1, v0->effective_icon_size); + + if (v0->icon_name != NULL && *v0->icon_name != '\0') + { + GtkIconTheme *icon_theme; + + icon_theme = gtk_icon_theme_get_default (); + + gtk_icon_theme_rescan_if_needed (icon_theme); + gtk_image_set_from_icon_name (image, v0->icon_name, GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (image, icon_size); + } + else if (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL) + { + cairo_surface_t *surface; + + surface = get_surface (v0, + gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)), + icon_size); + gtk_image_set_from_surface (image, surface); + cairo_surface_destroy (surface); + } + else + { + gtk_image_set_from_icon_name (image, "image-missing", GTK_ICON_SIZE_MENU); + gtk_image_set_pixel_size (image, icon_size); + } + + tip = v0->tooltip; + + if (tip != NULL) + { + gchar *markup; + + markup = NULL; + + if ((tip->title != NULL && *tip->title != '\0') && + (tip->text != NULL && *tip->text != '\0')) + { + markup = g_strdup_printf ("%s\n%s", tip->title, tip->text); + } + else if (tip->title != NULL && *tip->title != '\0') + { + markup = g_strdup (tip->title); + } + else if (tip->text != NULL && *tip->text != '\0') + { + markup = g_strdup (tip->text); + } + + gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), markup); + g_free (markup); + } + else + { + gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), NULL); + } + + accessible = gtk_widget_get_accessible (GTK_WIDGET (v0)); + + if (v0->title != NULL && *v0->title != '\0') + atk_object_set_name (accessible, v0->title); + else + atk_object_set_name (accessible, v0->id); + + visible = g_strcmp0 (v0->status, "Passive") != 0; + gtk_widget_set_visible (GTK_WIDGET (v0), visible); +} + +static gboolean +update_cb (gpointer user_data) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (user_data); + + v0->update_id = 0; + update (v0); + + return G_SOURCE_REMOVE; +} + +static void +queue_update (SnItemV0 *v0) +{ + if (v0->update_id != 0) + return; + + v0->update_id = g_timeout_add (10, update_cb, v0); + g_source_set_name_by_id (v0->update_id, "[status-notifier] update_cb"); +} + +static cairo_surface_t * +surface_from_variant (GVariant *variant, + gint width, + gint height) +{ + cairo_format_t format; + gint stride; + guint32 *data; + + format = CAIRO_FORMAT_ARGB32; + stride = cairo_format_stride_for_width (format, width); + data = (guint32 *) g_variant_get_data (variant); + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + { + gint i; + + for (i = 0; i < width * height; i++) + data[i] = GUINT32_FROM_BE (data[i]); + } +#endif + + return cairo_image_surface_create_for_data ((guchar *) data, format, + width, height, stride); +} + +static cairo_surface_t * +icon_surface_new (GVariant *variant, + gint width, + gint height) +{ + cairo_surface_t *surface; + cairo_surface_t *tmp; + cairo_t *cr; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + return NULL; + + tmp = surface_from_variant (variant, width, height); + if (cairo_surface_status (tmp) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + cr = cairo_create (surface); + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + cairo_surface_destroy (tmp); + return NULL; + } + + cairo_set_source_surface (cr, tmp, 0, 0); + cairo_paint (cr); + + cairo_surface_destroy (tmp); + cairo_destroy (cr); + + return surface; +} + +static SnIconPixmap ** +icon_pixmap_new (GVariant *variant) +{ + GPtrArray *array; + GVariantIter iter; + gint width; + gint height; + GVariant *value; + + if (variant == NULL || g_variant_iter_init (&iter, variant) == 0) + return NULL; + + array = g_ptr_array_new (); + while (g_variant_iter_next (&iter, "(ii@ay)", &width, &height, &value)) + { + cairo_surface_t *surface; + + if (width == 0 || height == 0) + { + g_variant_unref (value); + continue; + } + + surface = icon_surface_new (value, width, height); + g_variant_unref (value); + + if (surface != NULL) + { + SnIconPixmap *pixmap; + + pixmap = g_new0 (SnIconPixmap, 1); + + pixmap->surface = surface; + pixmap->width = width; + pixmap->height = height; + + g_ptr_array_add (array, pixmap); + } + } + + g_ptr_array_add (array, NULL); + return (SnIconPixmap **) g_ptr_array_free (array, FALSE); +} + +static void +icon_pixmap_free (SnIconPixmap **data) +{ + gint i; + + if (data == NULL) + return; + + for (i = 0; data[i] != NULL; i++) + { + cairo_surface_destroy (data[i]->surface); + g_free (data[i]); + } + + g_free (data); +} + +static SnTooltip * +sn_tooltip_new (GVariant *variant) +{ + const gchar *icon_name; + GVariant *icon_pixmap; + const gchar *title; + const gchar *text; + SnTooltip *tooltip; + + if (variant == NULL) + return NULL; + + g_variant_get (variant, "(&s@a(iiay)&s&s)", + &icon_name, &icon_pixmap, + &title, &text); + + tooltip = g_new0 (SnTooltip, 1); + + tooltip->icon_name = g_strdup (icon_name); + tooltip->icon_pixmap = icon_pixmap_new (icon_pixmap); + tooltip->title = g_strdup (title); + tooltip->text = g_strdup (text); + + g_variant_unref (icon_pixmap); + return tooltip; +} + +static void +sn_tooltip_free (SnTooltip *tooltip) +{ + if (tooltip == NULL) + return; + + g_free (tooltip->icon_name); + icon_pixmap_free (tooltip->icon_pixmap); + g_free (tooltip->title); + g_free (tooltip->text); + + g_free (tooltip); +} + +static GVariant * +get_property (GObject *source_object, + GAsyncResult *res, + gpointer user_data, + gboolean *cancelled) +{ + GVariant *variant; + GError *error; + GVariant *property; + + error = NULL; + variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, &error); + + *cancelled = FALSE; + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + *cancelled = TRUE; + g_error_free (error); + return NULL; + } + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + return NULL; + } + + g_variant_get (variant, "(v)", &property); + g_variant_unref (variant); + + return property; +} + +static void +update_property (SnItemV0 *v0, + const gchar *property, + GAsyncReadyCallback callback) +{ + GDBusProxy *proxy; + SnItem *item; + + proxy = G_DBUS_PROXY (v0->proxy); + item = SN_ITEM (v0); + + g_dbus_connection_call (g_dbus_proxy_get_connection (proxy), + sn_item_get_bus_name (item), + sn_item_get_object_path (item), + "org.freedesktop.DBus.Properties", "Get", + g_variant_new ("(ss)", SN_ITEM_INTERFACE, property), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, -1, + v0->cancellable, callback, v0); +} + +static void +update_title (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->title, g_free); + v0->title = g_variant_dup_string (variant, NULL); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +new_title_cb (SnItemV0 *v0) +{ + update_property (v0, "Title", update_title); +} + +static void +update_icon_name (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->icon_name, g_free); + v0->icon_name = g_variant_dup_string (variant, NULL); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +update_icon_pixmap (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free); + v0->icon_pixmap = icon_pixmap_new (variant); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +new_icon_cb (SnItemV0 *v0) +{ + update_property (v0, "IconName", update_icon_name); + update_property (v0, "IconPixmap", update_icon_pixmap); +} + +static void +update_overlay_icon_name (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->overlay_icon_name, g_free); + v0->overlay_icon_name = g_variant_dup_string (variant, NULL); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +update_overlay_icon_pixmap (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free); + v0->overlay_icon_pixmap = icon_pixmap_new (variant); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +new_overlay_icon_cb (SnItemV0 *v0) +{ + update_property (v0, "OverlayIconName", update_overlay_icon_name); + update_property (v0, "OverlayIconPixmap", update_overlay_icon_pixmap); +} + +static void +update_attention_icon_name (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->attention_icon_name, g_free); + v0->attention_icon_name = g_variant_dup_string (variant, NULL); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +update_attention_icon_pixmap (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free); + v0->attention_icon_pixmap = icon_pixmap_new (variant); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +new_attention_icon_cb (SnItemV0 *v0) +{ + update_property (v0, "AttentionIconName", update_attention_icon_name); + update_property (v0, "AttentionIconPixmap", update_attention_icon_pixmap); +} + +static void +update_tooltip (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *variant; + gboolean cancelled; + + variant = get_property (source_object, res, user_data, &cancelled); + if (cancelled) + return; + + v0 = SN_ITEM_V0 (user_data); + + g_clear_pointer (&v0->tooltip, sn_tooltip_free); + v0->tooltip = sn_tooltip_new (variant); + g_clear_pointer (&variant, g_variant_unref); + + queue_update (v0); +} + +static void +new_tooltip_cb (SnItemV0 *v0) +{ + update_property (v0, "ToolTip", update_tooltip); +} + +static void +new_status_cb (SnItemV0 *v0, + GVariant *parameters) +{ + GVariant *variant; + + variant = g_variant_get_child_value (parameters, 0); + + g_free (v0->status); + v0->status = g_variant_dup_string (variant, NULL); + g_variant_unref (variant); + + queue_update (v0); +} + +static void +new_icon_theme_path_cb (SnItemV0 *v0, + GVariant *parameters) +{ + GVariant *variant; + + variant = g_variant_get_child_value (parameters, 0); + + g_free (v0->icon_theme_path); + v0->icon_theme_path = g_variant_dup_string (variant, NULL); + g_variant_unref (variant); + + if (v0->icon_theme_path != NULL) + { + GtkIconTheme *icon_theme; + + icon_theme = gtk_icon_theme_get_default (); + + gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path); + } + + queue_update (v0); +} + +static void +g_properties_changed_cb (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + SnItemV0 *v0) +{ + gchar *debug; + + debug = g_variant_print (changed_properties, FALSE); + g_debug ("g_properties_changed_cb: %s", debug); + g_free (debug); +} + +static void +g_signal_cb (GDBusProxy *proxy, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + SnItemV0 *v0) +{ + if (g_strcmp0 (signal_name, "NewTitle") == 0) + new_title_cb (v0); + else if (g_strcmp0 (signal_name, "NewIcon") == 0) + new_icon_cb (v0); + else if (g_strcmp0 (signal_name, "NewOverlayIcon") == 0) + new_overlay_icon_cb (v0); + else if (g_strcmp0 (signal_name, "NewAttentionIcon") == 0) + new_attention_icon_cb (v0); + else if (g_strcmp0 (signal_name, "NewToolTip") == 0) + new_tooltip_cb (v0); + else if (g_strcmp0 (signal_name, "NewStatus") == 0) + new_status_cb (v0, parameters); + else if (g_strcmp0 (signal_name, "NewIconThemePath") == 0) + new_icon_theme_path_cb (v0, parameters); + else + g_assert_not_reached (); +} + +static void +get_all_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + GVariant *properties; + GError *error; + GVariantIter *iter; + gchar *key; + GVariant *value; + + error = NULL; + properties = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + v0 = SN_ITEM_V0 (user_data); + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + g_variant_get (properties, "(a{sv})", &iter); + while (g_variant_iter_next (iter, "{sv}", &key, &value)) + { + if (g_strcmp0 (key, "Category") == 0) + v0->category = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "Id") == 0) + v0->id = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "Title") == 0) + v0->title = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "Status") == 0) + v0->status = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "WindowId") == 0) + v0->window_id = g_variant_get_int32 (value); + else if (g_strcmp0 (key, "IconName") == 0) + v0->icon_name = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "IconPixmap") == 0) + v0->icon_pixmap = icon_pixmap_new (value); + else if (g_strcmp0 (key, "OverlayIconName") == 0) + v0->overlay_icon_name = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "OverlayIconPixmap") == 0) + v0->overlay_icon_pixmap = icon_pixmap_new (value); + else if (g_strcmp0 (key, "AttentionIconName") == 0) + v0->attention_icon_name = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "AttentionIconPixmap") == 0) + v0->attention_icon_pixmap = icon_pixmap_new (value); + else if (g_strcmp0 (key, "AttentionMovieName") == 0) + v0->attention_movie_name = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "ToolTip") == 0) + v0->tooltip = sn_tooltip_new (value); + else if (g_strcmp0 (key, "IconThemePath") == 0) + v0->icon_theme_path = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "Menu") == 0) + v0->menu = g_variant_dup_string (value, NULL); + else if (g_strcmp0 (key, "ItemIsMenu") == 0) + v0->item_is_menu = g_variant_get_boolean (value); + else + g_debug ("property '%s' not handled!", key); + + g_variant_unref (value); + } + + g_variant_iter_free (iter); + g_variant_unref (properties); + + if (v0->id == NULL || v0->category == NULL || v0->status == NULL) + { + SnItem *item; + const gchar *bus_name; + const gchar *object_path; + + item = SN_ITEM (v0); + bus_name = sn_item_get_bus_name (item); + object_path = sn_item_get_object_path (item); + + g_warning ("Invalid Status Notifier Item (%s, %s)", + bus_name, object_path); + + return; + } + + if (v0->icon_theme_path != NULL) + { + GtkIconTheme *icon_theme; + + icon_theme = gtk_icon_theme_get_default (); + + gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path); + } + + g_signal_connect (v0->proxy, "g-properties-changed", + G_CALLBACK (g_properties_changed_cb), v0); + + g_signal_connect (v0->proxy, "g-signal", + G_CALLBACK (g_signal_cb), v0); + + update (v0); + sn_item_emit_ready (SN_ITEM (v0)); +} + +static void +proxy_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + SnItemV0Gen *proxy; + GError *error; + + error = NULL; + proxy = sn_item_v0_gen_proxy_new_for_bus_finish (res, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + v0 = SN_ITEM_V0 (user_data); + v0->proxy = proxy; + + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + return; + } + + g_dbus_connection_call (g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy)), + sn_item_get_bus_name (SN_ITEM (v0)), + sn_item_get_object_path (SN_ITEM (v0)), + "org.freedesktop.DBus.Properties", "GetAll", + g_variant_new ("(s)", SN_ITEM_INTERFACE), + G_VARIANT_TYPE ("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, -1, + v0->cancellable, get_all_cb, v0); +} + +static void +sn_item_v0_constructed (GObject *object) +{ + SnItemV0 *v0; + SnItem *item; + + v0 = SN_ITEM_V0 (object); + item = SN_ITEM (v0); + + G_OBJECT_CLASS (sn_item_v0_parent_class)->constructed (object); + + v0->cancellable = g_cancellable_new (); + sn_item_v0_gen_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + sn_item_get_bus_name (item), + sn_item_get_object_path (item), + v0->cancellable, + proxy_ready_cb, v0); +} + +static void +sn_item_v0_dispose (GObject *object) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (object); + + g_cancellable_cancel (v0->cancellable); + g_clear_object (&v0->cancellable); + g_clear_object (&v0->proxy); + + if (v0->update_id != 0) + { + g_source_remove (v0->update_id); + v0->update_id = 0; + } + + G_OBJECT_CLASS (sn_item_v0_parent_class)->dispose (object); +} + +static void +sn_item_v0_finalize (GObject *object) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (object); + + g_clear_pointer (&v0->id, g_free); + g_clear_pointer (&v0->category, g_free); + g_clear_pointer (&v0->status, g_free); + + g_clear_pointer (&v0->title, g_free); + g_clear_pointer (&v0->icon_name, g_free); + g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free); + g_clear_pointer (&v0->overlay_icon_name, g_free); + g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free); + g_clear_pointer (&v0->attention_icon_name, g_free); + g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free); + g_clear_pointer (&v0->attention_movie_name, g_free); + g_clear_pointer (&v0->tooltip, sn_tooltip_free); + g_clear_pointer (&v0->icon_theme_path, g_free); + g_clear_pointer (&v0->menu, g_free); + + G_OBJECT_CLASS (sn_item_v0_parent_class)->finalize (object); +} + +static const gchar * +sn_item_v0_get_id (SnItem *item) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + return v0->id; +} + +static const gchar * +sn_item_v0_get_category (SnItem *item) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + return v0->category; +} + +static const gchar * +sn_item_v0_get_menu (SnItem *item) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + return v0->menu; +} + +static void +context_menu_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (user_data); + + sn_item_v0_gen_call_context_menu_finish (v0->proxy, res, NULL); +} + +static void +sn_item_v0_context_menu (SnItem *item, + gint x, + gint y) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + sn_item_v0_gen_call_context_menu (v0->proxy, x, y, NULL, + context_menu_cb, v0); +} + +static void +activate_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (user_data); + + sn_item_v0_gen_call_activate_finish (v0->proxy, res, NULL); +} + +static void +sn_item_v0_activate (SnItem *item, + gint x, + gint y) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + sn_item_v0_gen_call_activate (v0->proxy, x, y, NULL, + activate_cb, v0); +} + +static void +secondary_activate_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (user_data); + + sn_item_v0_gen_call_secondary_activate_finish (v0->proxy, res, NULL); +} + +static void +sn_item_v0_secondary_activate (SnItem *item, + gint x, + gint y) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (item); + + sn_item_v0_gen_call_secondary_activate (v0->proxy, x, y, NULL, + secondary_activate_cb, v0); +} + +static void +scroll_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (user_data); + + sn_item_v0_gen_call_scroll_finish (v0->proxy, res, NULL); +} + +static void +sn_item_v0_scroll (SnItem *item, + gint delta, + SnItemOrientation orientation) +{ + SnItemV0 *v0; + const gchar *tmp; + + v0 = SN_ITEM_V0 (item); + + switch (orientation) + { + case SN_ITEM_ORIENTATION_VERTICAL: + tmp = "Vertical"; + break; + + case SN_ITEM_ORIENTATION_HORIZONTAL: + default: + tmp = "Horizontal"; + break; + } + + sn_item_v0_gen_call_scroll (v0->proxy, delta, tmp, NULL, scroll_cb, v0); +} + +static void +sn_item_v0_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + SnItemV0 *v0 = SN_ITEM_V0 (widget); + + GTK_WIDGET_CLASS (sn_item_v0_parent_class)->size_allocate (widget, allocation); + + /* FIXME: this leads to grow-only size, unless there's underallocation. + * not a problem in the panel, but one in the test app. + * NOTE: actually, it *can* shrink, as we request a little less for padding + * around, but that only allows shrinking by steps of 2 */ + if (v0->icon_size <= 0) + { + gint prev_effective_icon_size = v0->effective_icon_size; + + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)) == GTK_ORIENTATION_HORIZONTAL) + v0->effective_icon_size = allocation->height; + else + v0->effective_icon_size = allocation->width; + + /* leave some padding around */ + if (v0->effective_icon_size > 2) + v0->effective_icon_size -= 2; + + if (v0->effective_icon_size != prev_effective_icon_size) + queue_update (SN_ITEM_V0 (widget)); + } +} + +static void +sn_item_v0_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (object); + + switch (property_id) + { + case PROP_ICON_SIZE: + g_value_set_uint (value, v0->icon_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +sn_item_v0_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + SnItemV0 *v0; + + v0 = SN_ITEM_V0 (object); + + switch (property_id) + { + case PROP_ICON_SIZE: + sn_item_v0_set_icon_size (v0, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +install_properties (GObjectClass *object_class) +{ + properties[PROP_ICON_SIZE] = + g_param_spec_int ("icon-size", "Icon size", "Icon size", 0, G_MAXINT, 16, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, properties); +} + +static void +sn_item_v0_class_init (SnItemV0Class *v0_class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + SnItemClass *item_class; + + object_class = G_OBJECT_CLASS (v0_class); + widget_class = GTK_WIDGET_CLASS (v0_class); + item_class = SN_ITEM_CLASS (v0_class); + + object_class->constructed = sn_item_v0_constructed; + object_class->dispose = sn_item_v0_dispose; + object_class->finalize = sn_item_v0_finalize; + object_class->get_property = sn_item_v0_get_property; + object_class->set_property = sn_item_v0_set_property; + + item_class->get_id = sn_item_v0_get_id; + item_class->get_category = sn_item_v0_get_category; + item_class->get_menu = sn_item_v0_get_menu; + + item_class->context_menu = sn_item_v0_context_menu; + item_class->activate = sn_item_v0_activate; + item_class->secondary_activate = sn_item_v0_secondary_activate; + item_class->scroll = sn_item_v0_scroll; + + widget_class->size_allocate = sn_item_v0_size_allocate; + + gtk_widget_class_set_css_name (widget_class, "sn-item"); + + install_properties (object_class); +} + +static void +sn_item_v0_init (SnItemV0 *v0) +{ + v0->icon_size = 16; + v0->effective_icon_size = 0; + v0->image = gtk_image_new (); + gtk_container_add (GTK_CONTAINER (v0), v0->image); + gtk_widget_show (v0->image); +} + +SnItem * +sn_item_v0_new (const gchar *bus_name, + const gchar *object_path) +{ + return g_object_new (SN_TYPE_ITEM_V0, + "bus-name", bus_name, + "object-path", object_path, + NULL); +} + +gint +sn_item_v0_get_icon_size (SnItemV0 *v0) +{ + return v0->icon_size; +} + +void +sn_item_v0_set_icon_size (SnItemV0 *v0, + gint size) +{ + if (v0->icon_size != size) + { + v0->icon_size = size; + g_object_notify_by_pspec (G_OBJECT (v0), properties[PROP_ICON_SIZE]); + + if (v0->id != NULL) + queue_update (v0); + } +} diff --git a/applets/notification_area/status-notifier/sn-item-v0.h b/applets/notification_area/status-notifier/sn-item-v0.h new file mode 100644 index 00000000..4b1bd492 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-item-v0.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_ITEM_V0_H +#define SN_ITEM_V0_H + +#include "sn-item.h" + +G_BEGIN_DECLS + +#define SN_TYPE_ITEM_V0 sn_item_v0_get_type () +G_DECLARE_FINAL_TYPE (SnItemV0, sn_item_v0, SN, ITEM_V0, SnItem) + +SnItem *sn_item_v0_new (const gchar *bus_name, + const gchar *object_path); + +gint sn_item_v0_get_icon_size (SnItemV0 *v0); +void sn_item_v0_set_icon_size (SnItemV0 *v0, + gint size); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/status-notifier/sn-item.c b/applets/notification_area/status-notifier/sn-item.c new file mode 100644 index 00000000..13ee68f3 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-item.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "sn-dbus-menu.h" +#include "sn-item.h" + +#include "na-item.h" + +typedef struct +{ + gchar *bus_name; + gchar *object_path; + + GtkOrientation orientation; + + GtkMenu *menu; +} SnItemPrivate; + +enum +{ + PROP_0, + + PROP_BUS_NAME, + PROP_OBJECT_PATH, + + PROP_ORIENTATION, + + LAST_PROP +}; + +enum +{ + SIGNAL_READY, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void na_item_init (NaItemInterface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SnItem, sn_item, GTK_TYPE_BUTTON, + //~ G_ADD_PRIVATE (SnItem); + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, + NULL) + G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM, + na_item_init)) + +/* FIXME: apparently if requesting < 2.38, G_ADD_PRIVATE() or the default + * sn_item_get_instance_private() does NOT work, and returns some garbage + * (either get_instance_private() works but G_ADD_PRIVATE() didn't do nothing, + * or the other way around, but it leads to using incorrect memory) */ +#define sn_item_get_instance_private(i) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((i), SN_TYPE_ITEM, SnItemPrivate)) + +static void +sn_item_dispose (GObject *object) +{ + SnItem *item; + SnItemPrivate *priv; + + item = SN_ITEM (object); + priv = sn_item_get_instance_private (item); + + g_clear_object (&priv->menu); + + G_OBJECT_CLASS (sn_item_parent_class)->dispose (object); +} + +static void +sn_item_finalize (GObject *object) +{ + SnItem *item; + SnItemPrivate *priv; + + item = SN_ITEM (object); + priv = sn_item_get_instance_private (item); + + g_clear_pointer (&priv->bus_name, g_free); + g_clear_pointer (&priv->object_path, g_free); + + G_OBJECT_CLASS (sn_item_parent_class)->finalize (object); +} + +static void +sn_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + SnItem *item; + SnItemPrivate *priv; + + item = SN_ITEM (object); + priv = sn_item_get_instance_private (item); + + switch (property_id) + { + case PROP_BUS_NAME: + g_value_set_string (value, priv->bus_name); + break; + + case PROP_OBJECT_PATH: + g_value_set_string (value, priv->object_path); + break; + + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +sn_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + SnItem *item; + SnItemPrivate *priv; + + item = SN_ITEM (object); + priv = sn_item_get_instance_private (item); + + switch (property_id) + { + case PROP_BUS_NAME: + priv->bus_name = g_value_dup_string (value); + break; + + case PROP_OBJECT_PATH: + priv->object_path = g_value_dup_string (value); + break; + + case PROP_ORIENTATION: + priv->orientation = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +sn_item_get_action_coordinates (SnItem *item, + gint *x, + gint *y) +{ + GtkWidget *widget; + SnItemPrivate *priv; + GdkWindow *window; + GtkWidget *toplevel; + gint width; + gint height; + + priv = sn_item_get_instance_private (item); + widget = GTK_WIDGET (item); + window = gtk_widget_get_window (widget); + toplevel = gtk_widget_get_toplevel (widget); + + gdk_window_get_geometry (window, x, y, &width, &height); + gtk_widget_translate_coordinates (widget, toplevel, *x, *y, x, y); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + *y += height; + else + *x += width; +} + +static gboolean +sn_item_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + SnItem *item; + SnItemPrivate *priv; + GdkDisplay *display; + GdkSeat *seat; + gint x; + gint y; + + if (event->button < 2 || event->button > 3) + return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event); + + item = SN_ITEM (widget); + priv = sn_item_get_instance_private (item); + display = gdk_display_get_default (); + seat = gdk_display_get_default_seat (display); + + sn_item_get_action_coordinates (item, &x, &y); + + if (event->button == 2) + { + gdk_seat_ungrab (seat); + SN_ITEM_GET_CLASS (item)->secondary_activate (item, x, y); + } + else if (event->button == 3) + { + if (priv->menu != NULL) + { + gtk_menu_popup_at_widget (priv->menu, widget, + GDK_GRAVITY_SOUTH_WEST, + GDK_GRAVITY_NORTH_WEST, + (GdkEvent *) event); + } + else + { + gdk_seat_ungrab (seat); + SN_ITEM_GET_CLASS (item)->context_menu (item, x, y); + } + } + else + { + g_assert_not_reached (); + } + + return GTK_WIDGET_CLASS (sn_item_parent_class)->button_press_event (widget, event); +} + +static gboolean +sn_item_popup_menu (GtkWidget *widget) +{ + SnItem *item; + SnItemPrivate *priv; + + item = SN_ITEM (widget); + priv = sn_item_get_instance_private (item); + + if (priv->menu != NULL) + { + gtk_menu_popup_at_widget (priv->menu, widget, + GDK_GRAVITY_SOUTH_WEST, + GDK_GRAVITY_NORTH_WEST, + NULL); + } + else + { + gint x; + gint y; + + sn_item_get_action_coordinates (item, &x, &y); + + SN_ITEM_GET_CLASS (item)->context_menu (item, x, y); + } + + return TRUE; +} + +static void +sn_item_clicked (GtkButton *button) +{ + SnItem *item; + gint x; + gint y; + + item = SN_ITEM (button); + + sn_item_get_action_coordinates (item, &x, &y); + + SN_ITEM_GET_CLASS (item)->activate (item, x, y); +} + +static gboolean +sn_item_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + SnItem *item; + GdkScrollDirection direction; + SnItemOrientation orientation; + gdouble dx; + gdouble dy; + gint delta; + + item = SN_ITEM (widget); + + if (!gdk_event_get_scroll_direction ((GdkEvent *) event, &direction)) + { + g_assert_not_reached (); + } + else + { + switch (direction) + { + case GDK_SCROLL_UP: + case GDK_SCROLL_DOWN: + orientation = SN_ITEM_ORIENTATION_VERTICAL; + break; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_RIGHT: + orientation = SN_ITEM_ORIENTATION_HORIZONTAL; + break; + + case GDK_SCROLL_SMOOTH: + default: + g_assert_not_reached (); + break; + } + } + + if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy)) + { + switch (direction) + { + case GDK_SCROLL_UP: + case GDK_SCROLL_LEFT: + delta = 1; + break; + + case GDK_SCROLL_DOWN: + case GDK_SCROLL_RIGHT: + delta = -1; + break; + + case GDK_SCROLL_SMOOTH: + default: + g_assert_not_reached (); + break; + } + } + else + { + if (dy != 0) + delta = (gint) dy; + else + delta = (gint) dx; + } + + SN_ITEM_GET_CLASS (item)->scroll (item, delta, orientation); + + return GDK_EVENT_STOP; +} + +static void +sn_item_ready (SnItem *item) +{ + const gchar *menu; + SnItemPrivate *priv; + + menu = SN_ITEM_GET_CLASS (item)->get_menu (item); + if (menu == NULL) + return; + + if (menu == NULL || *menu == '\0' || g_strcmp0 (menu, "/") == 0) + return; + + priv = sn_item_get_instance_private (item); + priv->menu = sn_dbus_menu_new (priv->bus_name, menu); + g_object_ref_sink (priv->menu); +} + +static const gchar * +sn_item_get_id (NaItem *item) +{ + return SN_ITEM_GET_CLASS (item)->get_id (SN_ITEM (item)); +} + +static NaItemCategory +sn_item_get_category (NaItem *item) +{ + const gchar *string; + NaItemCategory category; + + string = SN_ITEM_GET_CLASS (item)->get_category (SN_ITEM (item)); + + if (g_strcmp0 (string, "Hardware") == 0) + category = NA_ITEM_CATEGORY_HARDWARE; + else if (g_strcmp0 (string, "SystemServices") == 0) + category = NA_ITEM_CATEGORY_SYSTEM_SERVICES; + else if (g_strcmp0 (string, "Communications") == 0) + category = NA_ITEM_CATEGORY_COMMUNICATIONS; + else + category = NA_ITEM_CATEGORY_APPLICATION_STATUS; + + return category; +} + +static void +na_item_init (NaItemInterface *iface) +{ + iface->get_id = sn_item_get_id; + iface->get_category = sn_item_get_category; +} + +static void +install_properties (GObjectClass *object_class) +{ + g_object_class_install_property (object_class, PROP_BUS_NAME, + g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_OBJECT_PATH, + g_param_spec_string ("object-path", "object-path", "object-path", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_override_property (object_class, PROP_ORIENTATION, + "orientation"); +} + +static void +install_signals (SnItemClass *item_class) +{ + signals[SIGNAL_READY] = + g_signal_new ("ready", G_TYPE_FROM_CLASS (item_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SnItemClass, ready), NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static void +sn_item_class_init (SnItemClass *item_class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkButtonClass *button_class; + + object_class = G_OBJECT_CLASS (item_class); + widget_class = GTK_WIDGET_CLASS (item_class); + button_class = GTK_BUTTON_CLASS (item_class); + + object_class->dispose = sn_item_dispose; + object_class->finalize = sn_item_finalize; + object_class->get_property = sn_item_get_property; + object_class->set_property = sn_item_set_property; + + widget_class->button_press_event = sn_item_button_press_event; + widget_class->popup_menu = sn_item_popup_menu; + widget_class->scroll_event = sn_item_scroll_event; + + button_class->clicked = sn_item_clicked; + + item_class->ready = sn_item_ready; + + install_properties (object_class); + install_signals (item_class); + + g_type_class_add_private (item_class, sizeof (SnItemPrivate)); +} + +static void +sn_item_init (SnItem *item) +{ + gtk_widget_add_events (GTK_WIDGET (item), GDK_SCROLL_MASK); +} + +const gchar * +sn_item_get_bus_name (SnItem *item) +{ + SnItemPrivate *priv; + + priv = sn_item_get_instance_private (item); + + return priv->bus_name; +} + +const gchar * +sn_item_get_object_path (SnItem *item) +{ + SnItemPrivate *priv; + + priv = sn_item_get_instance_private (item); + + return priv->object_path; +} + +void +sn_item_emit_ready (SnItem *item) +{ + g_signal_emit (item, signals[SIGNAL_READY], 0); +} diff --git a/applets/notification_area/status-notifier/sn-item.h b/applets/notification_area/status-notifier/sn-item.h new file mode 100644 index 00000000..c414b942 --- /dev/null +++ b/applets/notification_area/status-notifier/sn-item.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * + * 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 of the License, 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SN_ITEM_H +#define SN_ITEM_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define SN_TYPE_ITEM sn_item_get_type () +G_DECLARE_DERIVABLE_TYPE (SnItem, sn_item, SN, ITEM, GtkButton) + +typedef enum +{ + SN_ITEM_ORIENTATION_HORIZONTAL, + SN_ITEM_ORIENTATION_VERTICAL +} SnItemOrientation; + +struct _SnItemClass +{ + GtkButtonClass parent_class; + + void (* ready) (SnItem *item); + + const gchar * (* get_id) (SnItem *item); + + const gchar * (* get_category) (SnItem *item); + + const gchar * (* get_menu) (SnItem *item); + + void (* context_menu) (SnItem *item, + gint x, + gint y); + + void (* activate) (SnItem *item, + gint x, + gint y); + + void (* secondary_activate) (SnItem *item, + gint x, + gint y); + + void (* scroll) (SnItem *item, + gint delta, + SnItemOrientation orientation); +}; + +const gchar *sn_item_get_bus_name (SnItem *item); +const gchar *sn_item_get_object_path (SnItem *item); + +void sn_item_emit_ready (SnItem *item); + +G_END_DECLS + +#endif |