diff --git a/qtox.pro b/qtox.pro index 882baceca..0007c1862 100644 --- a/qtox.pro +++ b/qtox.pro @@ -74,6 +74,23 @@ contains(ENABLE_SYSTRAY_UNITY_BACKEND, YES) { LIBS += -lgobject-2.0 -lappindicator -lgtk-x11-2.0 } +contains(ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND, YES) { + DEFINES += ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + + INCLUDEPATH += "/usr/include/gtk-2.0" + INCLUDEPATH += "/usr/include/glib-2.0" + INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include" + INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include" + INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include" + INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include" + INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0" + INCLUDEPATH += "/usr/include/cairo" + INCLUDEPATH += "/usr/include/pango-1.0" + INCLUDEPATH += "/usr/include/atk-1.0" + + LIBS += -lglib-2.0 -lgdk_pixbuf-2.0 -lgio-2.0 -lcairo -lgtk-x11-2.0 -lgdk-x11-2.0 -lgobject-2.0 +} + android { ANDROID_TOOLCHAIN=/opt/android/toolchain-r9d-17/ INCLUDEPATH += $$ANDROID_TOOLCHAIN/include/ @@ -249,7 +266,11 @@ HEADERS += src/widget/form/addfriendform.h \ src/nexus.h \ src/widget/gui.h \ src/widget/androidgui.h \ - src/offlinemsgengine.h + src/offlinemsgengine.h \ + src/platform/statusnotifier/closures.h \ + src/platform/statusnotifier/enums.h \ + src/platform/statusnotifier/interfaces.h \ + src/platform/statusnotifier/statusnotifier.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -328,7 +349,10 @@ SOURCES += \ src/nexus.cpp \ src/widget/gui.cpp \ src/widget/androidgui.cpp \ - src/offlinemsgengine.cpp + src/offlinemsgengine.cpp \ + src/platform/statusnotifier/closures.c \ + src/platform/statusnotifier/enums.c \ + src/platform/statusnotifier/statusnotifier.c contains(DEFINES, QTOX_FILTER_AUDIO) { HEADERS += src/audiofilterer.h diff --git a/src/platform/statusnotifier/closures.c b/src/platform/statusnotifier/closures.c new file mode 100644 index 000000000..90599dfb8 --- /dev/null +++ b/src/platform/statusnotifier/closures.c @@ -0,0 +1,114 @@ +/* + * statusnotifier - Copyright (C) 2014 Olivier Brunel + * + * closures.c + * Copyright (C) 2014 Olivier Brunel + * + * This file is part of statusnotifier. + * + * statusnotifier 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 3 of the License, or (at your option) any + * later version. + * + * statusnotifier 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 + * statusnotifier. If not, see http://www.gnu.org/licenses/ + */ + +#include +#include "closures.h" + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_schar (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#define g_marshal_value_peek_variant(v) g_value_get_variant (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* BOOLEAN:INT,INT (closures.def:1) */ +void +g_cclosure_user_marshal_BOOLEAN__INT_INT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__INT_INT) (gpointer data1, + gint arg_1, + gint arg_2, + gpointer data2); + register GMarshalFunc_BOOLEAN__INT_INT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__INT_INT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_int (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + diff --git a/src/platform/statusnotifier/closures.h b/src/platform/statusnotifier/closures.h new file mode 100644 index 000000000..4968e4999 --- /dev/null +++ b/src/platform/statusnotifier/closures.h @@ -0,0 +1,41 @@ +/* + * statusnotifier - Copyright (C) 2014 Olivier Brunel + * + * closures.h + * Copyright (C) 2014 Olivier Brunel + * + * This file is part of statusnotifier. + * + * statusnotifier 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 3 of the License, or (at your option) any + * later version. + * + * statusnotifier 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 + * statusnotifier. If not, see http://www.gnu.org/licenses/ + */ + +#ifndef __g_cclosure_user_marshal_MARSHAL_H__ +#define __g_cclosure_user_marshal_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +/* BOOLEAN:INT,INT (closures.def:1) */ +extern void g_cclosure_user_marshal_BOOLEAN__INT_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +G_END_DECLS + +#endif /* __g_cclosure_user_marshal_MARSHAL_H__ */ + diff --git a/src/platform/statusnotifier/enums.c b/src/platform/statusnotifier/enums.c new file mode 100644 index 000000000..5c72d8363 --- /dev/null +++ b/src/platform/statusnotifier/enums.c @@ -0,0 +1,101 @@ + + + +#include "enums.h" +GType +status_notifier_error_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_ERROR_NO_CONNECTION, "STATUS_NOTIFIER_ERROR_NO_CONNECTION", "connection" }, + { STATUS_NOTIFIER_ERROR_NO_NAME, "STATUS_NOTIFIER_ERROR_NO_NAME", "name" }, + { STATUS_NOTIFIER_ERROR_NO_WATCHER, "STATUS_NOTIFIER_ERROR_NO_WATCHER", "watcher" }, + { STATUS_NOTIFIER_ERROR_NO_HOST, "STATUS_NOTIFIER_ERROR_NO_HOST", "host" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierError", values); + } + return etype; +} +GType +status_notifier_state_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_STATE_NOT_REGISTERED, "STATUS_NOTIFIER_STATE_NOT_REGISTERED", "not-registered" }, + { STATUS_NOTIFIER_STATE_REGISTERING, "STATUS_NOTIFIER_STATE_REGISTERING", "registering" }, + { STATUS_NOTIFIER_STATE_REGISTERED, "STATUS_NOTIFIER_STATE_REGISTERED", "registered" }, + { STATUS_NOTIFIER_STATE_FAILED, "STATUS_NOTIFIER_STATE_FAILED", "failed" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierState", values); + } + return etype; +} +GType +status_notifier_icon_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_ICON, "STATUS_NOTIFIER_ICON", "status-notifier-icon" }, + { STATUS_NOTIFIER_ATTENTION_ICON, "STATUS_NOTIFIER_ATTENTION_ICON", "status-notifier-attention-icon" }, + { STATUS_NOTIFIER_OVERLAY_ICON, "STATUS_NOTIFIER_OVERLAY_ICON", "status-notifier-overlay-icon" }, + { STATUS_NOTIFIER_TOOLTIP_ICON, "STATUS_NOTIFIER_TOOLTIP_ICON", "status-notifier-tooltip-icon" }, + { _NB_STATUS_NOTIFIER_ICONS, "_NB_STATUS_NOTIFIER_ICONS", "-nb-status-notifier-icons" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierIcon", values); + } + return etype; +} +GType +status_notifier_category_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS, "STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS", "application-status" }, + { STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS, "STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS", "communications" }, + { STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES, "STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES", "system-services" }, + { STATUS_NOTIFIER_CATEGORY_HARDWARE, "STATUS_NOTIFIER_CATEGORY_HARDWARE", "hardware" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierCategory", values); + } + return etype; +} +GType +status_notifier_status_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_STATUS_PASSIVE, "STATUS_NOTIFIER_STATUS_PASSIVE", "passive" }, + { STATUS_NOTIFIER_STATUS_ACTIVE, "STATUS_NOTIFIER_STATUS_ACTIVE", "active" }, + { STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION, "STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION", "needs-attention" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierStatus", values); + } + return etype; +} +GType +status_notifier_scroll_orientation_get_type (void) +{ + static GType etype = 0; + if (etype == 0) { + static const GEnumValue values[] = { + { STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL, "STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL", "horizontal" }, + { STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL, "STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL", "vertical" }, + { 0, NULL, NULL } + }; + etype = g_enum_register_static ("StatusNotifierScrollOrientation", values); + } + return etype; +} + + + diff --git a/src/platform/statusnotifier/enums.h b/src/platform/statusnotifier/enums.h new file mode 100644 index 000000000..0a60f2683 --- /dev/null +++ b/src/platform/statusnotifier/enums.h @@ -0,0 +1,24 @@ +#ifndef __STATUS_NOTIFIER_ENUMS_H__ +#define __STATUS_NOTIFIER_ENUMS_H__ +#include "statusnotifier.h" + + + +GType status_notifier_error_get_type (void); +#define TYPE_STATUS_NOTIFIER_ERROR (status_notifier_error_get_type()) +GType status_notifier_state_get_type (void); +#define TYPE_STATUS_NOTIFIER_STATE (status_notifier_state_get_type()) +GType status_notifier_icon_get_type (void); +#define TYPE_STATUS_NOTIFIER_ICON (status_notifier_icon_get_type()) +GType status_notifier_category_get_type (void); +#define TYPE_STATUS_NOTIFIER_CATEGORY (status_notifier_category_get_type()) +GType status_notifier_status_get_type (void); +#define TYPE_STATUS_NOTIFIER_STATUS (status_notifier_status_get_type()) +GType status_notifier_scroll_orientation_get_type (void); +#define TYPE_STATUS_NOTIFIER_SCROLL_ORIENTATION (status_notifier_scroll_orientation_get_type()) +G_END_DECLS + +#endif /* __STATUS_NOTIFIER_ENUMS_H__ */ + + + diff --git a/src/platform/statusnotifier/interfaces.h b/src/platform/statusnotifier/interfaces.h new file mode 100644 index 000000000..507bfa75d --- /dev/null +++ b/src/platform/statusnotifier/interfaces.h @@ -0,0 +1,94 @@ +/* + * statusnotifier - Copyright (C) 2014 Olivier Brunel + * + * interfaces.h + * Copyright (C) 2014 Olivier Brunel + * + * This file is part of statusnotifier. + * + * statusnotifier 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 3 of the License, or (at your option) any + * later version. + * + * statusnotifier 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 + * statusnotifier. If not, see http://www.gnu.org/licenses/ + */ + +#ifndef __INTERFACES_H__ +#define __INTERFACES_H__ + +G_BEGIN_DECLS + +#define WATCHER_NAME "org.kde.StatusNotifierWatcher" +#define WATCHER_OBJECT "/StatusNotifierWatcher" +#define WATCHER_INTERFACE "org.kde.StatusNotifierWatcher" + +#define ITEM_NAME "org.kde.StatusNotifierItem" +#define ITEM_OBJECT "/StatusNotifierItem" +#define ITEM_INTERFACE "org.kde.StatusNotifierItem" + +static const gchar watcher_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + +static const gchar item_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +G_END_DECLS + +#endif /* __INTERFACES_H__ */ diff --git a/src/platform/statusnotifier/statusnotifier.c b/src/platform/statusnotifier/statusnotifier.c new file mode 100644 index 000000000..2b672d301 --- /dev/null +++ b/src/platform/statusnotifier/statusnotifier.c @@ -0,0 +1,1955 @@ +/* + * statusnotifier - Copyright (C) 2014 Olivier Brunel + * + * statusnotifier.c + * Copyright (C) 2014 Olivier Brunel + * + * This file is part of statusnotifier. + * + * statusnotifier 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 3 of the License, or (at your option) any + * later version. + * + * statusnotifier 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 + * statusnotifier. If not, see http://www.gnu.org/licenses/ + */ + +//#include "config.h" + +#include +#include +#include "statusnotifier.h" +#include "enums.h" +#include "interfaces.h" +#include "closures.h" + +/** + * SECTION:statusnotifier + * @Short_description: A StatusNotifierItem as per KDE's specifications + * + * Starting with Plasma Next, KDE doesn't support the XEmbed systray in favor of + * their own Status Notifier Specification. + * + * A #StatusNotifier is a #GObject that can be used to represent a + * StatusNotifierItem, handling all the DBus implementation and leaving you + * simply dealing with regular properties and signals. + * + * You can simply create a new #StatusNotifier using one of the helper function, + * e.g. status_notifier_new_from_icon_name(), or simply creating an object as + * usual - you then just need to make sure to specify #StatusNotifier:id : + * + * sn = (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, + * "id", "app-id", + * "status", STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION, + * "main-icon-name", "app-icon", + * "attention-icon-pixbuf", pixbuf, + * "tooltip-title", "My tooltip", + * "tooltip-body", "This is an item about <b>app</b>", + * NULL); + * + * + * You can also set properties (other than id) after creation. Once ready, call + * status_notifier_register() to register the item on the session bus and to the + * StatusNotifierWatcher. + * + * If an error occurs, signal #StatusNotifier::registration-failed will be + * emitted. On success, #StatusNotifier:state will be + * %STATUS_NOTIFIER_STATE_REGISTERED. See status_notifier_register() for more. + * + * Once registered, you can change properties as needed, and the proper DBus + * signal will be emitted to let visualizations (hosts) know, and connect to the + * signals (such as #StatusNotifier::context-menu) which will be emitted when + * the corresponding DBus method was called. + * + * For reference, the KDE specifications can be found at + * http://www.notmart.org/misc/statusnotifieritem/index.html + */ + +enum +{ + PROP_0, + + PROP_ID, + PROP_TITLE, + PROP_CATEGORY, + PROP_STATUS, + PROP_MAIN_ICON_NAME, + PROP_MAIN_ICON_PIXBUF, + PROP_OVERLAY_ICON_NAME, + PROP_OVERLAY_ICON_PIXBUF, + PROP_ATTENTION_ICON_NAME, + PROP_ATTENTION_ICON_PIXBUF, + PROP_ATTENTION_MOVIE_NAME, + PROP_TOOLTIP_ICON_NAME, + PROP_TOOLTIP_ICON_PIXBUF, + PROP_TOOLTIP_TITLE, + PROP_TOOLTIP_BODY, + PROP_WINDOW_ID, + + PROP_STATE, + + NB_PROPS +}; + +static guint prop_name_from_icon[_NB_STATUS_NOTIFIER_ICONS] = { + PROP_MAIN_ICON_NAME, + PROP_ATTENTION_ICON_NAME, + PROP_OVERLAY_ICON_NAME, + PROP_TOOLTIP_ICON_NAME +}; +static guint prop_pixbuf_from_icon[_NB_STATUS_NOTIFIER_ICONS] = { + PROP_MAIN_ICON_PIXBUF, + PROP_ATTENTION_ICON_PIXBUF, + PROP_OVERLAY_ICON_PIXBUF, + PROP_TOOLTIP_ICON_PIXBUF +}; + +enum +{ + SIGNAL_REGISTRATION_FAILED, + SIGNAL_CONTEXT_MENU, + SIGNAL_ACTIVATE, + SIGNAL_SECONDARY_ACTIVATE, + SIGNAL_SCROLL, + NB_SIGNALS +}; + +struct _StatusNotifierPrivate +{ + gchar *id; + StatusNotifierCategory category; + gchar *title; + StatusNotifierStatus status; + struct { + gboolean has_pixbuf; + union { + gchar *icon_name; + GdkPixbuf *pixbuf; + }; + } icon[_NB_STATUS_NOTIFIER_ICONS]; + gchar *attention_movie_name; + gchar *tooltip_title; + gchar *tooltip_body; + guint32 window_id; + + guint tooltip_freeze; + + StatusNotifierState state; + guint dbus_watch_id; + guint dbus_sid; + guint dbus_owner_id; + guint dbus_reg_id; + GDBusProxy *dbus_proxy; + GDBusConnection *dbus_conn; + GError *dbus_err; +}; + +static guint uniq_id = 0; + +static GParamSpec *status_notifier_props[NB_PROPS] = { NULL, }; +static guint status_notifier_signals[NB_SIGNALS] = { 0, }; + +#define notify(sn,prop) \ + g_object_notify_by_pspec ((GObject *) sn, status_notifier_props[prop]) + +static void status_notifier_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void status_notifier_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void status_notifier_finalize (GObject *object); + +G_DEFINE_TYPE (StatusNotifier, status_notifier, G_TYPE_OBJECT) + +static void +status_notifier_class_init (StatusNotifierClass *klass) +{ + GObjectClass *o_class; + + o_class = G_OBJECT_CLASS (klass); + o_class->set_property = status_notifier_set_property; + o_class->get_property = status_notifier_get_property; + o_class->finalize = status_notifier_finalize; + + /** + * StatusNotifier:id: + * + * It's a name that should be unique for this application and consistent + * between sessions, such as the application name itself. + */ + status_notifier_props[PROP_ID] = + g_param_spec_string ("id", "id", "Unique application identifier", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + /** + * StatusNotifier:title: + * + * It's a name that describes the application, it can be more descriptive + * than #StatusNotifier:id. + */ + status_notifier_props[PROP_TITLE] = + g_param_spec_string ("title", "title", "Descriptive name for the item", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:category: + * + * Describes the category of this item. + */ + status_notifier_props[PROP_CATEGORY] = + g_param_spec_enum ("category", "category", "Category of the item", + TYPE_STATUS_NOTIFIER_CATEGORY, + STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + /** + * StatusNotifier:status: + * + * Describes the status of this item or of the associated application. + */ + status_notifier_props[PROP_STATUS] = + g_param_spec_enum ("status", "status", "Status of the item", + TYPE_STATUS_NOTIFIER_STATUS, + STATUS_NOTIFIER_STATUS_PASSIVE, + G_PARAM_READWRITE); + + /** + * StatusNotifier:main-icon-name: + * + * The item can carry an icon that can be used by the visualization to + * identify the item. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by this property, or by the icon data itself, set by the property + * #StatusNotifier:main-icon-pixbuf. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_MAIN_ICON_NAME] = + g_param_spec_string ("main-icon-name", "main-icon-name", + "Icon name for the main icon", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:main-icon-pixbuf: + * + * The item can carry an icon that can be used by the visualization to + * identify the item. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by property #StatusNotifier:main-icon-name, or by the icon data + * itself, set by this property. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_MAIN_ICON_PIXBUF] = + g_param_spec_object ("main-icon-pixbuf", "main-icon-pixbuf", + "Pixbuf for the main icon", + GDK_TYPE_PIXBUF, + G_PARAM_READWRITE); + + /** + * StatusNotifier:overlay-icon-name: + * + * This can be used by the visualization to indicate extra state + * information, for instance as an overlay for the main icon. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by this property, or by the icon data itself, set by property + * #StatusNotifier:overlay-icon-pixbuf. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_OVERLAY_ICON_NAME] = + g_param_spec_string ("overlay-icon-name", "overlay-icon-name", + "Icon name for the overlay icon", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:overlay-icon-pixbuf: + * + * This can be used by the visualization to indicate extra state + * information, for instance as an overlay for the main icon. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by property #StatusNotifier:overlay-icon-name, or by the icon data + * itself, set by this property. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_OVERLAY_ICON_PIXBUF] = + g_param_spec_object ("overlay-icon-pixbuf", "overlay-icon-pixbuf", + "Pixbuf for the overlay icon", + GDK_TYPE_PIXBUF, + G_PARAM_READWRITE); + + /** + * StatusNotifier:attention-icon-name: + * + * This can be used by the visualization to indicate that the item is in + * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by this property, or by the icon data itself, set by property + * #StatusNotifier:attention-icon-pixbuf. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_ATTENTION_ICON_NAME] = + g_param_spec_string ("attention-icon-name", "attention-icon-name", + "Icon name for the attention icon", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:attention-icon-pixbuf: + * + * This can be used by the visualization to indicate that the item is in + * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * set by property #StatusNotifier:attention-icon-name, or by the icon data + * itself, set by this property. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_ATTENTION_ICON_PIXBUF] = + g_param_spec_object ("attention-icon-pixbuf", "attention-icon-pixbuf", + "Pixbuf for the attention icon", + GDK_TYPE_PIXBUF, + G_PARAM_READWRITE); + + /** + * StatusNotifier:attention-movie-name: + * + * In addition to the icon, the item can also specify an animation + * associated to the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. + * + * This should be either a Freedesktop-compliant icon name or a full path. + * The visualization can chose between the movie or icon (or using neither + * of those) at its discretion. + */ + status_notifier_props[PROP_ATTENTION_MOVIE_NAME] = + g_param_spec_string ("attention-movie-name", "attention-movie-name", + "Animation name/full path when the item is in needs-attention status", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:tooltip-icon-name: + * + * A tooltip can be defined on the item; It can be used by the visualization + * to show as a tooltip (or by any other mean it considers appropriate). + * + * The tooltip is composed of a title, a body, and an icon. Note that + * changing any of these will trigger a DBus signal NewToolTip (for hosts to + * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for + * changing more than one and only emitting one DBus signal at the end. + * + * The icon can either be identified by its Freedesktop-compliant icon name, + * set by this property, or by the icon data itself, set by property + * #StatusNotifier:tooltip-icon-pixbuf. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_TOOLTIP_ICON_NAME] = + g_param_spec_string ("tooltip-icon-name", "tooltip-icon-name", + "Icon name for the tooltip icon", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:tooltip-icon-pixbuf: + * + * A tooltip can be defined on the item; It can be used by the visualization + * to show as a tooltip (or by any other mean it considers appropriate). + * + * The tooltip is composed of a title, a body, and an icon. Note that + * changing any of these will trigger a DBus signal NewToolTip (for hosts to + * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for + * changing more than one and only emitting one DBus signal at the end. + * + * The icon can either be identified by its Freedesktop-compliant icon name, + * set by property #StatusNotifier:tooltip-icon-name, or by the icon data + * itself, set by this property. + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ + status_notifier_props[PROP_TOOLTIP_ICON_PIXBUF] = + g_param_spec_object ("tooltip-icon-pixbuf", "tooltip-icon-pixbuf", + "Pixbuf for the tooltip icon", + GDK_TYPE_PIXBUF, + G_PARAM_READWRITE); + + /** + * StatusNotifier:tooltip-title: + * + * A tooltip can be defined on the item; It can be used by the visualization + * to show as a tooltip (or by any other mean it considers appropriate). + * + * The tooltip is composed of a title, a body, and an icon. Note that + * changing any of these will trigger a DBus signal NewToolTip (for hosts to + * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for + * changing more than one and only emitting one DBus signal at the end. + */ + status_notifier_props[PROP_TOOLTIP_TITLE] = + g_param_spec_string ("tooltip-title", "tooltip-title", + "Title of the tooltip", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:tooltip-body: + * + * A tooltip can be defined on the item; It can be used by the visualization + * to show as a tooltip (or by any other mean it considers appropriate). + * + * The tooltip is composed of a title, a body, and an icon. Note that + * changing any of these will trigger a DBus signal NewToolTip (for hosts to + * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for + * changing more than one and only emitting one DBus signal at the end. + * + * This body can contain some markup, which consists of a small subset of + * XHTML. See http://www.notmart.org/misc/statusnotifieritem/markup.html for + * more. + */ + status_notifier_props[PROP_TOOLTIP_BODY] = + g_param_spec_string ("tooltip-body", "tooltip-body", + "Body of the tooltip", + NULL, + G_PARAM_READWRITE); + + /** + * StatusNotifier:window-id: + * + * It's the windowing-system dependent identifier for a window, the + * application can chose one of its windows to be available trough this + * property or just set 0 if it's not interested. + */ + status_notifier_props[PROP_WINDOW_ID] = + g_param_spec_uint ("window-id", "window-id", "Window ID", + 0, G_MAXUINT32, + 0, + G_PARAM_READWRITE); + + /** + * StatusNotifier:state: + * + * The state of the item, regarding its DBus registration on the + * StatusNotifierWatcher. After you've created the item, you need to call + * status_notifier_register() to have it registered via DBus on the watcher. + * + * See status_notifier_register() for more. + */ + status_notifier_props[PROP_STATE] = + g_param_spec_enum ("state", "state", + "DBus registration state of the item", + TYPE_STATUS_NOTIFIER_STATE, + STATUS_NOTIFIER_STATE_NOT_REGISTERED, + G_PARAM_READABLE); + + g_object_class_install_properties (o_class, NB_PROPS, status_notifier_props); + + /** + * StatusNotifier::registration-failed: + * @sn: The #StatusNotifier + * @error: A #GError with the reason of failure + * + * This signal is emited after a call to status_notifier_register() when + * registering the item eventually failed (e.g. if there wasn't (yet) any + * StatusNotifierHost registered.) + * + * When this happens, you should fallback to using the systray. You should + * also check #StatusNotifier:state as it might still be + * %STATUS_NOTIFIER_STATE_REGISTERING if the registration remains eventually + * possible (e.g. waiting for a StatusNotifierHost to register) + * + * See status_notifier_register() for more. + */ + status_notifier_signals[SIGNAL_REGISTRATION_FAILED] = g_signal_new ( + "registration-failed", + TYPE_STATUS_NOTIFIER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StatusNotifierClass, registration_failed), + NULL, + NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + G_TYPE_ERROR); + + /** + * StatusNotifier::context-menu: + * @sn: The #StatusNotifier + * @x: screen coordinates X + * @y: screen coordinates Y + * + * Emitted when the ContextMenu method was called on the item. Item should + * then show a context menu, this is typically a consequence of user input, + * such as mouse right click over the graphical representation of the item. + * + * @x and @y are to be considered an hint to the item about where to show + * the context menu. + */ + status_notifier_signals[SIGNAL_CONTEXT_MENU] = g_signal_new ( + "context-menu", + TYPE_STATUS_NOTIFIER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StatusNotifierClass, context_menu), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_user_marshal_BOOLEAN__INT_INT, + G_TYPE_BOOLEAN, + 2, + G_TYPE_INT, + G_TYPE_INT); + + /** + * StatusNotifier::activate: + * @sn: The #StatusNotifier + * @x: screen coordinates X + * @y: screen coordinates Y + * + * Emitted when the Activate method was called on the item. Activation of + * the item was requested, this is typically a consequence of user input, + * such as mouse left click over the graphical representation of the item. + * + * @x and @y are to be considered an hint to the item about where to show + * the context menu. + */ + status_notifier_signals[SIGNAL_ACTIVATE] = g_signal_new ( + "activate", + TYPE_STATUS_NOTIFIER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StatusNotifierClass, activate), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_user_marshal_BOOLEAN__INT_INT, + G_TYPE_BOOLEAN, + 2, + G_TYPE_INT, + G_TYPE_INT); + + /** + * StatusNotifier::secondary-activate: + * @sn: The #StatusNotifier + * @x: screen coordinates X + * @y: screen coordinates Y + * + * Emitted when the SecondaryActivate method was called on the item. + * Secondary and less important form of activation (compared to + * #StatusNotifier::activate) of the item was requested. This is typically a + * consequence of user input, such as mouse middle click over the graphical + * representation of the item. + * + * @x and @y are to be considered an hint to the item about where to show + * the context menu. + */ + status_notifier_signals[SIGNAL_SECONDARY_ACTIVATE] = g_signal_new ( + "secondary-activate", + TYPE_STATUS_NOTIFIER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StatusNotifierClass, secondary_activate), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_user_marshal_BOOLEAN__INT_INT, + G_TYPE_BOOLEAN, + 2, + G_TYPE_INT, + G_TYPE_INT); + + /** + * StatusNotifier::scroll: + * @sn: The #StatusNotifier + * @delta: the amount of scroll + * @orientation: orientation of the scroll request + * + * Emitted when the Scroll method was called on the item. The user asked for + * a scroll action. This is caused from input such as mouse wheel over the + * graphical representation of the item. + */ + status_notifier_signals[SIGNAL_SCROLL] = g_signal_new ( + "scroll", + TYPE_STATUS_NOTIFIER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (StatusNotifierClass, scroll), + g_signal_accumulator_true_handled, + NULL, + g_cclosure_user_marshal_BOOLEAN__INT_INT, + G_TYPE_BOOLEAN, + 2, + G_TYPE_INT, + TYPE_STATUS_NOTIFIER_SCROLL_ORIENTATION); + + g_type_class_add_private (klass, sizeof (StatusNotifierPrivate)); +} + +static void +status_notifier_init (StatusNotifier *sn) +{ + sn->priv = G_TYPE_INSTANCE_GET_PRIVATE (sn, + TYPE_STATUS_NOTIFIER, StatusNotifierPrivate); +} + +static void +status_notifier_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StatusNotifier *sn = (StatusNotifier *) object; + StatusNotifierPrivate *priv = sn->priv; + + switch (prop_id) + { + case PROP_ID: /* G_PARAM_CONSTRUCT_ONLY */ + priv->id = g_value_dup_string (value); + break; + case PROP_TITLE: + status_notifier_set_title (sn, g_value_get_string (value)); + break; + case PROP_CATEGORY: /* G_PARAM_CONSTRUCT_ONLY */ + priv->category = g_value_get_enum (value); + break; + case PROP_STATUS: + status_notifier_set_status (sn, g_value_get_enum (value)); + break; + case PROP_MAIN_ICON_NAME: + status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ICON, + g_value_get_string (value)); + break; + case PROP_MAIN_ICON_PIXBUF: + status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ICON, + g_value_get_object (value)); + break; + case PROP_OVERLAY_ICON_NAME: + status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_OVERLAY_ICON, + g_value_get_string (value)); + break; + case PROP_OVERLAY_ICON_PIXBUF: + status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_OVERLAY_ICON, + g_value_get_object (value)); + break; + case PROP_ATTENTION_ICON_NAME: + status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ATTENTION_ICON, + g_value_get_string (value)); + break; + case PROP_ATTENTION_ICON_PIXBUF: + status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ATTENTION_ICON, + g_value_get_object (value)); + break; + case PROP_ATTENTION_MOVIE_NAME: + status_notifier_set_attention_movie_name (sn, g_value_get_string (value)); + break; + case PROP_TOOLTIP_ICON_NAME: + status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, + g_value_get_string (value)); + break; + case PROP_TOOLTIP_ICON_PIXBUF: + status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_TOOLTIP_ICON, + g_value_get_object (value)); + break; + case PROP_TOOLTIP_TITLE: + status_notifier_set_tooltip_title (sn, g_value_get_string (value)); + break; + case PROP_TOOLTIP_BODY: + status_notifier_set_tooltip_body (sn, g_value_get_string (value)); + break; + case PROP_WINDOW_ID: + status_notifier_set_window_id (sn, g_value_get_uint (value)); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +status_notifier_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StatusNotifier *sn = (StatusNotifier *) object; + StatusNotifierPrivate *priv = sn->priv; + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, priv->id); + break; + case PROP_TITLE: + g_value_set_string (value, priv->title); + break; + case PROP_CATEGORY: + g_value_set_enum (value, priv->category); + break; + case PROP_STATUS: + g_value_set_enum (value, priv->status); + break; + case PROP_MAIN_ICON_NAME: + g_value_take_string (value, status_notifier_get_icon_name (sn, + STATUS_NOTIFIER_ICON)); + break; + case PROP_MAIN_ICON_PIXBUF: + g_value_take_object (value, status_notifier_get_pixbuf (sn, + STATUS_NOTIFIER_ICON)); + break; + case PROP_OVERLAY_ICON_NAME: + g_value_take_string (value, status_notifier_get_icon_name (sn, + STATUS_NOTIFIER_OVERLAY_ICON)); + break; + case PROP_OVERLAY_ICON_PIXBUF: + g_value_take_object (value, status_notifier_get_pixbuf (sn, + STATUS_NOTIFIER_OVERLAY_ICON)); + break; + case PROP_ATTENTION_ICON_NAME: + g_value_take_string (value, status_notifier_get_icon_name (sn, + STATUS_NOTIFIER_ATTENTION_ICON)); + break; + case PROP_ATTENTION_ICON_PIXBUF: + g_value_take_object (value, status_notifier_get_pixbuf (sn, + STATUS_NOTIFIER_ATTENTION_ICON)); + break; + case PROP_ATTENTION_MOVIE_NAME: + g_value_set_string (value, priv->attention_movie_name); + break; + case PROP_TOOLTIP_ICON_NAME: + g_value_take_string (value, status_notifier_get_icon_name (sn, + STATUS_NOTIFIER_TOOLTIP_ICON)); + break; + case PROP_TOOLTIP_ICON_PIXBUF: + g_value_take_object (value, status_notifier_get_pixbuf (sn, + STATUS_NOTIFIER_TOOLTIP_ICON)); + break; + case PROP_TOOLTIP_TITLE: + g_value_set_string (value, priv->tooltip_title); + break; + case PROP_TOOLTIP_BODY: + g_value_set_string (value, priv->tooltip_body); + break; + case PROP_WINDOW_ID: + g_value_set_uint (value, priv->window_id); + case PROP_STATE: + g_value_set_enum (value, priv->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +free_icon (StatusNotifier *sn, StatusNotifierIcon icon) +{ + StatusNotifierPrivate *priv = sn->priv; + + if (priv->icon[icon].has_pixbuf) + g_object_unref (priv->icon[icon].pixbuf); + else + g_free (priv->icon[icon].icon_name); + priv->icon[icon].has_pixbuf = FALSE; + priv->icon[icon].icon_name = NULL; +} + +static void +dbus_free (StatusNotifier *sn) +{ + StatusNotifierPrivate *priv = sn->priv; + + if (priv->dbus_watch_id > 0) + { + g_bus_unwatch_name (priv->dbus_watch_id); + priv->dbus_watch_id = 0; + } + if (priv->dbus_sid > 0) + { + g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid); + priv->dbus_sid = 0; + } + if (G_LIKELY (priv->dbus_owner_id > 0)) + { + g_bus_unown_name (priv->dbus_owner_id); + priv->dbus_owner_id = 0; + } + if (priv->dbus_proxy) + { + g_object_unref (priv->dbus_proxy); + priv->dbus_proxy = NULL; + } + if (priv->dbus_reg_id > 0) + { + g_dbus_connection_unregister_object (priv->dbus_conn, priv->dbus_reg_id); + priv->dbus_reg_id = 0; + } + if (priv->dbus_conn) + { + g_object_unref (priv->dbus_conn); + priv->dbus_conn = NULL; + } +} + +static void +status_notifier_finalize (GObject *object) +{ + StatusNotifier *sn = (StatusNotifier *) object; + StatusNotifierPrivate *priv = sn->priv; + guint i; + + g_free (priv->id); + g_free (priv->title); + for (i = 0; i < _NB_STATUS_NOTIFIER_ICONS; ++i) + free_icon (sn, i); + g_free (priv->attention_movie_name); + g_free (priv->tooltip_title); + g_free (priv->tooltip_body); + + dbus_free (sn); + + G_OBJECT_CLASS (status_notifier_parent_class)->finalize (object); +} + +static void +dbus_notify (StatusNotifier *sn, guint prop) +{ + StatusNotifierPrivate *priv = sn->priv; + const gchar *signal; + + if (priv->state != STATUS_NOTIFIER_STATE_REGISTERED) + return; + + switch (prop) + { + case PROP_STATUS: + { + const gchar *s_status[] = { + "Passive", + "Active", + "NeedsAttention" + }; + signal = "NewStatus"; + g_dbus_connection_emit_signal (priv->dbus_conn, + NULL, + ITEM_OBJECT, + ITEM_INTERFACE, + signal, + g_variant_new ("(s)", s_status[priv->status]), + NULL); + return; + } + case PROP_TITLE: + signal = "NewTitle"; + break; + case PROP_MAIN_ICON_NAME: + case PROP_MAIN_ICON_PIXBUF: + signal = "NewIcon"; + break; + case PROP_ATTENTION_ICON_NAME: + case PROP_ATTENTION_ICON_PIXBUF: + signal = "NewAttentionIcon"; + break; + case PROP_OVERLAY_ICON_NAME: + case PROP_OVERLAY_ICON_PIXBUF: + signal = "NewOverlayIcon"; + break; + case PROP_TOOLTIP_TITLE: + case PROP_TOOLTIP_BODY: + case PROP_TOOLTIP_ICON_NAME: + case PROP_TOOLTIP_ICON_PIXBUF: + signal = "NewToolTip"; + break; + default: + g_return_if_reached (); + } + + g_dbus_connection_emit_signal (priv->dbus_conn, + NULL, + ITEM_OBJECT, + ITEM_INTERFACE, + signal, + NULL, + NULL); +} + +/** + * status_notifier_new_from_pixbuf: + * @id: The application id + * @category: The category for the item + * @pixbuf: The icon to use as main icon + * + * Creates a new item + * + * Returns: (transfer full): A new #StatusNotifier + */ +StatusNotifier * +status_notifier_new_from_pixbuf (const gchar *id, + StatusNotifierCategory category, + GdkPixbuf *pixbuf) +{ + return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, + "id", id, + "category", category, + "main-icon-pixbuf", pixbuf, + NULL); +} + +/** + * status_notifier_new_from_icon_name: + * @id: The application id + * @category: The category for the item + * @icon_name: The name of the icon to use as main icon + * + * Creates a new item + * + * Returns: (transfer full): A new #StatusNotifier + */ +StatusNotifier * +status_notifier_new_from_icon_name (const gchar *id, + StatusNotifierCategory category, + const gchar *icon_name) +{ + return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER, + "id", id, + "category", category, + "main-icon-name", icon_name, + NULL); +} + +/** + * status_notifier_get_id: + * @sn: A #StatusNotifier + * + * Returns the application id of @sn + * + * Returns: The application id of @sn. The string is owned by @sn, you should + * not free it + */ +const gchar * +status_notifier_get_id (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + return sn->priv->id; +} + +/** + * status_notifier_get_category: + * @sn: A #StatusNotifier + * + * Returns the category of @sn + * + * Returns: The category of @sn + */ +StatusNotifierCategory +status_notifier_get_category (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1); + return sn->priv->category; +} + +/** + * status_notifier_set_from_pixbuf: + * @sn: A #StatusNotifier + * @icon: Which icon to set + * @pixbuf: A #GdkPixbuf to use for @icon + * + * Sets the icon @icon to @pixbuf. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * or by the icon data itself (via #GdkPixbuf). + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ +void +status_notifier_set_from_pixbuf (StatusNotifier *sn, + StatusNotifierIcon icon, + GdkPixbuf *pixbuf) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + free_icon (sn, icon); + priv->icon[icon].has_pixbuf = TRUE; + priv->icon[icon].pixbuf = g_object_ref (pixbuf); + + notify (sn, prop_name_from_icon[icon]); + if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0) + dbus_notify (sn, prop_name_from_icon[icon]); +} + +/** + * status_notifier_set_from_icon_name: + * @sn: A #StatusNotifier + * @icon: Which icon to set + * @icon_name: Name of an icon to use for @icon + * + * Sets the icon @icon to be @icon_name. + * + * An icon can either be identified by its Freedesktop-compliant icon name, + * or by the icon data itself (via #GdkPixbuf). + * + * It is currently not possible to set both, as setting one will unset the + * other. + */ +void +status_notifier_set_from_icon_name (StatusNotifier *sn, + StatusNotifierIcon icon, + const gchar *icon_name) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + free_icon (sn, icon); + priv->icon[icon].icon_name = g_strdup (icon_name); + + notify (sn, prop_pixbuf_from_icon[icon]); + if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0) + dbus_notify (sn, prop_name_from_icon[icon]); +} + +/** + * status_notifier_has_pixbuf: + * @sn: A #StatusNotifier + * @icon: Which icon + * + * Returns whether icon @icon currently has a #GdkPixbuf set or not. If so, the + * icon data will be sent via DBus, else the icon name (if any) will be used. + * + * Returns: %TRUE is a #GdkPixbuf is set for @icon, else %FALSE + */ +gboolean +status_notifier_has_pixbuf (StatusNotifier *sn, + StatusNotifierIcon icon) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE); + return sn->priv->icon[icon].has_pixbuf; +} + +/** + * status_notifier_get_pixbuf: + * @sn: A #StatusNotifier + * @icon: The icon to get + * + * Returns the #GdkPixbuf set for @icon, if there's one. Not that it will return + * %NULL if an icon name is set. + * + * Returns: (transfer full): The #GdkPixbuf set for @icon, or %NULL + */ +GdkPixbuf * +status_notifier_get_pixbuf (StatusNotifier *sn, + StatusNotifierIcon icon) +{ + StatusNotifierPrivate *priv; + + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + priv = sn->priv; + + if (!priv->icon[icon].has_pixbuf) + return NULL; + + return g_object_ref (priv->icon[icon].pixbuf); +} + +/** + * status_notifier_get_icon_name: + * @sn: A #StatusNotifier + * @icon: The icon to get + * + * Returns the icon name set for @icon, if there's one. Not that it will return + * %NULL if a #GdkPixbuf is set. + * + * Returns: (transfer full): A newly allocated string of the icon name set for + * @icon, free using g_free() + */ +gchar * +status_notifier_get_icon_name (StatusNotifier *sn, + StatusNotifierIcon icon) +{ + StatusNotifierPrivate *priv; + + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + priv = sn->priv; + + if (priv->icon[icon].has_pixbuf) + return NULL; + + return g_strdup (priv->icon[icon].icon_name); +} + +/** + * status_notifier_set_attention_movie_name: + * @sn: A #StatusNotifier + * @movie_name: The name of the movie + * + * In addition to the icon, the item can also specify an animation associated to + * the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status. + * + * This should be either a Freedesktop-compliant icon name or a full path. The + * visualization can chose between the movie or icon (or using neither of those) + * at its discretion. + */ +void +status_notifier_set_attention_movie_name (StatusNotifier *sn, + const gchar *movie_name) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + g_free (priv->attention_movie_name); + priv->attention_movie_name = g_strdup (movie_name); + + notify (sn, PROP_ATTENTION_MOVIE_NAME); +} + +/** + * status_notifier_get_attention_movie_name: + * @sn: A #StatusNotifier + * + * Returns the movie name set for animation associated with the + * #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status + * + * Returns: A newly allocated string with the movie name, free using g_free() + * when done + */ +gchar * +status_notifier_get_attention_movie_name (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + return g_strdup (sn->priv->attention_movie_name); +} + +/** + * status_notifier_set_title: + * @sn: A #StatusNotifier + * @title: The title + * + * Sets the title of the item (might be used by visualization e.g. in menu of + * hidden items when #STATUS_NOTIFIER_STATUS_PASSIVE) + */ +void +status_notifier_set_title (StatusNotifier *sn, + const gchar *title) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + g_free (priv->title); + priv->title = g_strdup (title); + + notify (sn, PROP_TITLE); + dbus_notify (sn, PROP_TITLE); +} + +/** + * status_notifier_get_title: + * @sn: A #StatusNotifier + * + * Returns the title of the item + * + * Returns: A newly allocated string, free with g_free() when done + */ +gchar * +status_notifier_get_title (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + return g_strdup (sn->priv->title); +} + +/** + * status_notifier_set_status: + * @sn: A #StatusNotifier + * @status: The new status + * + * Sets the item status to @status, describing the status of this item or of the + * associated application. + */ +void +status_notifier_set_status (StatusNotifier *sn, + StatusNotifierStatus status) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + priv->status = status; + + notify (sn, PROP_STATUS); + dbus_notify (sn, PROP_STATUS); +} + +/** + * status_notifier_get_status: + * @sn: A #StatusNotifier + * + * Returns the status of @sn + * + * Returns: Current status of @sn + */ +StatusNotifierStatus +status_notifier_get_status (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1); + return sn->priv->status; +} + +/** + * status_notifier_set_window_id: + * @sn: A #StatusNotifier + * @window_id: The window ID + * + * Sets the window ID for @sn + * + * It's the windowing-system dependent identifier for a window, the application + * can chose one of its windows to be available trough this property or just set + * 0 if it's not interested. + */ +void +status_notifier_set_window_id (StatusNotifier *sn, + guint32 window_id) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + priv->window_id = window_id; + + notify (sn, PROP_WINDOW_ID); +} + +/** + * status_notifier_get_window_id: + * @sn: A #StatusNotifier + * + * Returns the windowing-system dependent idnetifier for a window associated + * with @sn + * + * Returns: The window ID associated with @sn + */ +guint32 +status_notifier_get_window_id (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), 0); + return sn->priv->window_id; +} + +/** + * status_notifier_freeze_tooltip: + * @sn:A #StatusNotifier + * + * Increases the freeze count for tooltip on @sn. If the freeze count is + * non-zero, the emission of a DBus signal for StatusNotifierHost to refresh the + * ToolTip property will be blocked until the freeze count drops back to zero + * (via status_notifier_thaw_tooltip()) + * + * This is to allow to set the different properties forming the tooltip (title, + * body and icon) without triggering a refresh afetr each change (as there is a + * single property ToolTip on the DBus item, with all data). + * + * Every call to status_notifier_freeze_tooltip() should later be followed by a + * call to status_notifier_thaw_tooltip() + */ +void +status_notifier_freeze_tooltip (StatusNotifier *sn) +{ + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + ++sn->priv->tooltip_freeze; +} + +/** + * status_notifier_thaw_tooltip: + * @sn: A #StatusNotifier + * + * Reverts the effect of a previous call to status_notifier_freeze_tooltip(). If + * the freeze count drops back to zero, a signal NewToolTip will be emitted on + * the DBus object for @sn, for StatusNotifierHost to refresh its ToolTip + * property. + * + * It is an error to call this function when the freeze count is zero. + */ +void +status_notifier_thaw_tooltip (StatusNotifier *sn) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + g_return_if_fail (priv->tooltip_freeze > 0); + + if (--priv->tooltip_freeze == 0) + dbus_notify (sn, PROP_TOOLTIP_TITLE); +} + +/** + * status_notifier_set_tooltip: + * @sn: A #StatusNotifier + * @icon_name: The icon name to be used for #STATUS_NOTIFIER_TOOLTIP_ICON + * @title: The title of the tooltip + * @body: The body of the tooltip + * + * This is an helper function that allows to set icon name, title and body of + * the tooltip and then emit one DBus signal NewToolTip. + * + * It is equivalent to the following code, and similar code can be used e.g. to + * set the icon from a #GdkPixbuf instead: + * + * status_notifier_freeze_tooltip (sn); + * status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name); + * status_notifier_set_tooltip_title (sn, title); + * status_notifier_set_tooltip_body (sn, body); + * status_notifier_thaw_tooltip (sn); + * + */ +void +status_notifier_set_tooltip (StatusNotifier *sn, + const gchar *icon_name, + const gchar *title, + const gchar *body) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + ++priv->tooltip_freeze; + status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name); + status_notifier_set_tooltip_title (sn, title); + status_notifier_set_tooltip_body (sn, body); + status_notifier_thaw_tooltip (sn); +} + +/** + * status_notifier_set_tooltip_title: + * @sn: A #StatusNotifier + * @title: The tooltip title + * + * Sets the title of the tooltip + * + * The tooltip is composed of a title, a body, and an icon. Note that changing + * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus + * property ToolTip), see status_notifier_freeze_tooltip() for changing more + * than one and only emitting one DBus signal at the end. + */ +void +status_notifier_set_tooltip_title (StatusNotifier *sn, + const gchar *title) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + g_free (priv->tooltip_title); + priv->tooltip_title = g_strdup (title); + + notify (sn, PROP_TOOLTIP_TITLE); + if (priv->tooltip_freeze == 0) + dbus_notify (sn, PROP_TOOLTIP_TITLE); +} + +/** + * status_notifier_get_tooltip_title: + * @sn: A #StatusNotifier + * + * Returns the tooltip title + * + * Returns: A newly allocated string of the tooltip title, use g_free() when + * done + */ +gchar * +status_notifier_get_tooltip_title (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + return g_strdup (sn->priv->tooltip_title); +} + +/** + * status_notifier_set_tooltip_body: + * @sn: A #StatusNotifier + * @body: The tooltip body + * + * Sets the body of the tooltip + * + * This body can contain some markup, which consists of a small subset of XHTML. + * See http://www.notmart.org/misc/statusnotifieritem/markup.html for more. + * + * The tooltip is composed of a title, a body, and an icon. Note that changing + * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus + * property ToolTip), see status_notifier_freeze_tooltip() for changing more + * than one and only emitting one DBus signal at the end. + */ +void +status_notifier_set_tooltip_body (StatusNotifier *sn, + const gchar *body) +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + g_free (priv->tooltip_body); + priv->tooltip_body = g_strdup (body); + + notify (sn, PROP_TOOLTIP_BODY); + if (priv->tooltip_freeze == 0) + dbus_notify (sn, PROP_TOOLTIP_BODY); +} + +/** + * status_notifier_get_tooltip_body: + * @sn: A #StatusNotifier + * + * Returns the tooltip body + * + * Returns: A newly allocated string of the tooltip body, use g_free() when done + */ +gchar * +status_notifier_get_tooltip_body (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL); + return g_strdup (sn->priv->tooltip_body); +} + +static void +method_call (GDBusConnection *conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *params, + GDBusMethodInvocation *invocation, + gpointer data) +{ + (void)conn; + (void)sender; + (void)object; + (void)interface; + StatusNotifier *sn = (StatusNotifier *) data; + guint signal; + gint x, y; + gboolean ret; + + if (!g_strcmp0 (method, "ContextMenu")) + signal = SIGNAL_CONTEXT_MENU; + else if (!g_strcmp0 (method, "Activate")) + signal = SIGNAL_ACTIVATE; + else if (!g_strcmp0 (method, "SecondaryActivate")) + signal = SIGNAL_SECONDARY_ACTIVATE; + else if (!g_strcmp0 (method, "Scroll")) + { + gint delta, orientation; + gchar *s_orientation; + + g_variant_get (params, "(is)", &delta, &s_orientation); + if (!g_strcmp0 (s_orientation, "vertical")) + orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL; + else + orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL; + g_free (s_orientation); + + g_signal_emit (sn, status_notifier_signals[SIGNAL_SCROLL], 0, + delta, orientation, &ret); + g_dbus_method_invocation_return_value (invocation, NULL); + return; + } + else + /* should never happen */ + g_return_if_reached (); + + g_variant_get (params, "(ii)", &x, &y); + g_signal_emit (sn, status_notifier_signals[signal], 0, x, y, &ret); + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static GVariantBuilder * +get_builder_for_icon_pixmap (StatusNotifier *sn, StatusNotifierIcon icon) +{ + StatusNotifierPrivate *priv = sn->priv; + GVariantBuilder *builder; + cairo_surface_t *surface; + cairo_t *cr; + gint width, height, stride; + guint *data; + + if (G_UNLIKELY (!priv->icon[icon].has_pixbuf)) + return NULL; + + width = gdk_pixbuf_get_width (priv->icon[icon].pixbuf); + height = gdk_pixbuf_get_height (priv->icon[icon].pixbuf); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + gdk_cairo_set_source_pixbuf (cr, priv->icon[icon].pixbuf, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + + stride = cairo_image_surface_get_stride (surface); + cairo_surface_flush (surface); + data = (guint *) cairo_image_surface_get_data (surface); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + guint i, max; + + max = (stride * height) / sizeof (guint); + for (i = 0; i < max; ++i) + data[i] = GUINT_TO_BE (data[i]); +#endif + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a(iiay)")); + g_variant_builder_open (builder, G_VARIANT_TYPE ("(iiay)")); + g_variant_builder_add (builder, "i", width); + g_variant_builder_add (builder, "i", height); + g_variant_builder_add_value (builder, + g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + data, + stride * height, + TRUE, + (GDestroyNotify) cairo_surface_destroy, + surface)); + g_variant_builder_close (builder); + return builder; +} + +static GVariant * +get_prop (GDBusConnection *conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *property, + GError **error, + gpointer data) +{ + (void)conn; + (void)sender; + (void)object; + (void)interface; + (void)error; + StatusNotifier *sn = (StatusNotifier *) data; + StatusNotifierPrivate *priv = sn->priv; + + if (!g_strcmp0 (property, "Id")) + return g_variant_new ("s", priv->id); + else if (!g_strcmp0 (property, "Category")) + { + const gchar *s_category[] = { + "ApplicationStatus", + "Communications", + "SystemServices", + "Hardware" + }; + return g_variant_new ("s", s_category[priv->category]); + } + else if (!g_strcmp0 (property, "Title")) + return g_variant_new ("s", (priv->title) ? priv->title : ""); + else if (!g_strcmp0 (property, "Status")) + { + const gchar *s_status[] = { + "Passive", + "Active", + "NeedsAttention" + }; + return g_variant_new ("s", s_status[priv->status]); + } + else if (!g_strcmp0 (property, "WindowId")) + return g_variant_new ("i", priv->window_id); + else if (!g_strcmp0 (property, "IconName")) + return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ICON].has_pixbuf) + ? ((priv->icon[STATUS_NOTIFIER_ICON].icon_name) + ? priv->icon[STATUS_NOTIFIER_ICON].icon_name : "") : ""); + else if (!g_strcmp0 (property, "IconPixmap")) + return g_variant_new ("a(iiay)", + get_builder_for_icon_pixmap (sn, STATUS_NOTIFIER_ICON)); + else if (!g_strcmp0 (property, "OverlayIconName")) + return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].has_pixbuf) + ? ((priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name) + ? priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name : "") : ""); + else if (!g_strcmp0 (property, "OverlayIconPixmap")) + return g_variant_new ("a(iiay)", + get_builder_for_icon_pixmap (sn, STATUS_NOTIFIER_OVERLAY_ICON)); + else if (!g_strcmp0 (property, "AttentionIconName")) + return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].has_pixbuf) + ? ((priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name) + ? priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name : "") : ""); + else if (!g_strcmp0 (property, "AttentionIconPixmap")) + return g_variant_new ("a(iiay)", + get_builder_for_icon_pixmap (sn, STATUS_NOTIFIER_ATTENTION_ICON)); + else if (!g_strcmp0 (property, "AttentionMovieName")) + return g_variant_new ("s", (priv->attention_movie_name) + ? priv->attention_movie_name : ""); + else if (!g_strcmp0 (property, "ToolTip")) + { + GVariant *variant; + GVariantBuilder *builder; + + if (!priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].has_pixbuf) + { + variant = g_variant_new ("(sa(iiay)ss)", + (priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name) + ? priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name : "", + NULL, + (priv->tooltip_title) ? priv->tooltip_title : "", + (priv->tooltip_body) ? priv->tooltip_body : ""); + return variant; + } + + builder = get_builder_for_icon_pixmap (sn, STATUS_NOTIFIER_TOOLTIP_ICON); + variant = g_variant_new ("(sa(iiay)ss)", + "", + builder, + (priv->tooltip_title) ? priv->tooltip_title : "", + (priv->tooltip_body) ? priv->tooltip_body : ""); + g_variant_builder_unref (builder); + + return variant; + } + + g_return_val_if_reached (NULL); +} + +static void +dbus_failed (StatusNotifier *sn, GError *error, gboolean fatal) +{ + StatusNotifierPrivate *priv = sn->priv; + + dbus_free (sn); + if (fatal) + { + priv->state = STATUS_NOTIFIER_STATE_FAILED; + notify (sn, PROP_STATE); + } + g_signal_emit (sn, status_notifier_signals[SIGNAL_REGISTRATION_FAILED], 0, + error); + g_error_free (error); +} + +static void +bus_acquired (GDBusConnection *conn, const gchar *name, gpointer data) +{ + (void)name; + GError *err = NULL; + StatusNotifier *sn = (StatusNotifier *) data; + StatusNotifierPrivate *priv = sn->priv; + GDBusInterfaceVTable interface_vtable = { + .method_call = method_call, + .get_property = get_prop, + .set_property = NULL + }; + GDBusNodeInfo *info; + + info = g_dbus_node_info_new_for_xml (item_xml, NULL); + priv->dbus_reg_id = g_dbus_connection_register_object (conn, + ITEM_OBJECT, + info->interfaces[0], + &interface_vtable, + sn, NULL, + &err); + g_dbus_node_info_unref (info); + if (priv->dbus_reg_id == 0) + { + dbus_failed (sn, err, TRUE); + return; + } + + priv->dbus_conn = g_object_ref (conn); +} + +static void +register_item_cb (GObject *sce, GAsyncResult *result, gpointer data) +{ + GError *err = NULL; + StatusNotifier *sn = (StatusNotifier *) data; + StatusNotifierPrivate *priv = sn->priv; + GVariant *variant; + + variant = g_dbus_proxy_call_finish ((GDBusProxy *) sce, result, &err); + if (!variant) + { + dbus_failed (sn, err, TRUE); + return; + } + g_variant_unref (variant); + + priv->state = STATUS_NOTIFIER_STATE_REGISTERED; + notify (sn, PROP_STATE); +} + +static void +name_acquired (GDBusConnection *conn, const gchar *name, gpointer data) +{ + (void)conn; + StatusNotifier *sn = (StatusNotifier *) data; + StatusNotifierPrivate *priv = sn->priv; + + g_dbus_proxy_call (priv->dbus_proxy, + "RegisterStatusNotifierItem", + g_variant_new ("(s)", name), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + register_item_cb, + sn); + g_object_unref (priv->dbus_proxy); + priv->dbus_proxy = NULL; +} + +static void +name_lost (GDBusConnection *conn, const gchar *name, gpointer data) +{ + (void)name; + GError *err = NULL; + StatusNotifier *sn = (StatusNotifier *) data; + + if (!conn) + g_set_error (&err, STATUS_NOTIFIER_ERROR, + STATUS_NOTIFIER_ERROR_NO_CONNECTION, + "Failed to establish DBus connection"); + else + g_set_error (&err, STATUS_NOTIFIER_ERROR, + STATUS_NOTIFIER_ERROR_NO_NAME, + "Failed to acquire name for item"); + dbus_failed (sn, err, TRUE); +} + +static void +dbus_reg_item (StatusNotifier *sn) +{ + StatusNotifierPrivate *priv = sn->priv; + gchar buf[64], *b = buf; + + if (G_UNLIKELY (g_snprintf (buf, 64, "org.kde.StatusNotifierItem-%u-%u", + getpid (), ++uniq_id) >= 64)) + b = g_strdup_printf ("org.kde.StatusNotifierItem-%u-%u", + getpid (), uniq_id); + priv->dbus_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + b, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired, + name_acquired, + name_lost, + sn, NULL); + if (G_UNLIKELY (b != buf)) + g_free (b); +} + +static void +watcher_signal (GDBusProxy *proxy, + const gchar *sender, + const gchar *signal, + GVariant *params, + StatusNotifier *sn) +{ + (void)proxy; + (void)sender; + (void)params; + StatusNotifierPrivate *priv = sn->priv; + + if (!g_strcmp0 (signal, "StatusNotifierHostRegistered")) + { + g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid); + priv->dbus_sid = 0; + + dbus_reg_item (sn); + } +} + +static void +proxy_cb (GObject *sce, GAsyncResult *result, gpointer data) +{ + (void)sce; + GError *err = NULL; + StatusNotifier *sn = (StatusNotifier *) data; + StatusNotifierPrivate *priv = sn->priv; + GVariant *variant; + + priv->dbus_proxy = g_dbus_proxy_new_for_bus_finish (result, &err); + if (!priv->dbus_proxy) + { + dbus_failed (sn, err, TRUE); + return; + } + + variant = g_dbus_proxy_get_cached_property (priv->dbus_proxy, + "IsStatusNotifierHostRegistered"); + if (!variant || !g_variant_get_boolean (variant)) + { + GDBusProxy *proxy; + + g_set_error (&err, STATUS_NOTIFIER_ERROR, + STATUS_NOTIFIER_ERROR_NO_HOST, + "No Host registered on the Watcher"); + if (variant) + g_variant_unref (variant); + + /* keep the proxy, we'll wait for the signal when a host registers */ + proxy = priv->dbus_proxy; + /* (so dbus_free() from dbus_failed() doesn't unref) */ + priv->dbus_proxy = NULL; + dbus_failed (sn, err, FALSE); + priv->dbus_proxy = proxy; + + priv->dbus_sid = g_signal_connect (priv->dbus_proxy, "g-signal", + (GCallback) watcher_signal, sn); + return; + } + g_variant_unref (variant); + + dbus_reg_item (sn); +} + +static void +watcher_appeared (GDBusConnection *conn, + const gchar *name, + const gchar *owner, + gpointer data) +{ + (void)conn; + (void)name; + (void)owner; + StatusNotifier *sn = data; + StatusNotifierPrivate *priv = sn->priv; + GDBusNodeInfo *info; + + g_bus_unwatch_name (priv->dbus_watch_id); + priv->dbus_watch_id = 0; + + info = g_dbus_node_info_new_for_xml (watcher_xml, NULL); + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + info->interfaces[0], + WATCHER_NAME, + WATCHER_OBJECT, + WATCHER_INTERFACE, + NULL, + proxy_cb, + sn); + g_dbus_node_info_unref (info); +} + +static void +watcher_vanished (GDBusConnection *conn, + const gchar *name, + gpointer data) +{ + (void)conn; + (void)name; + GError *err = NULL; + StatusNotifier *sn = data; + StatusNotifierPrivate *priv = sn->priv; + guint id; + + /* keep the watch active, so if a watcher shows up we'll resume the + * registering automatically */ + id = priv->dbus_watch_id; + /* (so dbus_free() from dbus_failed() doesn't unwatch) */ + priv->dbus_watch_id = 0; + + g_set_error (&err, STATUS_NOTIFIER_ERROR, + STATUS_NOTIFIER_ERROR_NO_WATCHER, + "No Watcher found"); + dbus_failed (sn, err, FALSE); + + priv->dbus_watch_id = id; +} + +/** + * status_notifier_register: + * @sn: A #StatusNotifier + * + * Registers @sn to the StatusNotifierWatcher over DBus. + * + * Once you have created your #StatusNotifier you need to register it, so any + * host/visualization can use it and update their GUI as needed. + * + * This function will connect to the StatusNotifierWatcher and make sure at + * least one StatusNotifierHost is registered. Then, it will register a new + * StatusNotifierItem on the session bus and register it with the watcher. + * + * When done, property #StatusNotifier:state will change to + * %STATUS_NOTIFIER_STATE_REGISTERED. If something fails, signal + * #StatusNotifier::registration-failed will be emitted, at which point you + * should fallback to using the systray. + * + * However there are two possible types of failures: fatal and non-fatal ones. + * Fatal error means that #StatusNotifier:state will be + * %STATUS_NOTIFIER_STATE_FAILED and you can unref @sn. + * + * Non-fatal error means it will still be %STATUS_NOTIFIER_STATE_REGISTERING as + * the registration process could still eventually succeed. For example, if + * there was no host registered on the watcher, as soon as signal + * StatusNotifierHostRegistered is emitted on the watcher, the registration + * process for @sn will complete and #StatusNotifier:state set to + * %STATUS_NOTIFIER_STATE_REGISTERED, at which point you should stop using the + * systray. + * + * This also means it is possible to have multiple signals + * #StatusNotifier::registration-failed emitted on the same #StatusNotifier. + * + * Note that you can call status_notifier_register() after a fatal error + * occured, to try again. You can also unref @sn while it is + * %STATUS_NOTIFIER_STATE_REGISTERING safely. + */ +void +status_notifier_register (StatusNotifier *sn) + +{ + StatusNotifierPrivate *priv; + + g_return_if_fail (IS_STATUS_NOTIFIER (sn)); + priv = sn->priv; + + if (priv->state == STATUS_NOTIFIER_STATE_REGISTERING + || priv->state == STATUS_NOTIFIER_STATE_REGISTERED) + return; + priv->state = STATUS_NOTIFIER_STATE_REGISTERING; + + priv->dbus_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + WATCHER_NAME, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + watcher_appeared, + watcher_vanished, + sn, NULL); +} + +/** + * status_notifier_get_state: + * @sn: A #StatusNotifier + * + * Returns the DBus registration state of @sn. See status_notifier_register() + * for more. + * + * Returns: The DBus registration state of @sn + */ +StatusNotifierState +status_notifier_get_state (StatusNotifier *sn) +{ + g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE); + return sn->priv->state; +} diff --git a/src/platform/statusnotifier/statusnotifier.h b/src/platform/statusnotifier/statusnotifier.h new file mode 100644 index 000000000..3834064d5 --- /dev/null +++ b/src/platform/statusnotifier/statusnotifier.h @@ -0,0 +1,304 @@ +/* + * statusnotifier - Copyright (C) 2014 Olivier Brunel + * + * statusnotifier.h + * Copyright (C) 2014 Olivier Brunel + * + * This file is part of statusnotifier. + * + * statusnotifier 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 3 of the License, or (at your option) any + * later version. + * + * statusnotifier 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 + * statusnotifier. If not, see http://www.gnu.org/licenses/ + */ + +#ifndef __STATUS_NOTIFIER_H__ +#define __STATUS_NOTIFIER_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _StatusNotifier StatusNotifier; +typedef struct _StatusNotifierPrivate StatusNotifierPrivate; +typedef struct _StatusNotifierClass StatusNotifierClass; + +#define TYPE_STATUS_NOTIFIER (status_notifier_get_type ()) +#define STATUS_NOTIFIER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_STATUS_NOTIFIER, StatusNotifier)) +#define STATUS_NOTIFIER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_STATUS_NOTIFIER, StatusNotiferClass)) +#define IS_STATUS_NOTIFIER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_STATUS_NOTIFIER)) +#define IS_STATUS_NOTIFIER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TYPE_STATUS_NOTIFIER)) +#define STATUS_NOTIFIER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_STATUS_NOTIFIER, StatusNotifierClass)) + +GType status_notifier_get_type (void) G_GNUC_CONST; + +#define STATUS_NOTIFIER_ERROR g_quark_from_static_string ("StatusNotifier error") +/** + * StatusNotifierError: + * @STATUS_NOTIFIER_ERROR_NO_CONNECTION: Failed to establish connection to + * register service on session bus + * @STATUS_NOTIFIER_ERROR_NO_NAME: Failed to acquire name for the item on the + * session bus + * @STATUS_NOTIFIER_ERROR_NO_WATCHER: No StatusNotifierWatcher found on the + * session bus + * @STATUS_NOTIFIER_ERROR_NO_HOST: No StatusNotifierHost registered with the + * StatusNotifierWatcher + * + * Errors that can occur while trying to register the item. Note that errors + * other the #StatusNotifierError might be returned. + */ +typedef enum +{ + STATUS_NOTIFIER_ERROR_NO_CONNECTION = 0, + STATUS_NOTIFIER_ERROR_NO_NAME, + STATUS_NOTIFIER_ERROR_NO_WATCHER, + STATUS_NOTIFIER_ERROR_NO_HOST +} StatusNotifierError; + +/** + * StatusNotifierState: + * @STATUS_NOTIFIER_STATE_NOT_REGISTERED: Item hasn't yet been asked to + * register, i.e. no call to status_notifier_register() have been made yet + * @STATUS_NOTIFIER_STATE_REGISTERING: Item is in the process of registering. + * This state is also valid after #StatusNotifier::registration-failed was + * emitted, if the item is waiting for possible "recovery" (e.g. if no host was + * registered on watcher, waiting for one to do so) + * @STATUS_NOTIFIER_STATE_REGISTERED: Item was sucessfully registered on DBus + * and StatusNotifierWatcher + * @STATUS_NOTIFIER_STATE_FAILED: Registration failed, with no possible pending + * recovery + * + * State in which a #StatusNotifier item can be. See status_notifier_register() + * for more + */ +typedef enum +{ + STATUS_NOTIFIER_STATE_NOT_REGISTERED = 0, + STATUS_NOTIFIER_STATE_REGISTERING, + STATUS_NOTIFIER_STATE_REGISTERED, + STATUS_NOTIFIER_STATE_FAILED +} StatusNotifierState; + +/** + * StatusNotifierIcon: + * @STATUS_NOTIFIER_ICON: The icon that can be used by the visualization to + * identify the item. + * @STATUS_NOTIFIER_ATTENTION_ICON: The icon that can be used by the + * visualization when the item's status is + * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION. + * @STATUS_NOTIFIER_OVERLAY_ICON: This can be used by the visualization to + * indicate extra state information, for instance as an overlay for the main + * icon. + * @STATUS_NOTIFIER_TOOLTIP_ICON: The icon that can be used be the visualization + * in the tooltip of the item. + * + * Possible icons that can be used on a status notifier item. + */ +typedef enum +{ + STATUS_NOTIFIER_ICON = 0, + STATUS_NOTIFIER_ATTENTION_ICON, + STATUS_NOTIFIER_OVERLAY_ICON, + STATUS_NOTIFIER_TOOLTIP_ICON, + /*< private >*/ + _NB_STATUS_NOTIFIER_ICONS +} StatusNotifierIcon; + +/** + * StatusNotifierCategory: + * @STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS: The item describes the status + * of a generic application, for instance the current state of a media player. + * In the case where the category of the item can not be known, such as when the + * item is being proxied from another incompatible or emulated system, this can + * be used a sensible default fallback. + * @STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS: The item describes the status of + * communication oriented applications, like an instant messenger or an email + * client. + * @STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES: The item describes services of the + * system not seen as a stand alone application by the user, such as an + * indicator for the activity of a disk indexing service. + * @STATUS_NOTIFIER_CATEGORY_HARDWARE: The item describes the state and control + * of a particular hardware, such as an indicator of the battery charge or sound + * card volume control. + * + * The category of the status notifier item. + */ +typedef enum +{ + STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS = 0, + STATUS_NOTIFIER_CATEGORY_COMMUNICATIONS, + STATUS_NOTIFIER_CATEGORY_SYSTEM_SERVICES, + STATUS_NOTIFIER_CATEGORY_HARDWARE +} StatusNotifierCategory; + +/** + * StatusNotifierStatus: + * @STATUS_NOTIFIER_STATUS_PASSIVE: The item doesn't convey important + * information to the user, it can be considered an "idle" status and is likely + * that visualizations will chose to hide it. + * @STATUS_NOTIFIER_STATUS_ACTIVE: The item is active, is more important that + * the item will be shown in some way to the user. + * @STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION: The item carries really important + * information for the user, such as battery charge running out and is wants to + * incentive the direct user intervention. Visualizations should emphasize in + * some way the items with this status. + * + * The status of the status notifier item or its associated application. + */ +typedef enum +{ + STATUS_NOTIFIER_STATUS_PASSIVE = 0, + STATUS_NOTIFIER_STATUS_ACTIVE, + STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION +} StatusNotifierStatus; + +/** + * StatusNotifierScrollOrientation: + * @STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL: Scroll request was + * horizontal. + * @STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL: Scroll request was vertical. + * + * The orientation of a scroll request performed on the representation of the + * item in the visualization. + */ +typedef enum +{ + STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL = 0, + STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL +} StatusNotifierScrollOrientation; + +struct _StatusNotifier +{ + /*< private >*/ + GObject parent; + StatusNotifierPrivate *priv; +}; + +/** + * StatusNotifierClass: + * @parent_class: Parent class + * @registration_failed: When registering the item failed, e.g. because there's + * no StatusNotifierHost registered (yet); If this occurs, you should fallback + * to using the systray + * @context_menu: Item should show a context menu, this is typically a + * consequence of user input, such as mouse right click over the graphical + * representation of the item. + * @activate: Activation of the item was requested, this is typically a + * consequence of user input, such as mouse left click over the graphical + * representation of the item. + * @secondary_activate: Secondary and less important form of activation + * (compared to @activate) of the item was requested. This is typically a + * consequence of user input, such as mouse middle click over the graphical + * representation of the item. + * @scroll: The user asked for a scroll action. This is caused from input such + * as mouse wheel over the graphical representation of the item. + */ +struct _StatusNotifierClass +{ + GObjectClass parent_class; + + /* signals */ + void (*registration_failed) (StatusNotifier *sn, + GError *error); + + gboolean (*context_menu) (StatusNotifier *sn, + gint x, + gint y); + gboolean (*activate) (StatusNotifier *sn, + gint x, + gint y); + gboolean (*secondary_activate) (StatusNotifier *sn, + gint x, + gint y); + gboolean (*scroll) (StatusNotifier *sn, + gint delta, + StatusNotifierScrollOrientation orientation); +}; + +StatusNotifier * status_notifier_new_from_pixbuf ( + const gchar *id, + StatusNotifierCategory category, + GdkPixbuf *pixbuf); +StatusNotifier * status_notifier_new_from_icon_name ( + const gchar *id, + StatusNotifierCategory category, + const gchar *icon_name); +const gchar * status_notifier_get_id ( + StatusNotifier *sn); +StatusNotifierCategory status_notifier_get_category ( + StatusNotifier *sn); +void status_notifier_set_from_pixbuf ( + StatusNotifier *sn, + StatusNotifierIcon icon, + GdkPixbuf *pixbuf); +void status_notifier_set_from_icon_name ( + StatusNotifier *sn, + StatusNotifierIcon icon, + const gchar *icon_name); +gboolean status_notifier_has_pixbuf ( + StatusNotifier *sn, + StatusNotifierIcon icon); +GdkPixbuf * status_notifier_get_pixbuf ( + StatusNotifier *sn, + StatusNotifierIcon icon); +gchar * status_notifier_get_icon_name ( + StatusNotifier *sn, + StatusNotifierIcon icon); +void status_notifier_set_attention_movie_name ( + StatusNotifier *sn, + const gchar *movie_name); +gchar * status_notifier_get_attention_movie_name ( + StatusNotifier *sn); +void status_notifier_set_title ( + StatusNotifier *sn, + const gchar *title); +gchar * status_notifier_get_title ( + StatusNotifier *sn); +void status_notifier_set_status ( + StatusNotifier *sn, + StatusNotifierStatus status); +StatusNotifierStatus status_notifier_get_status ( + StatusNotifier *sn); +void status_notifier_set_window_id ( + StatusNotifier *sn, + guint32 window_id); +guint32 status_notifier_get_window_id ( + StatusNotifier *sn); +void status_notifier_freeze_tooltip ( + StatusNotifier *sn); +void status_notifier_thaw_tooltip ( + StatusNotifier *sn); +void status_notifier_set_tooltip ( + StatusNotifier *sn, + const gchar *icon_name, + const gchar *title, + const gchar *body); +void status_notifier_set_tooltip_title ( + StatusNotifier *sn, + const gchar *title); +gchar * status_notifier_get_tooltip_title ( + StatusNotifier *sn); +void status_notifier_set_tooltip_body ( + StatusNotifier *sn, + const gchar *body); +gchar * status_notifier_get_tooltip_body ( + StatusNotifier *sn); +void status_notifier_register ( + StatusNotifier *sn); +StatusNotifierState status_notifier_get_state ( + StatusNotifier *sn); + +G_END_DECLS + +#endif /* __STATUS_NOTIFIER_H__ */ diff --git a/src/widget/systemtrayicon.cpp b/src/widget/systemtrayicon.cpp index cf10e1506..de569cd53 100644 --- a/src/widget/systemtrayicon.cpp +++ b/src/widget/systemtrayicon.cpp @@ -34,6 +34,29 @@ SystemTrayIcon::SystemTrayIcon() app_indicator_set_menu(unityIndicator, GTK_MENU(unityMenu)); } #endif + #ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + else if (true) + { + backendType = SystrayBackendType::StatusNotifier; + gtk_init(nullptr, nullptr); + snMenu = gtk_menu_new(); + void (*callbackFreeImage)(guchar*, gpointer) = + [](guchar*, gpointer image) + { + delete reinterpret_cast(image); + }; + QImage* image = new QImage(":/img/icon.png"); + if (image->format() != QImage::Format_RGBA8888_Premultiplied) + *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied); + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(image->bits(), GDK_COLORSPACE_RGB, image->hasAlphaChannel(), + 8, image->width(), image->height(), + image->bytesPerLine(), callbackFreeImage, image); + + statusNotifier = status_notifier_new_from_pixbuf("qtox", + STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS, pixbuf); + status_notifier_register(statusNotifier); + } + #endif else if (desktop.toLower() == "kde" && getenv("KDE_SESSION_VERSION") == QString("5")) { @@ -69,12 +92,69 @@ QString SystemTrayIcon::extractIconToFile(QIcon icon, QString name) void SystemTrayIcon::setContextMenu(QMenu* menu) { if (false); + #ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + else if (backendType == SystrayBackendType::StatusNotifier) + { + void (*callbackClick)(StatusNotifier*, gint, gint, gpointer) = + [](StatusNotifier*, gint, gint, gpointer data) + { + ((SystemTrayIcon*)data)->activated(QSystemTrayIcon::Trigger); + }; + g_signal_connect(statusNotifier, "activate", G_CALLBACK(callbackClick), this); + void (*callbackMiddleClick)(StatusNotifier*, gint, gint, gpointer) = + [](StatusNotifier*, gint, gint, gpointer data) + { + ((SystemTrayIcon*)data)->activated(QSystemTrayIcon::MiddleClick); + }; + g_signal_connect(statusNotifier, "secondary_activate", G_CALLBACK(callbackMiddleClick), this); + + for (QAction* a : menu->actions()) + { + QString aText = a->text().replace('&',""); + GtkWidget* item; + if (a->isSeparator()) + item = gtk_menu_item_new(); + else if (a->icon().isNull()) + item = gtk_menu_item_new_with_label(aText.toStdString().c_str()); + else + { + void (*callbackFreeImage)(guchar*, gpointer) = + [](guchar*, gpointer image) + { + delete reinterpret_cast(image); + }; + QImage* image = new QImage(a->icon().pixmap(64, 64).toImage()); + if (image->format() != QImage::Format_RGBA8888_Premultiplied) + *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied); + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(image->bits(), GDK_COLORSPACE_RGB, image->hasAlphaChannel(), + 8, image->width(), image->height(), + image->bytesPerLine(), callbackFreeImage, image); + item = gtk_image_menu_item_new_with_label(aText.toStdString().c_str()); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), gtk_image_new_from_pixbuf(pixbuf)); + gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item),TRUE); + } + gtk_menu_shell_append(GTK_MENU_SHELL(snMenu), item); + void (*callback)(GtkMenu*, gpointer data) = [](GtkMenu*, gpointer a) + { + ((QAction*)a)->activate(QAction::Trigger); + }; + g_signal_connect(item, "activate", G_CALLBACK(callback), a); + gtk_widget_show(item); + } + void (*callbackMenu)(StatusNotifier*, gint, gint, gpointer) = + [](StatusNotifier*, gint, gint, gpointer data) + { + gtk_widget_show_all(((SystemTrayIcon*)data)->snMenu); + gtk_menu_popup(GTK_MENU(((SystemTrayIcon*)data)->snMenu), 0, 0, 0, 0, 3, gtk_get_current_event_time()); + }; + g_signal_connect(statusNotifier, "context-menu", G_CALLBACK(callbackMenu), this); + } + #endif #ifdef ENABLE_SYSTRAY_UNITY_BACKEND else if (backendType == SystrayBackendType::Unity) { for (QAction* a : menu->actions()) { - gtk_image_menu_item_new(); QString aText = a->text().replace('&',""); GtkWidget* item; if (a->isSeparator()) @@ -129,6 +209,15 @@ void SystemTrayIcon::hide() void SystemTrayIcon::setVisible(bool newState) { if (false); + #ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + else if (backendType == SystrayBackendType::StatusNotifier) + { + if (newState) + status_notifier_set_status(statusNotifier, STATUS_NOTIFIER_STATUS_ACTIVE); + else + status_notifier_set_status(statusNotifier, STATUS_NOTIFIER_STATUS_PASSIVE); + } + #endif #ifdef ENABLE_SYSTRAY_UNITY_BACKEND else if (backendType == SystrayBackendType::Unity) { @@ -150,6 +239,23 @@ void SystemTrayIcon::setVisible(bool newState) void SystemTrayIcon::setIcon(QIcon &&icon) { if (false); + #ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + else if (backendType == SystrayBackendType::StatusNotifier) + { + void (*callbackFreeImage)(guchar*, gpointer) = + [](guchar*, gpointer image) + { + delete reinterpret_cast(image); + }; + QImage* image = new QImage(icon.pixmap(64, 64).toImage()); + if (image->format() != QImage::Format_RGBA8888_Premultiplied) + *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied); + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(image->bits(), GDK_COLORSPACE_RGB, image->hasAlphaChannel(), + 8, image->width(), image->height(), + image->bytesPerLine(), callbackFreeImage, image); + status_notifier_set_from_pixbuf(statusNotifier, STATUS_NOTIFIER_ICON, pixbuf); + } + #endif #ifdef ENABLE_SYSTRAY_UNITY_BACKEND else if (backendType == SystrayBackendType::Unity) { diff --git a/src/widget/systemtrayicon.h b/src/widget/systemtrayicon.h index 540d96285..66186fa26 100644 --- a/src/widget/systemtrayicon.h +++ b/src/widget/systemtrayicon.h @@ -32,6 +32,10 @@ private: AppIndicator *unityIndicator; GtkWidget *unityMenu; #endif +#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + StatusNotifier* statusNotifier; + GtkWidget* snMenu; +#endif }; #endif // SYSTEMTRAYICON_H diff --git a/src/widget/systemtrayicon_private.h b/src/widget/systemtrayicon_private.h index fa3f904fe..e84389f84 100644 --- a/src/widget/systemtrayicon_private.h +++ b/src/widget/systemtrayicon_private.h @@ -3,6 +3,21 @@ #include +#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND +#ifdef signals +#undef signals +#endif +extern "C" { + #include + #include + #include + #include + #include + #include "src/platform/statusnotifier/statusnotifier.h" +} +#define signals public +#endif + #ifdef ENABLE_SYSTRAY_UNITY_BACKEND #ifdef signals #undef signals @@ -20,7 +35,10 @@ enum class SystrayBackendType Qt, KDE5, #ifdef ENABLE_SYSTRAY_UNITY_BACKEND - Unity + Unity, +#endif +#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND + StatusNotifier, #endif };