From 7d39b2e82f46777efa67224f078c1cec9e827654 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Thu, 19 Jan 2017 18:46:10 +0100 Subject: Add StatusNotifier support to the Notification Area applet The StatusNotifier part of the implementation is based off gnome-panel's status-notifier applet. --- applets/notification_area/Makefile.am | 43 +- applets/notification_area/fixedtip.c | 281 ---- applets/notification_area/fixedtip.h | 72 -- .../libstatus-notifier-watcher/Makefile.am | 45 + .../libstatus-notifier-watcher/README | 2 + .../libstatus-notifier-watcher/gf-sn-watcher-v0.c | 395 ++++++ .../libstatus-notifier-watcher/gf-sn-watcher-v0.h | 33 + .../gf-status-notifier-watcher.c | 64 + .../gf-status-notifier-watcher.h | 33 + .../org.kde.StatusNotifierWatcher.xml | 42 + applets/notification_area/main.c | 166 ++- applets/notification_area/na-box.c | 366 ++++++ applets/notification_area/na-box.h | 56 + applets/notification_area/na-host.c | 105 ++ applets/notification_area/na-host.h | 48 + applets/notification_area/na-item.c | 91 ++ applets/notification_area/na-item.h | 64 + applets/notification_area/na-marshal.list | 3 - applets/notification_area/na-tray-child.c | 538 -------- applets/notification_area/na-tray-child.h | 76 -- applets/notification_area/na-tray-manager.c | 988 -------------- applets/notification_area/na-tray-manager.h | 113 -- applets/notification_area/na-tray.c | 856 ------------- applets/notification_area/na-tray.h | 76 -- .../notification_area/status-notifier/Makefile.am | 83 ++ applets/notification_area/status-notifier/README | 2 + .../status-notifier/com.canonical.dbusmenu.xml | 74 ++ .../status-notifier/org.kde.StatusNotifierHost.xml | 8 + .../status-notifier/org.kde.StatusNotifierItem.xml | 80 ++ .../org.kde.StatusNotifierWatcher.xml | 42 + .../status-notifier/sn-dbus-menu-item.c | 472 +++++++ .../status-notifier/sn-dbus-menu-item.h | 64 + .../status-notifier/sn-dbus-menu.c | 472 +++++++ .../status-notifier/sn-dbus-menu.h | 33 + .../notification_area/status-notifier/sn-host-v0.c | 487 +++++++ .../notification_area/status-notifier/sn-host-v0.h | 33 + .../status-notifier/sn-image-menu-item.c | 216 ++++ .../status-notifier/sn-image-menu-item.h | 41 + .../notification_area/status-notifier/sn-item-v0.c | 1355 ++++++++++++++++++++ .../notification_area/status-notifier/sn-item-v0.h | 37 + .../notification_area/status-notifier/sn-item.c | 491 +++++++ .../notification_area/status-notifier/sn-item.h | 70 + applets/notification_area/system-tray/Makefile.am | 42 + applets/notification_area/system-tray/fixedtip.c | 281 ++++ applets/notification_area/system-tray/fixedtip.h | 72 ++ .../notification_area/system-tray/na-marshal.list | 3 + .../notification_area/system-tray/na-tray-child.c | 715 +++++++++++ .../notification_area/system-tray/na-tray-child.h | 78 ++ .../system-tray/na-tray-manager.c | 988 ++++++++++++++ .../system-tray/na-tray-manager.h | 113 ++ applets/notification_area/system-tray/na-tray.c | 730 +++++++++++ applets/notification_area/system-tray/na-tray.h | 69 + applets/notification_area/testtray.c | 88 +- 53 files changed, 8687 insertions(+), 3108 deletions(-) delete mode 100644 applets/notification_area/fixedtip.c delete mode 100644 applets/notification_area/fixedtip.h create mode 100644 applets/notification_area/libstatus-notifier-watcher/Makefile.am create mode 100644 applets/notification_area/libstatus-notifier-watcher/README create mode 100644 applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c create mode 100644 applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h create mode 100644 applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c create mode 100644 applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h create mode 100644 applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml create mode 100644 applets/notification_area/na-box.c create mode 100644 applets/notification_area/na-box.h create mode 100644 applets/notification_area/na-host.c create mode 100644 applets/notification_area/na-host.h create mode 100644 applets/notification_area/na-item.c create mode 100644 applets/notification_area/na-item.h delete mode 100644 applets/notification_area/na-marshal.list delete mode 100644 applets/notification_area/na-tray-child.c delete mode 100644 applets/notification_area/na-tray-child.h delete mode 100644 applets/notification_area/na-tray-manager.c delete mode 100644 applets/notification_area/na-tray-manager.h delete mode 100644 applets/notification_area/na-tray.c delete mode 100644 applets/notification_area/na-tray.h create mode 100644 applets/notification_area/status-notifier/Makefile.am create mode 100644 applets/notification_area/status-notifier/README create mode 100644 applets/notification_area/status-notifier/com.canonical.dbusmenu.xml create mode 100644 applets/notification_area/status-notifier/org.kde.StatusNotifierHost.xml create mode 100644 applets/notification_area/status-notifier/org.kde.StatusNotifierItem.xml create mode 100644 applets/notification_area/status-notifier/org.kde.StatusNotifierWatcher.xml create mode 100644 applets/notification_area/status-notifier/sn-dbus-menu-item.c create mode 100644 applets/notification_area/status-notifier/sn-dbus-menu-item.h create mode 100644 applets/notification_area/status-notifier/sn-dbus-menu.c create mode 100644 applets/notification_area/status-notifier/sn-dbus-menu.h create mode 100644 applets/notification_area/status-notifier/sn-host-v0.c create mode 100644 applets/notification_area/status-notifier/sn-host-v0.h create mode 100644 applets/notification_area/status-notifier/sn-image-menu-item.c create mode 100644 applets/notification_area/status-notifier/sn-image-menu-item.h create mode 100644 applets/notification_area/status-notifier/sn-item-v0.c create mode 100644 applets/notification_area/status-notifier/sn-item-v0.h create mode 100644 applets/notification_area/status-notifier/sn-item.c create mode 100644 applets/notification_area/status-notifier/sn-item.h create mode 100644 applets/notification_area/system-tray/Makefile.am create mode 100644 applets/notification_area/system-tray/fixedtip.c create mode 100644 applets/notification_area/system-tray/fixedtip.h create mode 100644 applets/notification_area/system-tray/na-marshal.list create mode 100644 applets/notification_area/system-tray/na-tray-child.c create mode 100644 applets/notification_area/system-tray/na-tray-child.h create mode 100644 applets/notification_area/system-tray/na-tray-manager.c create mode 100644 applets/notification_area/system-tray/na-tray-manager.h create mode 100644 applets/notification_area/system-tray/na-tray.c create mode 100644 applets/notification_area/system-tray/na-tray.h (limited to 'applets/notification_area') diff --git a/applets/notification_area/Makefile.am b/applets/notification_area/Makefile.am index cbd9d0b6..4e7272e5 100644 --- a/applets/notification_area/Makefile.am +++ b/applets/notification_area/Makefile.am @@ -1,3 +1,8 @@ +SUBDIRS = \ + libstatus-notifier-watcher \ + status-notifier \ + system-tray + noinst_LTLIBRARIES = libtray.la noinst_PROGRAMS = testtray @@ -10,28 +15,29 @@ AM_CPPFLAGS = \ -DMATELOCALEDIR=\""$(datadir)/locale"\" \ -DG_LOG_DOMAIN=\""notification-area-applet"\" \ -DNOTIFICATION_AREA_MENU_UI_DIR=\""$(uidir)"\" \ + -DPROVIDE_WATCHER_SERVICE=1 \ $(DISABLE_DEPRECATED_CFLAGS) AM_CFLAGS = $(WARN_CFLAGS) -libtray_la_SOURCES = \ - fixedtip.h \ - fixedtip.c \ - na-marshal.c \ - na-marshal.h \ - na-tray.c \ - na-tray.h \ - na-tray-child.c \ - na-tray-child.h \ - na-tray-manager.c \ - na-tray-manager.h +libtray_la_SOURCES = \ + na-box.c \ + na-box.h \ + na-host.c \ + na-host.h \ + na-item.c \ + na-item.h + +libtray_la_LIBADD = \ + libstatus-notifier-watcher/libstatus-notifier-watcher.la \ + status-notifier/libstatus-notifier.la \ + system-tray/libsystem-tray.la NOTIFICATION_AREA_SOURCES = main.c main.h NOTIFICATION_AREA_LDADD = \ ../../libmate-panel-applet/libmate-panel-applet-4.la \ libtray.la \ - $(X_LIBS) \ $(NOTIFICATION_AREA_LIBS) \ $(LIBMATE_PANEL_APPLET_LIBS) @@ -39,7 +45,6 @@ NOTIFICATION_AREA_LDADD = \ testtray_SOURCES = testtray.c testtray_LDADD = \ libtray.la \ - $(X_LIBS) \ $(NOTIFICATION_AREA_LIBS) if NOTIFICATION_AREA_INPROCESS @@ -62,15 +67,6 @@ notification_area_applet_LDADD = $(NOTIFICATION_AREA_LDADD) notification_area_applet_CFLAGS = $(AM_CFLAGS) endif -na-marshal.h: na-marshal.list $(GLIB_GENMARSHAL) - $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=_na_marshal > $@ - -na-marshal.c: na-marshal.list $(GLIB_GENMARSHAL) - $(AM_V_GEN)echo "#include \"na-marshal.h\"" > $@ && \ - $(GLIB_GENMARSHAL) $< --body --prefix=_na_marshal >> $@ - -BUILT_SOURCES = na-marshal.c na-marshal.h - appletdir = $(datadir)/mate-panel/applets applet_in_files = org.mate.panel.NotificationAreaApplet.mate-panel-applet.in applet_DATA = $(applet_in_files:.mate-panel-applet.in=.mate-panel-applet) @@ -101,8 +97,7 @@ ui_DATA = notification-area-menu.xml EXTRA_DIST = \ org.mate.panel.NotificationAreaApplet.mate-panel-applet.in.in \ $(ui_DATA) \ - $(service_in_files) \ - na-marshal.list + $(service_in_files) CLEANFILES = \ $(applet_DATA) \ diff --git a/applets/notification_area/fixedtip.c b/applets/notification_area/fixedtip.c deleted file mode 100644 index 6356de9b..00000000 --- a/applets/notification_area/fixedtip.c +++ /dev/null @@ -1,281 +0,0 @@ -/* Marco fixed tooltip routine */ - -/* - * Copyright (C) 2001 Havoc Pennington - * Copyright (C) 2003-2006 Vincent Untz - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#include "fixedtip.h" - -/* Signals */ -enum -{ - CLICKED, - LAST_SIGNAL -}; - -static guint fixedtip_signals[LAST_SIGNAL] = { 0 }; - -struct _NaFixedTipPrivate -{ - GtkWidget *parent; - GtkWidget *label; - GtkOrientation orientation; -}; - -G_DEFINE_TYPE (NaFixedTip, na_fixed_tip, GTK_TYPE_WINDOW) - -static gboolean -button_press_handler (GtkWidget *fixedtip, - GdkEventButton *event, - gpointer data) -{ - if (event->button == 1 && event->type == GDK_BUTTON_PRESS) - g_signal_emit (fixedtip, fixedtip_signals[CLICKED], 0); - - return FALSE; -} - -static gboolean -na_fixed_tip_draw (GtkWidget *widget, cairo_t *cr) -{ - GtkStyleContext *context; - GtkStateFlags state; - int width, height; - - width = gtk_widget_get_allocated_width (widget); - height = gtk_widget_get_allocated_height (widget); - - state = gtk_widget_get_state_flags (widget); - context = gtk_widget_get_style_context (widget); - gtk_style_context_save (context); - gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOOLTIP); - gtk_style_context_set_state (context, state); - - cairo_save (cr); - gtk_render_background (context, cr, - 0., 0., - (gdouble)width, - (gdouble)height); - cairo_restore (cr); - - gtk_style_context_restore (context); - - return FALSE; -} - -static void -na_fixed_tip_class_init (NaFixedTipClass *class) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); - widget_class->draw = na_fixed_tip_draw; - - fixedtip_signals[CLICKED] = - g_signal_new ("clicked", - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaFixedTipClass, clicked), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - - g_type_class_add_private (class, sizeof (NaFixedTipPrivate)); -} - -/* Did you already see this code? Yes, it's gtk_tooltips_ force_window() ;-) */ -static void -na_fixed_tip_init (NaFixedTip *fixedtip) -{ - GtkWidget *label; - - fixedtip->priv = G_TYPE_INSTANCE_GET_PRIVATE (fixedtip, NA_TYPE_FIXED_TIP, - NaFixedTipPrivate); - - gtk_window_set_type_hint (GTK_WINDOW (fixedtip), - GDK_WINDOW_TYPE_HINT_TOOLTIP); - - gtk_widget_set_app_paintable (GTK_WIDGET (fixedtip), TRUE); - gtk_window_set_resizable (GTK_WINDOW (fixedtip), FALSE); - gtk_widget_set_name (GTK_WIDGET (fixedtip), "gtk-tooltips"); - gtk_container_set_border_width (GTK_CONTAINER (fixedtip), 4); - - label = gtk_label_new (NULL); - gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); -#if GTK_CHECK_VERSION (3, 16, 0) - gtk_label_set_xalign (GTK_LABEL (label), 0.5); - gtk_label_set_yalign (GTK_LABEL (label), 0.5); -#else - gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); -#endif - gtk_widget_show (label); - gtk_container_add (GTK_CONTAINER (fixedtip), label); - fixedtip->priv->label = label; - - gtk_widget_add_events (GTK_WIDGET (fixedtip), GDK_BUTTON_PRESS_MASK); - - g_signal_connect (fixedtip, "button_press_event", - G_CALLBACK (button_press_handler), NULL); - - fixedtip->priv->orientation = GTK_ORIENTATION_HORIZONTAL; -} - -static void -na_fixed_tip_position (NaFixedTip *fixedtip) -{ - GdkScreen *screen; - GdkWindow *parent_window; - GtkRequisition req; - int root_x; - int root_y; - int parent_width; - int parent_height; - int screen_width; - int screen_height; - - screen = gtk_widget_get_screen (fixedtip->priv->parent); - parent_window = gtk_widget_get_window (fixedtip->priv->parent); - - gtk_window_set_screen (GTK_WINDOW (fixedtip), screen); - - gtk_widget_get_preferred_size (GTK_WIDGET (fixedtip), &req, NULL); - - gdk_window_get_origin (parent_window, &root_x, &root_y); - parent_width = gdk_window_get_width(parent_window); - parent_height = gdk_window_get_height(parent_window); - - screen_width = gdk_screen_get_width (screen); - screen_height = gdk_screen_get_height (screen); - - /* pad between panel and message window */ -#define PAD 5 - - if (fixedtip->priv->orientation == GTK_ORIENTATION_VERTICAL) - { - if (root_x <= screen_width / 2) - root_x += parent_width + PAD; - else - root_x -= req.width + PAD; - } - else - { - if (root_y <= screen_height / 2) - root_y += parent_height + PAD; - else - root_y -= req.height + PAD; - } - - /* Push onscreen */ - if ((root_x + req.width) > screen_width) - root_x = screen_width - req.width; - - if ((root_y + req.height) > screen_height) - root_y = screen_height - req.height; - - gtk_window_move (GTK_WINDOW (fixedtip), root_x, root_y); -} - -static void -na_fixed_tip_parent_size_allocated (GtkWidget *parent, - GtkAllocation *allocation, - NaFixedTip *fixedtip) -{ - na_fixed_tip_position (fixedtip); -} - -static void -na_fixed_tip_parent_screen_changed (GtkWidget *parent, - GdkScreen *new_screen, - NaFixedTip *fixedtip) -{ - na_fixed_tip_position (fixedtip); -} - -GtkWidget * -na_fixed_tip_new (GtkWidget *parent, - GtkOrientation orientation) -{ - NaFixedTip *fixedtip; - - g_return_val_if_fail (parent != NULL, NULL); - - fixedtip = g_object_new (NA_TYPE_FIXED_TIP, - "type", GTK_WINDOW_POPUP, - NULL); - - fixedtip->priv->parent = parent; - -#if 0 - //FIXME: would be nice to be able to get the toplevel for the tip, but this - //doesn't work - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (parent); - /* - if (toplevel && gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel)) - gtk_window_set_transient_for (GTK_WINDOW (fixedtip), GTK_WINDOW (toplevel)); - */ -#endif - - fixedtip->priv->orientation = orientation; - - //FIXME: would be nice to move the tip when the notification area moves - g_signal_connect_object (parent, "size-allocate", - G_CALLBACK (na_fixed_tip_parent_size_allocated), - fixedtip, 0); - g_signal_connect_object (parent, "screen-changed", - G_CALLBACK (na_fixed_tip_parent_screen_changed), - fixedtip, 0); - - na_fixed_tip_position (fixedtip); - - return GTK_WIDGET (fixedtip); -} - -void -na_fixed_tip_set_markup (GtkWidget *widget, - const char *markup_text) -{ - NaFixedTip *fixedtip; - - g_return_if_fail (NA_IS_FIXED_TIP (widget)); - - fixedtip = NA_FIXED_TIP (widget); - - gtk_label_set_markup (GTK_LABEL (fixedtip->priv->label), - markup_text); - - na_fixed_tip_position (fixedtip); -} - -void -na_fixed_tip_set_orientation (GtkWidget *widget, - GtkOrientation orientation) -{ - NaFixedTip *fixedtip; - - g_return_if_fail (NA_IS_FIXED_TIP (widget)); - - fixedtip = NA_FIXED_TIP (widget); - - if (orientation == fixedtip->priv->orientation) - return; - - fixedtip->priv->orientation = orientation; - - na_fixed_tip_position (fixedtip); -} diff --git a/applets/notification_area/fixedtip.h b/applets/notification_area/fixedtip.h deleted file mode 100644 index c220f103..00000000 --- a/applets/notification_area/fixedtip.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Fixed tooltip routine */ - -/* - * Copyright (C) 2001 Havoc Pennington, 2002 Red Hat Inc. - * Copyright (C) 2003-2006 Vincent Untz - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#ifndef FIXED_TIP_H -#define FIXED_TIP_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define NA_TYPE_FIXED_TIP (na_fixed_tip_get_type ()) -#define NA_FIXED_TIP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_FIXED_TIP, NaFixedTip)) -#define NA_FIXED_TIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_FIXED_TIP, NaFixedTipClass)) -#define NA_IS_FIXED_TIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_FIXED_TIP)) -#define NA_IS_FIXED_TIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_FIXED_TIP)) -#define NA_FIXED_TIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_FIXED_TIP, NaFixedTipClass)) - -typedef struct _NaFixedTip NaFixedTip; -typedef struct _NaFixedTipPrivate NaFixedTipPrivate; -typedef struct _NaFixedTipClass NaFixedTipClass; - -struct _NaFixedTip -{ - GtkWindow parent_instance; - - NaFixedTipPrivate *priv; -}; - -struct _NaFixedTipClass -{ - GtkWindowClass parent_class; - - void (* clicked) (NaFixedTip *fixedtip); -}; - -GType na_fixed_tip_get_type (void); - -GtkWidget *na_fixed_tip_new (GtkWidget *parent, - GtkOrientation orientation); - -void na_fixed_tip_set_markup (GtkWidget *widget, - const char *markup_text); - -void na_fixed_tip_set_orientation (GtkWidget *widget, - GtkOrientation orientation); - -#ifdef __cplusplus -} -#endif - -#endif /* FIXED_TIP_H */ diff --git a/applets/notification_area/libstatus-notifier-watcher/Makefile.am b/applets/notification_area/libstatus-notifier-watcher/Makefile.am new file mode 100644 index 00000000..eb06ce83 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/Makefile.am @@ -0,0 +1,45 @@ +NULL = + +noinst_LTLIBRARIES = \ + libstatus-notifier-watcher.la \ + $(NULL) + +AM_CPPFLAGS = \ + $(NOTIFICATION_AREA_CFLAGS) \ + -DG_LOG_DOMAIN=\"status-notifier-watcher\" \ + $(DISABLE_DEPRECATED_CFLAGS) + +AM_CFLAGS = $(WARN_CFLAGS) + +libstatus_notifier_watcher_la_SOURCES = \ + gf-sn-watcher-v0.c \ + gf-sn-watcher-v0.h \ + gf-status-notifier-watcher.c \ + gf-status-notifier-watcher.h \ + $(BUILT_SOURCES) \ + $(NULL) + +libstatus_notifier_watcher_la_LIBADD = \ + $(NOTIFICATION_AREA_LIBS) \ + $(NULL) + +gf-sn-watcher-v0-gen.h: +gf-sn-watcher-v0-gen.c: org.kde.StatusNotifierWatcher.xml + $(AM_V_GEN) $(GDBUS_CODEGEN) --c-namespace Gf \ + --generate-c-code gf-sn-watcher-v0-gen \ + $(srcdir)/org.kde.StatusNotifierWatcher.xml + +BUILT_SOURCES = \ + gf-sn-watcher-v0-gen.c \ + gf-sn-watcher-v0-gen.h \ + $(NULL) + +EXTRA_DIST = \ + org.kde.StatusNotifierWatcher.xml \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/applets/notification_area/libstatus-notifier-watcher/README b/applets/notification_area/libstatus-notifier-watcher/README new file mode 100644 index 00000000..706b2a82 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/README @@ -0,0 +1,2 @@ +Borrowed from gnome-flashback: +https://git.gnome.org/browse/gnome-flashback/tree/gnome-flashback/libstatus-notifier-watcher diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c new file mode 100644 index 00000000..8e82c10f --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.c @@ -0,0 +1,395 @@ +/* + * 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 3 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 . + */ + +#include "config.h" + +#include "gf-sn-watcher-v0.h" + +struct _GfSnWatcherV0 +{ + GfSnWatcherV0GenSkeleton parent; + + guint bus_name_id; + + GSList *hosts; + GSList *items; +}; + +typedef enum +{ + GF_WATCH_TYPE_HOST, + GF_WATCH_TYPE_ITEM +} GfWatchType; + +typedef struct +{ + GfSnWatcherV0 *v0; + GfWatchType type; + + gchar *service; + gchar *bus_name; + gchar *object_path; + guint watch_id; +} GfWatch; + +static void gf_sn_watcher_v0_gen_init (GfSnWatcherV0GenIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GfSnWatcherV0, gf_sn_watcher_v0, GF_TYPE_SN_WATCHER_V0_GEN_SKELETON, + G_IMPLEMENT_INTERFACE (GF_TYPE_SN_WATCHER_V0_GEN, gf_sn_watcher_v0_gen_init)) + +static void +update_registered_items (GfSnWatcherV0 *v0) +{ + GVariantBuilder builder; + GSList *l; + GVariant *variant; + const gchar **items; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + for (l = v0->items; l != NULL; l = g_slist_next (l)) + { + GfWatch *watch; + gchar *item; + + watch = (GfWatch *) l->data; + + item = g_strdup_printf ("%s%s", watch->bus_name, watch->object_path); + g_variant_builder_add (&builder, "s", item); + g_free (item); + } + + variant = g_variant_builder_end (&builder); + items = g_variant_get_strv (variant, NULL); + + gf_sn_watcher_v0_gen_set_registered_items (GF_SN_WATCHER_V0_GEN (v0), items); + g_variant_unref (variant); +} + +static void +gf_watch_free (gpointer data) +{ + GfWatch *watch; + + watch = (GfWatch *) data; + + if (watch->watch_id > 0) + g_bus_unwatch_name (watch->watch_id); + + g_free (watch->service); + g_free (watch->bus_name); + g_free (watch->object_path); + + g_free (watch); +} + +static void +name_vanished_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + GfWatch *watch; + GfSnWatcherV0 *v0; + GfSnWatcherV0Gen *gen; + + watch = (GfWatch *) user_data; + v0 = watch->v0; + gen = GF_SN_WATCHER_V0_GEN (v0); + + if (watch->type == GF_WATCH_TYPE_HOST) + { + v0->hosts = g_slist_remove (v0->hosts, watch); + + if (v0->hosts == NULL) + { + gf_sn_watcher_v0_gen_set_is_host_registered (gen, FALSE); + gf_sn_watcher_v0_gen_emit_host_registered (gen); + } + } + else if (watch->type == GF_WATCH_TYPE_ITEM) + { + gchar *tmp; + + v0->items = g_slist_remove (v0->items, watch); + + update_registered_items (v0); + + tmp = g_strdup_printf ("%s%s", watch->bus_name, watch->object_path); + gf_sn_watcher_v0_gen_emit_item_unregistered (gen, tmp); + g_free (tmp); + } + else + { + g_assert_not_reached (); + } + + gf_watch_free (watch); +} + +static GfWatch * +gf_watch_new (GfSnWatcherV0 *v0, + GfWatchType type, + const gchar *service, + const gchar *bus_name, + const gchar *object_path) +{ + GfWatch *watch; + + watch = g_new0 (GfWatch, 1); + + watch->v0 = v0; + watch->type = type; + + watch->service = g_strdup (service); + watch->bus_name = g_strdup (bus_name); + watch->object_path = g_strdup (object_path); + watch->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, bus_name, + G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, + name_vanished_cb, watch, NULL); + + return watch; +} + +static GfWatch * +gf_watch_find (GSList *list, + const gchar *bus_name, + const gchar *object_path) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next (l)) + { + GfWatch *watch; + + watch = (GfWatch *) l->data; + + if (g_strcmp0 (watch->bus_name, bus_name) == 0 && + g_strcmp0 (watch->object_path, object_path) == 0) + { + return watch; + } + } + + return NULL; +} + +static gboolean +gf_sn_watcher_v0_handle_register_host (GfSnWatcherV0Gen *object, + GDBusMethodInvocation *invocation, + const gchar *service) +{ + GfSnWatcherV0 *v0; + const gchar *bus_name; + const gchar *object_path; + GfWatch *watch; + + v0 = GF_SN_WATCHER_V0 (object); + + if (*service == '/') + { + bus_name = g_dbus_method_invocation_get_sender (invocation); + object_path = service; + } + else + { + bus_name = service; + object_path = "/StatusNotifierHost"; + } + + if (g_dbus_is_name (bus_name) == FALSE) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "D-Bus bus name '%s' is not valid", + bus_name); + + return TRUE; + } + + watch = gf_watch_find (v0->hosts, bus_name, object_path); + + if (watch != NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Status Notifier Host with bus name '%s' and object path '%s' is already registered", + bus_name, object_path); + + return TRUE; + } + + watch = gf_watch_new (v0, GF_WATCH_TYPE_HOST, service, bus_name, object_path); + v0->hosts = g_slist_prepend (v0->hosts, watch); + + if (!gf_sn_watcher_v0_gen_get_is_host_registered (object)) + { + gf_sn_watcher_v0_gen_set_is_host_registered (object, TRUE); + gf_sn_watcher_v0_gen_emit_host_registered (object); + } + + gf_sn_watcher_v0_gen_complete_register_host (object, invocation); + + return TRUE; +} + +static gboolean +gf_sn_watcher_v0_handle_register_item (GfSnWatcherV0Gen *object, + GDBusMethodInvocation *invocation, + const gchar *service) +{ + GfSnWatcherV0 *v0; + const gchar *bus_name; + const gchar *object_path; + GfWatch *watch; + gchar *tmp; + + v0 = GF_SN_WATCHER_V0 (object); + + if (*service == '/') + { + bus_name = g_dbus_method_invocation_get_sender (invocation); + object_path = service; + } + else + { + bus_name = service; + object_path = "/StatusNotifierItem"; + } + + if (g_dbus_is_name (bus_name) == FALSE) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "D-Bus bus name '%s' is not valid", + bus_name); + + return TRUE; + } + + watch = gf_watch_find (v0->items, bus_name, object_path); + + if (watch != NULL) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "Status Notifier Item with bus name '%s' and object path '%s' is already registered", + bus_name, object_path); + + return TRUE; + } + + watch = gf_watch_new (v0, GF_WATCH_TYPE_ITEM, service, bus_name, object_path); + v0->items = g_slist_prepend (v0->items, watch); + + update_registered_items (v0); + + tmp = g_strdup_printf ("%s%s", bus_name, object_path); + gf_sn_watcher_v0_gen_emit_item_registered (object, tmp); + g_free (tmp); + + gf_sn_watcher_v0_gen_complete_register_item (object, invocation); + + return TRUE; +} + +static void +gf_sn_watcher_v0_gen_init (GfSnWatcherV0GenIface *iface) +{ + iface->handle_register_host = gf_sn_watcher_v0_handle_register_host; + iface->handle_register_item = gf_sn_watcher_v0_handle_register_item; +} + +static void +bus_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GfSnWatcherV0 *v0; + GDBusInterfaceSkeleton *skeleton; + GError *error; + + v0 = GF_SN_WATCHER_V0 (user_data); + skeleton = G_DBUS_INTERFACE_SKELETON (v0); + + error = NULL; + g_dbus_interface_skeleton_export (skeleton, connection, + "/StatusNotifierWatcher", &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + return; + } +} + +static void +gf_sn_watcher_v0_dispose (GObject *object) +{ + GfSnWatcherV0 *v0; + + v0 = GF_SN_WATCHER_V0 (object); + + if (v0->bus_name_id > 0) + { + g_bus_unown_name (v0->bus_name_id); + v0->bus_name_id = 0; + } + + if (v0->hosts != NULL) + { + g_slist_free_full (v0->hosts, gf_watch_free); + v0->hosts = NULL; + } + + if (v0->items != NULL) + { + g_slist_free_full (v0->items, gf_watch_free); + v0->items = NULL; + } + + G_OBJECT_CLASS (gf_sn_watcher_v0_parent_class)->dispose (object); +} + +static void +gf_sn_watcher_v0_class_init (GfSnWatcherV0Class *v0_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (v0_class); + + object_class->dispose = gf_sn_watcher_v0_dispose; +} + +static void +gf_sn_watcher_v0_init (GfSnWatcherV0 *v0) +{ + GBusNameOwnerFlags flags; + + flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE; + + v0->bus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.kde.StatusNotifierWatcher", flags, + bus_acquired_cb, NULL, NULL, v0, NULL); +} + +GfSnWatcherV0 * +gf_sn_watcher_v0_new (void) +{ + return g_object_new (GF_TYPE_SN_WATCHER_V0, NULL); +} diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-v0.h new file mode 100644 index 00000000..73bf6d04 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/gf-sn-watcher-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 3 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 . + */ + +#ifndef GF_SN_WATCHER_V0_H +#define GF_SN_WATCHER_V0_H + +#include "gf-sn-watcher-v0-gen.h" + +G_BEGIN_DECLS + +#define GF_TYPE_SN_WATCHER_V0 gf_sn_watcher_v0_get_type () +G_DECLARE_FINAL_TYPE (GfSnWatcherV0, gf_sn_watcher_v0, + GF, SN_WATCHER_V0, GfSnWatcherV0GenSkeleton) + +GfSnWatcherV0 *gf_sn_watcher_v0_new (void); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c new file mode 100644 index 00000000..e8b2c654 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.c @@ -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 3 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 . + */ + +#include "config.h" + +#include "gf-sn-watcher-v0.h" +#include "gf-status-notifier-watcher.h" + +struct _GfStatusNotifierWatcher +{ + GObject parent; + + GfSnWatcherV0 *v0; +}; + +G_DEFINE_TYPE (GfStatusNotifierWatcher, gf_status_notifier_watcher, G_TYPE_OBJECT) + +static void +gf_status_notifier_watcher_dispose (GObject *object) +{ + GfStatusNotifierWatcher *watcher; + + watcher = GF_STATUS_NOTIFIER_WATCHER (object); + + g_clear_object (&watcher->v0); + + G_OBJECT_CLASS (gf_status_notifier_watcher_parent_class)->dispose (object); +} + +static void +gf_status_notifier_watcher_class_init (GfStatusNotifierWatcherClass *watcher_class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (watcher_class); + + object_class->dispose = gf_status_notifier_watcher_dispose; +} + +static void +gf_status_notifier_watcher_init (GfStatusNotifierWatcher *watcher) +{ + watcher->v0 = gf_sn_watcher_v0_new (); +} + +GfStatusNotifierWatcher * +gf_status_notifier_watcher_new (void) +{ + return g_object_new (GF_TYPE_STATUS_NOTIFIER_WATCHER, NULL); +} diff --git a/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.h new file mode 100644 index 00000000..fd871a42 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/gf-status-notifier-watcher.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 3 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 . + */ + +#ifndef GF_STATUS_NOTIFIER_WATCHER_H +#define GF_STATUS_NOTIFIER_WATCHER_H + +#include + +G_BEGIN_DECLS + +#define GF_TYPE_STATUS_NOTIFIER_WATCHER gf_status_notifier_watcher_get_type () +G_DECLARE_FINAL_TYPE (GfStatusNotifierWatcher, gf_status_notifier_watcher, + GF, STATUS_NOTIFIER_WATCHER, GObject) + +GfStatusNotifierWatcher *gf_status_notifier_watcher_new (void); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml b/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml new file mode 100644 index 00000000..30d3f774 --- /dev/null +++ b/applets/notification_area/libstatus-notifier-watcher/org.kde.StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/notification_area/main.c b/applets/notification_area/main.c index 54080a20..f4de7d01 100644 --- a/applets/notification_area/main.c +++ b/applets/notification_area/main.c @@ -30,26 +30,54 @@ #include #include "main.h" -#include "na-tray-manager.h" -#include "na-tray.h" -#include "fixedtip.h" +#include "na-box.h" + +#ifdef PROVIDE_WATCHER_SERVICE +# include "libstatus-notifier-watcher/gf-status-notifier-watcher.h" +#endif #define NOTIFICATION_AREA_ICON "mate-panel-notification-area" struct _NaTrayAppletPrivate { - NaTray *tray; + GtkWidget *box; + +#ifdef PROVIDE_WATCHER_SERVICE + GfStatusNotifierWatcher *sn_watcher; +#endif }; G_DEFINE_TYPE (NaTrayApplet, na_tray_applet, PANEL_TYPE_APPLET) static void (*parent_class_realize) (GtkWidget *widget); -static void (*parent_class_unrealize) (GtkWidget *widget); static void (*parent_class_style_updated) (GtkWidget *widget); static void (*parent_class_change_background)(MatePanelApplet* panel_applet, MatePanelAppletBackgroundType type, GdkRGBA* color, cairo_pattern_t* pattern); static void (*parent_class_change_orient)(MatePanelApplet *panel_applet, MatePanelAppletOrient orient); +#ifdef PROVIDE_WATCHER_SERVICE +/* Quite dirty way of providing the org.kde.StatusNotifierWatcher service + * ourselves, in case the session doesn't already */ + +static GfStatusNotifierWatcher *sn_watcher_service = NULL; + +static GfStatusNotifierWatcher * +sn_watcher_service_ref (void) +{ + if (sn_watcher_service != NULL) + g_object_ref (sn_watcher_service); + else + { + sn_watcher_service = gf_status_notifier_watcher_new (); + g_object_add_weak_pointer ((GObject *) sn_watcher_service, + (gpointer *) &sn_watcher_service); + } + + return sn_watcher_service; +} +#endif + + static GtkOrientation get_gtk_orientation_from_applet_orient (MatePanelAppletOrient orient) { @@ -116,6 +144,8 @@ static void about_cb(GtkAction* action, NaTrayApplet* applet) "Havoc Pennington ", "Anders Carlsson ", "Vincent Untz ", + "Alberts Muktupāvels", + "Colomban Wendling ", NULL }; @@ -128,7 +158,7 @@ static void about_cb(GtkAction* action, NaTrayApplet* applet) "Copyright \xc2\xa9 2002 Red Hat, Inc.\n" "Copyright \xc2\xa9 2003-2006 Vincent Untz\n" "Copyright \xc2\xa9 2011 Perberos\n" - "Copyright \xc2\xa9 2012-2016 MATE developers"; + "Copyright \xc2\xa9 2012-2017 MATE developers"; gtk_show_about_dialog(NULL, "program-name", _("Notification Area"), @@ -156,21 +186,10 @@ static void na_tray_applet_realize (GtkWidget *widget) { NaTrayApplet *applet = NA_TRAY_APPLET (widget); - MatePanelAppletOrient orient; if (parent_class_realize) parent_class_realize (widget); - g_assert (applet->priv->tray == NULL); - - orient = mate_panel_applet_get_orient (MATE_PANEL_APPLET (widget)); - - applet->priv->tray = na_tray_new_for_screen (gtk_widget_get_screen (widget), - get_gtk_orientation_from_applet_orient (orient)); - - gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (applet->priv->tray)); - gtk_widget_show (GTK_WIDGET (applet->priv->tray)); - GtkActionGroup* action_group; gchar* ui_path; action_group = gtk_action_group_new("NA Applet Menu Actions"); @@ -183,60 +202,36 @@ na_tray_applet_realize (GtkWidget *widget) } static void -na_tray_applet_unrealize (GtkWidget *widget) +na_tray_applet_dispose (GObject *object) { - NaTrayApplet *applet = NA_TRAY_APPLET (widget); - - g_assert (applet->priv->tray != NULL); - - gtk_widget_destroy (GTK_WIDGET (applet->priv->tray)); - applet->priv->tray = NULL; +#ifdef PROVIDE_WATCHER_SERVICE + g_clear_object (&NA_TRAY_APPLET (object)->priv->sn_watcher); +#endif - if (parent_class_unrealize) - parent_class_unrealize (widget); + G_OBJECT_CLASS (na_tray_applet_parent_class)->dispose (object); } static void na_tray_applet_style_updated (GtkWidget *widget) { NaTrayApplet *applet = NA_TRAY_APPLET (widget); - GtkStyleContext *context; - GdkRGBA fg; - GdkRGBA error; - GdkRGBA warning; - GdkRGBA success; gint padding; gint icon_size; if (parent_class_style_updated) parent_class_style_updated (widget); - if (!applet->priv->tray) + if (!applet->priv->box) return; - context = gtk_widget_get_style_context (widget); - - gtk_style_context_save (context); - gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); - - gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg); - - if (!gtk_style_context_lookup_color (context, "error_color", &error)) - error = fg; - if (!gtk_style_context_lookup_color (context, "warning_color", &warning)) - warning = fg; - if (!gtk_style_context_lookup_color (context, "success_color", &success)) - success = fg; - - gtk_style_context_restore (context); - - na_tray_set_colors (applet->priv->tray, &fg, &error, &warning, &success); - - gtk_widget_style_get (widget, "icon-padding", &padding, NULL); - na_tray_set_padding (applet->priv->tray, padding); - - gtk_widget_style_get (widget, "icon-size", &icon_size, NULL); - na_tray_set_icon_size (applet->priv->tray, icon_size); + gtk_widget_style_get (widget, + "icon-padding", &padding, + "icon-size", &icon_size, + NULL); + g_object_set (applet->priv->box, + "icon-padding", padding, + "icon-size", icon_size, + NULL); } static void @@ -248,10 +243,8 @@ na_tray_applet_change_background(MatePanelApplet* panel_applet, MatePanelAppletB parent_class_change_background (panel_applet, type, color, pattern); } - if (!applet->priv->tray) - return; - - na_tray_force_redraw (applet->priv->tray); + if (applet->priv->box) + na_box_force_redraw (NA_BOX (applet->priv->box)); } static void @@ -263,11 +256,39 @@ na_tray_applet_change_orient (MatePanelApplet *panel_applet, if (parent_class_change_orient) parent_class_change_orient (panel_applet, orient); - if (!applet->priv->tray) + if (!applet->priv->box) return; - na_tray_set_orientation (applet->priv->tray, - get_gtk_orientation_from_applet_orient (orient)); + gtk_orientable_set_orientation (GTK_ORIENTABLE (applet->priv->box), + get_gtk_orientation_from_applet_orient (orient)); +} + +static gboolean +na_tray_applet_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + /* Prevent the panel from poping up the applet's popup on the the items, + * which may also popup a menu which then conflicts. + * This doesn't prevent the menu from poping up on the applet handle. */ + if (event->button == 3) + return TRUE; + + return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->button_press_event (widget, event); +} + +static gboolean +na_tray_applet_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + NaTrayApplet *applet = NA_TRAY_APPLET (widget); + + /* We let the box handle the focus movement because we behave more like a + * container than a single applet. But if focus didn't move, we let the + * applet do its thing. */ + if (gtk_widget_child_focus (applet->priv->box, direction)) + return TRUE; + + return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->focus (widget, direction); } #if !GTK_CHECK_VERSION (3, 20, 0) @@ -293,19 +314,23 @@ force_no_focus_padding (GtkWidget *widget) static void na_tray_applet_class_init (NaTrayAppletClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); MatePanelAppletClass *applet_class = MATE_PANEL_APPLET_CLASS (class); + object_class->dispose = na_tray_applet_dispose; + parent_class_realize = widget_class->realize; widget_class->realize = na_tray_applet_realize; - parent_class_unrealize = widget_class->unrealize; - widget_class->unrealize = na_tray_applet_unrealize; parent_class_style_updated = widget_class->style_updated; widget_class->style_updated = na_tray_applet_style_updated; parent_class_change_background = applet_class->change_background; applet_class->change_background = na_tray_applet_change_background; + widget_class->button_press_event = na_tray_applet_button_press_event; + widget_class->focus = na_tray_applet_focus; + parent_class_change_orient = applet_class->change_orient; applet_class->change_orient = na_tray_applet_change_orient; @@ -335,14 +360,21 @@ na_tray_applet_class_init (NaTrayAppletClass *class) static void na_tray_applet_init (NaTrayApplet *applet) { + MatePanelAppletOrient orient; AtkObject *atko; applet->priv = G_TYPE_INSTANCE_GET_PRIVATE (applet, NA_TYPE_TRAY_APPLET, NaTrayAppletPrivate); - /* Defer creating NaTray until applet is added to panel so - * gtk_widget_get_screen returns correct information */ - applet->priv->tray = NULL; +#ifdef PROVIDE_WATCHER_SERVICE + applet->priv->sn_watcher = sn_watcher_service_ref (); +#endif + + orient = mate_panel_applet_get_orient (MATE_PANEL_APPLET (applet)); + applet->priv->box = na_box_new (get_gtk_orientation_from_applet_orient (orient)); + + gtk_container_add (GTK_CONTAINER (applet), GTK_WIDGET (applet->priv->box)); + gtk_widget_show (GTK_WIDGET (applet->priv->box)); atko = gtk_widget_get_accessible (GTK_WIDGET (applet)); atk_object_set_name (atko, _("Panel Notification Area")); diff --git a/applets/notification_area/na-box.c b/applets/notification_area/na-box.c new file mode 100644 index 00000000..25933eea --- /dev/null +++ b/applets/notification_area/na-box.c @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2007 Christian Persch + * Copyright (C) 2017 Colomban Wendling + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* Well, actuall'y is the Tray itself, the container for the items. But + * NaTray is already taken for the XEMBED part, so for now it's called NaBox, + * don't make a big deal out of it. */ + +#include "config.h" + +#include + +#include "na-box.h" + +#include "system-tray/na-tray.h" +#include "status-notifier/sn-host-v0.h" + +#define ICON_SPACING 1 +#define MIN_BOX_SIZE 3 + +struct _NaBox +{ + GtkBox parent; + + gint icon_padding; + gint icon_size; + + GSList *hosts; + GSList *items; +}; + +enum +{ + PROP_0, + PROP_ICON_PADDING, + PROP_ICON_SIZE +}; + +G_DEFINE_TYPE (NaBox, na_box, GTK_TYPE_BOX) + +static gint +compare_items (gconstpointer a, + gconstpointer b) +{ + NaItem *item1; + NaItem *item2; + NaItemCategory c1; + NaItemCategory c2; + const gchar *id1; + const gchar *id2; + + item1 = (NaItem *) a; + item2 = (NaItem *) b; + + c1 = na_item_get_category (item1); + c2 = na_item_get_category (item2); + + if (c1 < c2) + return -1; + else if (c1 > c2) + return 1; + + id1 = na_item_get_id (item1); + id2 = na_item_get_id (item2); + + return g_strcmp0 (id1, id2); +} + +static void +reorder_items (GtkWidget *widget, + gpointer user_data) +{ + NaBox *nb; + gint position; + + nb = NA_BOX (user_data); + + position = g_slist_index (nb->items, widget); + gtk_box_reorder_child (GTK_BOX (nb), widget, position); +} + +static void +item_added_cb (NaHost *host, + NaItem *item, + NaBox *self) +{ + g_return_if_fail (NA_IS_HOST (host)); + g_return_if_fail (NA_IS_ITEM (item)); + g_return_if_fail (NA_IS_BOX (self)); + + self->items = g_slist_prepend (self->items, item); + gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (item), FALSE, FALSE, 0); + + self->items = g_slist_sort (self->items, compare_items); + gtk_container_foreach (GTK_CONTAINER (self), reorder_items, self); + + g_object_bind_property (self, "orientation", + item, "orientation", + G_BINDING_DEFAULT); +} + +static void +item_removed_cb (NaHost *host, + NaItem *item, + NaBox *self) +{ + g_return_if_fail (NA_IS_HOST (host)); + g_return_if_fail (NA_IS_ITEM (item)); + g_return_if_fail (NA_IS_BOX (self)); + + gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (item)); + self->items = g_slist_remove (self->items, item); +} + +static void +update_size_and_orientation (NaBox *self, + GtkOrientation orientation) +{ + /* FIXME: do we really need that? comes from NaTray */ + /* FIXME: if we do, do that in overridden preferred size handlers */ + + /* note, you want this larger if the frame has non-NONE relief by default. */ + switch (orientation) + { + case GTK_ORIENTATION_VERTICAL: + /* Give box a min size so the frame doesn't look dumb */ + gtk_widget_set_size_request (GTK_WIDGET (self), MIN_BOX_SIZE, -1); + break; + case GTK_ORIENTATION_HORIZONTAL: + gtk_widget_set_size_request (GTK_WIDGET (self), -1, MIN_BOX_SIZE); + break; + } +} + +static void +orientation_notify (GObject *object, + GParamSpec *pspec, + gpointer data) +{ + update_size_and_orientation (NA_BOX (object), + gtk_orientable_get_orientation (GTK_ORIENTABLE (object))); +} + +static void +na_box_init (NaBox *self) +{ + GtkOrientation orientation; + + self->icon_padding = 0; + self->icon_size = 0; + + self->hosts = NULL; + self->items = NULL; + + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); + update_size_and_orientation (self, orientation); + + g_signal_connect (self, "notify::orientation", G_CALLBACK (orientation_notify), NULL); +} + +static void +add_host (NaBox *self, + NaHost *host) +{ + self->hosts = g_slist_prepend (self->hosts, host); + + g_object_bind_property (self, "icon-padding", host, "icon-padding", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + g_object_bind_property (self, "icon-size", host, "icon-size", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + g_signal_connect_object (host, "item-added", + G_CALLBACK (item_added_cb), self, 0); + g_signal_connect_object (host, "item-removed", + G_CALLBACK (item_removed_cb), self, 0); +} + +static void +na_box_style_updated (GtkWidget *widget) +{ + NaBox *self = NA_BOX (widget); + GtkStyleContext *context; + GSList *node; + + if (GTK_WIDGET_CLASS (na_box_parent_class)->style_updated) + GTK_WIDGET_CLASS (na_box_parent_class)->style_updated (widget); + + context = gtk_widget_get_style_context (widget); + + for (node = self->hosts; node; node = node->next) + { + gtk_style_context_save (context); + na_host_style_updated (node->data, context); + gtk_style_context_restore (context); + } +} + +/* Custom drawing because system-tray items need weird stuff. */ +static gboolean +na_box_draw (GtkWidget *box, + cairo_t *cr) +{ + GList *child; + GList *children = gtk_container_get_children (GTK_CONTAINER (box)); + + for (child = children; child; child = child->next) + { + if (! NA_IS_ITEM (child->data) || + ! na_item_draw_on_parent (child->data, box, cr)) + { + if (gtk_widget_is_drawable (child->data) && + gtk_cairo_should_draw_window (cr, gtk_widget_get_window (child->data))) + gtk_container_propagate_draw (GTK_CONTAINER (box), child->data, cr); + } + } + + g_list_free (children); + + return TRUE; +} + +static void +na_box_realize (GtkWidget *widget) +{ + NaBox *self = NA_BOX (widget); + GdkScreen *screen; + GtkOrientation orientation; + + GTK_WIDGET_CLASS (na_box_parent_class)->realize (widget); + + /* Instantiate the hosts now we have a screen */ + screen = gtk_widget_get_screen (GTK_WIDGET (self)); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); + + add_host (self, na_tray_new_for_screen (screen, orientation)); + add_host (self, sn_host_v0_new ()); +} + +static void +na_box_unrealize (GtkWidget *widget) +{ + NaBox *self = NA_BOX (widget); + + if (self->hosts != NULL) + { + g_slist_free_full (self->hosts, g_object_unref); + self->hosts = NULL; + } + + g_clear_pointer (&self->items, g_slist_free); + + GTK_WIDGET_CLASS (na_box_parent_class)->unrealize (widget); +} + +static void +na_box_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NaBox *self = NA_BOX (object); + + switch (property_id) + { + case PROP_ICON_PADDING: + g_value_set_int (value, self->icon_padding); + break; + + case PROP_ICON_SIZE: + g_value_set_int (value, self->icon_size); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +na_box_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NaBox *self = NA_BOX (object); + + switch (property_id) + { + case PROP_ICON_PADDING: + self->icon_padding = g_value_get_int (value); + break; + + case PROP_ICON_SIZE: + self->icon_size = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +na_box_class_init (NaBoxClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->get_property = na_box_get_property; + gobject_class->set_property = na_box_set_property; + + widget_class->draw = na_box_draw; + widget_class->realize = na_box_realize; + widget_class->unrealize = na_box_unrealize; + widget_class->style_updated = na_box_style_updated; + + g_object_class_install_property (gobject_class, PROP_ICON_PADDING, + g_param_spec_int ("icon-padding", + "Padding around icons", + "Padding that should be put around icons, in pixels", + 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_ICON_SIZE, + g_param_spec_int ("icon-size", + "Icon size", + "If non-zero, hardcodes the size of the icons in pixels", + 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +GtkWidget * +na_box_new (GtkOrientation orientation) +{ + return g_object_new (NA_TYPE_BOX, + "orientation", orientation, + "spacing", ICON_SPACING, + NULL); +} + +void +na_box_force_redraw (NaBox *box) +{ + GSList *node; + + for (node = box->hosts; node; node = node->next) + na_host_force_redraw (node->data); +} diff --git a/applets/notification_area/na-box.h b/applets/notification_area/na-box.h new file mode 100644 index 00000000..5e13b488 --- /dev/null +++ b/applets/notification_area/na-box.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* na-tray-tray.h + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2017 Colomban Wendling + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Used to be: eggtraytray.h + */ + +#ifndef NA_BOX_H +#define NA_BOX_H + +#ifdef GDK_WINDOWING_X11 +#include +#endif +#include + +G_BEGIN_DECLS + +#define NA_TYPE_BOX (na_box_get_type ()) +#define NA_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_BOX, NaBox)) +#define NA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_BOX, NaBoxClass)) +#define NA_IS_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_BOX)) +#define NA_IS_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_BOX)) +#define NA_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_BOX, NaBoxClass)) + +typedef struct _NaBox NaBox; +typedef struct _NaBoxClass NaBoxClass; + +struct _NaBoxClass +{ + GtkBoxClass parent_class; +}; + +GType na_box_get_type (void); +GtkWidget *na_box_new (GtkOrientation orientation); +void na_box_force_redraw (NaBox *box); + +G_END_DECLS + +#endif /* __NA_TRAY_H__ */ diff --git a/applets/notification_area/na-host.c b/applets/notification_area/na-host.c new file mode 100644 index 00000000..969a6c70 --- /dev/null +++ b/applets/notification_area/na-host.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * Copyright (C) 2017 Colomban Wendling + * + * 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 . + */ + +#include "config.h" + +#include "na-host.h" +#include "na-item.h" + +enum +{ + SIGNAL_ITEM_ADDED, + SIGNAL_ITEM_REMOVED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_INTERFACE (NaHost, na_host, G_TYPE_OBJECT) + +static void +na_host_default_init (NaHostInterface *iface) +{ + signals[SIGNAL_ITEM_ADDED] = + g_signal_new ("item-added", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, NA_TYPE_ITEM); + + signals[SIGNAL_ITEM_REMOVED] = + g_signal_new ("item-removed", G_TYPE_FROM_INTERFACE (iface), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, NA_TYPE_ITEM); + + g_object_interface_install_property (iface, + g_param_spec_int ("icon-padding", + "Padding around icons", + "Padding that should be put around icons, in pixels", + 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_interface_install_property (iface, + g_param_spec_int ("icon-size", + "Icon size", + "If non-zero, hardcodes the size of the icons in pixels", + 0, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + iface->style_updated = NULL; +} + +void +na_host_force_redraw (NaHost *host) +{ + NaHostInterface *iface; + + g_return_if_fail (NA_IS_HOST (host)); + + iface = NA_HOST_GET_IFACE (host); + + if (iface->force_redraw != NULL) + iface->force_redraw (host); +} + +void +na_host_style_updated (NaHost *host, + GtkStyleContext *context) +{ + NaHostInterface *iface; + + g_return_if_fail (NA_IS_HOST (host)); + + iface = NA_HOST_GET_IFACE (host); + + if (iface->style_updated != NULL) + iface->style_updated (host, context); +} + +void +na_host_emit_item_added (NaHost *host, + NaItem *item) +{ + g_signal_emit (host, signals[SIGNAL_ITEM_ADDED], 0, item); +} + +void +na_host_emit_item_removed (NaHost *host, + NaItem *item) +{ + g_signal_emit (host, signals[SIGNAL_ITEM_REMOVED], 0, item); +} diff --git a/applets/notification_area/na-host.h b/applets/notification_area/na-host.h new file mode 100644 index 00000000..7f06678a --- /dev/null +++ b/applets/notification_area/na-host.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * Copyright (C) 2017 Colomban Wendling + * + * 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 . + */ + +#ifndef NA_HOST_H +#define NA_HOST_H + +#include "na-item.h" + +G_BEGIN_DECLS + +#define NA_TYPE_HOST na_host_get_type () +G_DECLARE_INTERFACE (NaHost, na_host, NA, HOST, GObject) + +struct _NaHostInterface +{ + GTypeInterface parent; + + void (*force_redraw) (NaHost *host); + void (*style_updated) (NaHost *host, + GtkStyleContext *context); +}; + +void na_host_force_redraw (NaHost *host); +void na_host_style_updated (NaHost *host, + GtkStyleContext *context); +void na_host_emit_item_added (NaHost *host, + NaItem *item); +void na_host_emit_item_removed (NaHost *host, + NaItem *item); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/na-item.c b/applets/notification_area/na-item.c new file mode 100644 index 00000000..497e4102 --- /dev/null +++ b/applets/notification_area/na-item.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * Copyright (C) 2017 Colomban Wendling + * + * 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 . + */ + +#include "config.h" + +#include "na-item.h" + +G_DEFINE_INTERFACE_WITH_CODE (NaItem, na_item, GTK_TYPE_WIDGET, + g_type_interface_add_prerequisite (g_define_type_id, + GTK_TYPE_ORIENTABLE);) + +static gboolean +na_item_draw_on_parent_default (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr) +{ + return FALSE; +} + +static void +na_item_default_init (NaItemInterface *iface) +{ + iface->draw_on_parent = na_item_draw_on_parent_default; +} + +const gchar * +na_item_get_id (NaItem *item) +{ + NaItemInterface *iface; + + g_return_val_if_fail (NA_IS_ITEM (item), NULL); + + iface = NA_ITEM_GET_IFACE (item); + g_return_val_if_fail (iface->get_id != NULL, NULL); + + return iface->get_id (item); +} + +NaItemCategory +na_item_get_category (NaItem *item) +{ + NaItemInterface *iface; + + g_return_val_if_fail (NA_IS_ITEM (item), + NA_ITEM_CATEGORY_APPLICATION_STATUS); + + iface = NA_ITEM_GET_IFACE (item); + g_return_val_if_fail (iface->get_category != NULL, + NA_ITEM_CATEGORY_APPLICATION_STATUS); + + return iface->get_category (item); +} + +/* + * Fairly ugly hack because system-tray/NaTrayChild uses a weird hack for + * drawing itself. I'm not sure it's still needed with the current GTK3 + * drawing where not all widgets have an own window, but well. + * + * Should return %TRUE if it handled itself, or %FALSE if the parent should + * draw normally. Default is to draw normally. + */ +gboolean +na_item_draw_on_parent (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr) +{ + NaItemInterface *iface; + + g_return_val_if_fail (NA_IS_ITEM (item), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (parent), FALSE); + + iface = NA_ITEM_GET_IFACE (item); + g_return_val_if_fail (iface->draw_on_parent != NULL, FALSE); + + return iface->draw_on_parent (item, parent, parent_cr); +} diff --git a/applets/notification_area/na-item.h b/applets/notification_area/na-item.h new file mode 100644 index 00000000..30e8e676 --- /dev/null +++ b/applets/notification_area/na-item.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 Alberts Muktupāvels + * Copyright (C) 2017 Colomban Wendling + * + * 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 . + */ + +#ifndef NA_ITEM_H +#define NA_ITEM_H + +#include + +G_BEGIN_DECLS + +#define NA_TYPE_ITEM na_item_get_type () +G_DECLARE_INTERFACE (NaItem, na_item, NA, ITEM, GtkWidget) + +typedef enum +{ + NA_ITEM_CATEGORY_APPLICATION_STATUS, + NA_ITEM_CATEGORY_COMMUNICATIONS, + NA_ITEM_CATEGORY_SYSTEM_SERVICES, + NA_ITEM_CATEGORY_HARDWARE, + /* FIXME: use proper categories for system tray stuff, too. + * See na_tray_child_get_category() in na-tray-child.c */ + NA_ITEM_CATEGORY_FAKE_BATTERY, + NA_ITEM_CATEGORY_FAKE_NETWORK, + NA_ITEM_CATEGORY_FAKE_BLUETOOTH, + NA_ITEM_CATEGORY_FAKE_VOLUME, + NA_ITEM_CATEGORY_FAKE_KEYBOARD, +} NaItemCategory; + +struct _NaItemInterface +{ + GTypeInterface g_iface; + + const gchar * (* get_id) (NaItem *item); + NaItemCategory (* get_category) (NaItem *item); + + gboolean (* draw_on_parent) (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr); +}; + +const gchar *na_item_get_id (NaItem *item); +NaItemCategory na_item_get_category (NaItem *item); +gboolean na_item_draw_on_parent (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr); + +G_END_DECLS + +#endif diff --git a/applets/notification_area/na-marshal.list b/applets/notification_area/na-marshal.list deleted file mode 100644 index e3fc3993..00000000 --- a/applets/notification_area/na-marshal.list +++ /dev/null @@ -1,3 +0,0 @@ -VOID:OBJECT,OBJECT -VOID:OBJECT,STRING,LONG,LONG -VOID:OBJECT,LONG diff --git a/applets/notification_area/na-tray-child.c b/applets/notification_area/na-tray-child.c deleted file mode 100644 index 64824a74..00000000 --- a/applets/notification_area/na-tray-child.c +++ /dev/null @@ -1,538 +0,0 @@ -/* na-tray-child.c - * Copyright (C) 2002 Anders Carlsson - * Copyright (C) 2003-2006 Vincent Untz - * Copyright (C) 2008 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include - -#include "na-tray-child.h" - -#include -#include -#include -#include -#include - -G_DEFINE_TYPE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET) - -static void -na_tray_child_finalize (GObject *object) -{ - G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object); -} - -static void -na_tray_child_realize (GtkWidget *widget) -{ - NaTrayChild *child = NA_TRAY_CHILD (widget); - GdkVisual *visual = gtk_widget_get_visual (widget); - GdkWindow *window; - - GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget); - - window = gtk_widget_get_window (widget); - - if (child->has_alpha) - { - /* We have real transparency with an ARGB visual and the Composite - * extension. */ - - /* Set a transparent background */ - cairo_pattern_t *transparent = cairo_pattern_create_rgba (0, 0, 0, 0); - gdk_window_set_background_pattern (window, transparent); - gdk_window_set_composited (window, TRUE); - cairo_pattern_destroy (transparent); - - child->parent_relative_bg = FALSE; - } - else if (visual == gdk_window_get_visual(gdk_window_get_parent(window))) - { - /* Otherwise, if the visual matches the visual of the parent window, we - * can use a parent-relative background and fake transparency. */ - gdk_window_set_background_pattern (window, NULL); - - child->parent_relative_bg = TRUE; - } - else - { - /* Nothing to do; the icon will sit on top of an ugly gray box */ - child->parent_relative_bg = FALSE; - } - - gdk_window_set_composited (window, child->composited); - - gtk_widget_set_app_paintable (GTK_WIDGET (child), - child->parent_relative_bg || child->has_alpha); - - /* Double-buffering will interfere with the parent-relative-background fake - * transparency, since the double-buffer code doesn't know how to fill in the - * background of the double-buffer correctly. - */ - gtk_widget_set_double_buffered (GTK_WIDGET (child), - child->parent_relative_bg); -} - -static void -na_tray_child_style_set (GtkWidget *widget, - GtkStyle *previous_style) -{ - /* The default handler resets the background according to the new style. - * We either use a transparent background or a parent-relative background - * and ignore the style background. So, just don't chain up. - */ -} - -static void -na_tray_child_get_preferred_width (GtkWidget *widget, - gint *minimal_width, - gint *natural_width) -{ - GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_width (widget, - minimal_width, - natural_width); - - if (*minimal_width < 16) - *minimal_width = 16; - - if (*natural_width < 16) - *natural_width = 16; -} - -static void -na_tray_child_get_preferred_height (GtkWidget *widget, - gint *minimal_height, - gint *natural_height) -{ - GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_height (widget, - minimal_height, - natural_height); - - if (*minimal_height < 16) - *minimal_height = 16; - - if (*natural_height < 16) - *natural_height = 16; -} - -static void -na_tray_child_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) -{ - NaTrayChild *child = NA_TRAY_CHILD (widget); - GtkAllocation widget_allocation; - gboolean moved, resized; - - gtk_widget_get_allocation (widget, &widget_allocation); - - moved = (allocation->x != widget_allocation.x || - allocation->y != widget_allocation.y); - resized = (allocation->width != widget_allocation.width || - allocation->height != widget_allocation.height); - - /* When we are allocating the widget while mapped we need special handling - * for both real and fake transparency. - * - * Real transparency: we need to invalidate and trigger a redraw of the old - * and new areas. (GDK really should handle this for us, but doesn't as of - * GTK+-2.14) - * - * Fake transparency: if the widget moved, we need to force the contents to - * be redrawn with the new offset for the parent-relative background. - */ - if ((moved || resized) && gtk_widget_get_mapped (widget)) - { - if (na_tray_child_has_alpha (child)) - gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), - &widget_allocation, FALSE); - } - - GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget, - allocation); - - if ((moved || resized) && gtk_widget_get_mapped (widget)) - { - if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget))) - gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), - &widget_allocation, FALSE); - else if (moved && child->parent_relative_bg) - na_tray_child_force_redraw (child); - } -} - -/* The plug window should completely occupy the area of the child, so we won't - * get an expose event. But in case we do (the plug unmaps itself, say), this - * expose handler draws with real or fake transparency. - */ -static gboolean -na_tray_child_draw (GtkWidget *widget, - cairo_t *cr) -{ - NaTrayChild *child = NA_TRAY_CHILD (widget); - - if (na_tray_child_has_alpha (child)) - { - /* Clear to transparent */ - cairo_set_source_rgba (cr, 0, 0, 0, 0); - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_paint (cr); - } - else if (child->parent_relative_bg) - { - /* Clear to parent-relative pixmap */ - GdkWindow *window; - cairo_surface_t *target; - GdkRectangle clip_rect; - - window = gtk_widget_get_window (widget); - target = cairo_get_group_target (cr); - - gdk_cairo_get_clip_rectangle (cr, &clip_rect); - - /* Clear to parent-relative pixmap - * We need to use direct X access here because GDK doesn't know about - * the parent relative pixmap. */ - cairo_surface_flush (target); - - XClearArea (GDK_WINDOW_XDISPLAY (window), - GDK_WINDOW_XID (window), - clip_rect.x, clip_rect.y, - clip_rect.width, clip_rect.height, - False); - cairo_surface_mark_dirty_rectangle (target, - clip_rect.x, clip_rect.y, - clip_rect.width, clip_rect.height); - } - - return FALSE; -} - -static void -na_tray_child_init (NaTrayChild *child) -{ -} - -static void -na_tray_child_class_init (NaTrayChildClass *klass) -{ - GObjectClass *gobject_class; - GtkWidgetClass *widget_class; - - gobject_class = (GObjectClass *)klass; - widget_class = (GtkWidgetClass *)klass; - - gobject_class->finalize = na_tray_child_finalize; - widget_class->style_set = na_tray_child_style_set; - widget_class->realize = na_tray_child_realize; - widget_class->get_preferred_width = na_tray_child_get_preferred_width; - widget_class->get_preferred_height = na_tray_child_get_preferred_height; - widget_class->size_allocate = na_tray_child_size_allocate; - widget_class->draw = na_tray_child_draw; -} - -GtkWidget * -na_tray_child_new (GdkScreen *screen, - Window icon_window) -{ - XWindowAttributes window_attributes; - Display *xdisplay; - NaTrayChild *child; - GdkVisual *visual; - gboolean visual_has_alpha; - int red_prec, green_prec, blue_prec, depth; - int result; - - g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); - g_return_val_if_fail (icon_window != None, NULL); - - xdisplay = GDK_SCREEN_XDISPLAY (screen); - - /* We need to determine the visual of the window we are embedding and create - * the socket in the same visual. - */ - - gdk_error_trap_push (); - result = XGetWindowAttributes (xdisplay, icon_window, - &window_attributes); - gdk_error_trap_pop_ignored (); - - if (!result) /* Window already gone */ - return NULL; - - visual = gdk_x11_screen_lookup_visual (screen, - window_attributes.visual->visualid); - if (!visual) /* Icon window is on another screen? */ - return NULL; - - child = g_object_new (NA_TYPE_TRAY_CHILD, NULL); - child->icon_window = icon_window; - - gtk_widget_set_visual (GTK_WIDGET (child), visual); - - /* We have alpha if the visual has something other than red, green, - * and blue */ - gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec); - gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec); - gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec); - depth = gdk_visual_get_depth (visual); - - visual_has_alpha = red_prec + blue_prec + green_prec < depth; - child->has_alpha = (visual_has_alpha && - gdk_display_supports_composite (gdk_screen_get_display (screen))); - - child->composited = child->has_alpha; - - return GTK_WIDGET (child); -} - -char * -na_tray_child_get_title (NaTrayChild *child) -{ - char *retval = NULL; - GdkDisplay *display; - Atom utf8_string, atom, type; - int result; - int format; - gulong nitems; - gulong bytes_after; - gchar *val; - - g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL); - - display = gtk_widget_get_display (GTK_WIDGET (child)); - - utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); - atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"); - - gdk_error_trap_push (); - - result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), - child->icon_window, - atom, - 0, G_MAXLONG, - False, utf8_string, - &type, &format, &nitems, - &bytes_after, (guchar **)&val); - - if (gdk_error_trap_pop () || result != Success) - return NULL; - - if (type != utf8_string || - format != 8 || - nitems == 0) - { - if (val) - XFree (val); - return NULL; - } - - if (!g_utf8_validate (val, nitems, NULL)) - { - XFree (val); - return NULL; - } - - retval = g_strndup (val, nitems); - - XFree (val); - - return retval; -} - -/** - * na_tray_child_has_alpha; - * @child: a #NaTrayChild - * - * Checks if the child has an ARGB visual and real alpha transparence. - * (as opposed to faked alpha transparency with an parent-relative - * background) - * - * Return value: %TRUE if the child has an alpha transparency - */ -gboolean -na_tray_child_has_alpha (NaTrayChild *child) -{ - g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE); - - return child->has_alpha; -} - -/** - * na_tray_child_set_composited; - * @child: a #NaTrayChild - * @composited: %TRUE if the child's window should be redirected - * - * Sets whether the #GdkWindow of the child should be set redirected - * using gdk_window_set_composited(). By default this is based off of - * na_tray_child_has_alpha(), but it may be useful to override it in - * certain circumstances; for example, if the #NaTrayChild is added - * to a parent window and that parent window is composited against the - * background. - */ -void -na_tray_child_set_composited (NaTrayChild *child, - gboolean composited) -{ - g_return_if_fail (NA_IS_TRAY_CHILD (child)); - - if (child->composited == composited) - return; - - child->composited = composited; - if (gtk_widget_get_realized (GTK_WIDGET (child))) - gdk_window_set_composited (gtk_widget_get_window (GTK_WIDGET (child)), - composited); -} - -/* If we are faking transparency with a window-relative background, force a - * redraw of the icon. This should be called if the background changes or if - * the child is shifted with respect to the background. - */ -void -na_tray_child_force_redraw (NaTrayChild *child) -{ - GtkWidget *widget = GTK_WIDGET (child); - - if (gtk_widget_get_mapped (widget) && child->parent_relative_bg) - { -#if 1 - /* Sending an ExposeEvent might cause redraw problems if the - * icon is expecting the server to clear-to-background before - * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon. - */ - Display *xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)); - XEvent xev; - GdkWindow *plug_window; - GtkAllocation allocation; - - plug_window = gtk_socket_get_plug_window (GTK_SOCKET (child)); - gtk_widget_get_allocation (widget, &allocation); - - xev.xexpose.type = Expose; - xev.xexpose.window = GDK_WINDOW_XID (plug_window); - xev.xexpose.x = 0; - xev.xexpose.y = 0; - xev.xexpose.width = allocation.width; - xev.xexpose.height = allocation.height; - xev.xexpose.count = 0; - - gdk_error_trap_push (); - XSendEvent (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)), - xev.xexpose.window, - False, ExposureMask, - &xev); - /* We have to sync to reliably catch errors from the XSendEvent(), - * since that is asynchronous. - */ - XSync (xdisplay, False); - gdk_error_trap_pop_ignored (); -#else - /* Hiding and showing is the safe way to do it, but can result in more - * flickering. - */ - gdk_window_hide (widget->window); - gdk_window_show (widget->window); -#endif - } -} - -/* from libwnck/xutils.c, comes as LGPLv2+ */ -static char * -latin1_to_utf8 (const char *latin1) -{ - GString *str; - const char *p; - - str = g_string_new (NULL); - - p = latin1; - while (*p) - { - g_string_append_unichar (str, (gunichar) *p); - ++p; - } - - return g_string_free (str, FALSE); -} - -/* derived from libwnck/xutils.c, comes as LGPLv2+ */ -static void -_get_wmclass (Display *xdisplay, - Window xwindow, - char **res_class, - char **res_name) -{ - XClassHint ch; - - ch.res_name = NULL; - ch.res_class = NULL; - - gdk_error_trap_push (); - XGetClassHint (xdisplay, xwindow, &ch); - gdk_error_trap_pop_ignored (); - - if (res_class) - *res_class = NULL; - - if (res_name) - *res_name = NULL; - - if (ch.res_name) - { - if (res_name) - *res_name = latin1_to_utf8 (ch.res_name); - - XFree (ch.res_name); - } - - if (ch.res_class) - { - if (res_class) - *res_class = latin1_to_utf8 (ch.res_class); - - XFree (ch.res_class); - } -} - -/** - * na_tray_child_get_wm_class; - * @child: a #NaTrayChild - * @res_name: return location for a string containing the application name of - * @child, or %NULL - * @res_class: return location for a string containing the application class of - * @child, or %NULL - * - * Fetches the resource associated with @child. - */ -void -na_tray_child_get_wm_class (NaTrayChild *child, - char **res_name, - char **res_class) -{ - GdkDisplay *display; - - g_return_if_fail (NA_IS_TRAY_CHILD (child)); - - display = gtk_widget_get_display (GTK_WIDGET (child)); - - _get_wmclass (GDK_DISPLAY_XDISPLAY (display), - child->icon_window, - res_class, - res_name); -} diff --git a/applets/notification_area/na-tray-child.h b/applets/notification_area/na-tray-child.h deleted file mode 100644 index 2ddfed9e..00000000 --- a/applets/notification_area/na-tray-child.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* na-tray-child.h - * Copyright (C) 2002 Anders Carlsson - * Copyright (C) 2003-2006 Vincent Untz - * Copyright (C) 2008 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __NA_TRAY_CHILD_H__ -#define __NA_TRAY_CHILD_H__ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define NA_TYPE_TRAY_CHILD (na_tray_child_get_type ()) -#define NA_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_CHILD, NaTrayChild)) -#define NA_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_CHILD, NaTrayChildClass)) -#define NA_IS_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_CHILD)) -#define NA_IS_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_CHILD)) -#define NA_TRAY_CHILD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_CHILD, NaTrayChildClass)) - -typedef struct _NaTrayChild NaTrayChild; -typedef struct _NaTrayChildClass NaTrayChildClass; -typedef struct _NaTrayChildChild NaTrayChildChild; - -struct _NaTrayChild -{ - GtkSocket parent_instance; - Window icon_window; - guint has_alpha : 1; - guint composited : 1; - guint parent_relative_bg : 1; -}; - -struct _NaTrayChildClass -{ - GtkSocketClass parent_class; -}; - -GType na_tray_child_get_type (void); - -GtkWidget *na_tray_child_new (GdkScreen *screen, - Window icon_window); -char *na_tray_child_get_title (NaTrayChild *child); -gboolean na_tray_child_has_alpha (NaTrayChild *child); -void na_tray_child_set_composited (NaTrayChild *child, - gboolean composited); -void na_tray_child_force_redraw (NaTrayChild *child); -void na_tray_child_get_wm_class (NaTrayChild *child, - char **res_name, - char **res_class); - -#ifdef __cplusplus -} -#endif - -#endif /* __NA_TRAY_CHILD_H__ */ diff --git a/applets/notification_area/na-tray-manager.c b/applets/notification_area/na-tray-manager.c deleted file mode 100644 index 94d3003d..00000000 --- a/applets/notification_area/na-tray-manager.c +++ /dev/null @@ -1,988 +0,0 @@ -/* na-tray-manager.c - * Copyright (C) 2002 Anders Carlsson - * Copyright (C) 2003-2006 Vincent Untz - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Used to be: eggtraymanager.c - */ - -#include -#include -#include - -#include "na-tray-manager.h" - -#include -#include -#include -#include - -#include "na-marshal.h" - -/* Signals */ -enum -{ - TRAY_ICON_ADDED, - TRAY_ICON_REMOVED, - MESSAGE_SENT, - MESSAGE_CANCELLED, - LOST_SELECTION, - LAST_SIGNAL -}; - -enum { - PROP_0, - PROP_ORIENTATION -}; - -typedef struct -{ - long id, len; - long remaining_len; - - long timeout; - char *str; -#ifdef GDK_WINDOWING_X11 - Window window; -#endif -} PendingMessage; - -static guint manager_signals[LAST_SIGNAL] = { 0 }; - -#define SYSTEM_TRAY_REQUEST_DOCK 0 -#define SYSTEM_TRAY_BEGIN_MESSAGE 1 -#define SYSTEM_TRAY_CANCEL_MESSAGE 2 - -#define SYSTEM_TRAY_ORIENTATION_HORZ 0 -#define SYSTEM_TRAY_ORIENTATION_VERT 1 - -#ifdef GDK_WINDOWING_X11 -static gboolean na_tray_manager_check_running_screen_x11 (GdkScreen *screen); -#endif - -static void na_tray_manager_finalize (GObject *object); -static void na_tray_manager_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void na_tray_manager_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); - -static void na_tray_manager_unmanage (NaTrayManager *manager); - -G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT) - -static void -na_tray_manager_init (NaTrayManager *manager) -{ - manager->invisible = NULL; - manager->socket_table = g_hash_table_new (NULL, NULL); - - manager->padding = 0; - manager->icon_size = 0; - - manager->fg.red = 0.0; - manager->fg.green = 0.0; - manager->fg.blue = 0.0; - manager->fg.alpha = 1.0; - - manager->error.red = 1.0; - manager->error.green = 0.0; - manager->error.blue = 0.0; - manager->error.alpha = 1.0; - - manager->warning.red = 1.0; - manager->warning.green = 1.0; - manager->warning.blue = 0.0; - manager->warning.alpha = 1.0; - - manager->success.red = 0.0; - manager->success.green = 1.0; - manager->success.blue = 0.0; - manager->success.alpha = 1.0; -} - -static void -na_tray_manager_class_init (NaTrayManagerClass *klass) -{ - GObjectClass *gobject_class; - - gobject_class = (GObjectClass *)klass; - - gobject_class->finalize = na_tray_manager_finalize; - gobject_class->set_property = na_tray_manager_set_property; - gobject_class->get_property = na_tray_manager_get_property; - - g_object_class_install_property (gobject_class, - PROP_ORIENTATION, - g_param_spec_enum ("orientation", - "orientation", - "orientation", - GTK_TYPE_ORIENTATION, - GTK_ORIENTATION_HORIZONTAL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_NICK | - G_PARAM_STATIC_BLURB)); - - manager_signals[TRAY_ICON_ADDED] = - g_signal_new ("tray_icon_added", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added), - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, - GTK_TYPE_SOCKET); - - manager_signals[TRAY_ICON_REMOVED] = - g_signal_new ("tray_icon_removed", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed), - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, - GTK_TYPE_SOCKET); - manager_signals[MESSAGE_SENT] = - g_signal_new ("message_sent", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaTrayManagerClass, message_sent), - NULL, NULL, - _na_marshal_VOID__OBJECT_STRING_LONG_LONG, - G_TYPE_NONE, 4, - GTK_TYPE_SOCKET, - G_TYPE_STRING, - G_TYPE_LONG, - G_TYPE_LONG); - manager_signals[MESSAGE_CANCELLED] = - g_signal_new ("message_cancelled", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled), - NULL, NULL, - _na_marshal_VOID__OBJECT_LONG, - G_TYPE_NONE, 2, - GTK_TYPE_SOCKET, - G_TYPE_LONG); - manager_signals[LOST_SELECTION] = - g_signal_new ("lost_selection", - G_OBJECT_CLASS_TYPE (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -static void -na_tray_manager_finalize (GObject *object) -{ - NaTrayManager *manager; - - manager = NA_TRAY_MANAGER (object); - - na_tray_manager_unmanage (manager); - - g_list_free (manager->messages); - g_hash_table_destroy (manager->socket_table); - - G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object); -} - -static void -na_tray_manager_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - NaTrayManager *manager = NA_TRAY_MANAGER (object); - - switch (prop_id) - { - case PROP_ORIENTATION: - na_tray_manager_set_orientation (manager, g_value_get_enum (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -na_tray_manager_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - NaTrayManager *manager = NA_TRAY_MANAGER (object); - - switch (prop_id) - { - case PROP_ORIENTATION: - g_value_set_enum (value, manager->orientation); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -NaTrayManager * -na_tray_manager_new (void) -{ - NaTrayManager *manager; - - manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL); - - return manager; -} - -#ifdef GDK_WINDOWING_X11 - -static gboolean -na_tray_manager_plug_removed (GtkSocket *socket, - NaTrayManager *manager) -{ - NaTrayChild *child = NA_TRAY_CHILD (socket); - - g_hash_table_remove (manager->socket_table, - GINT_TO_POINTER (child->icon_window)); - g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child); - - /* This destroys the socket. */ - return FALSE; -} - -static void -na_tray_manager_handle_dock_request (NaTrayManager *manager, - XClientMessageEvent *xevent) -{ - Window icon_window = xevent->data.l[2]; - GtkWidget *child; - - if (g_hash_table_lookup (manager->socket_table, - GINT_TO_POINTER (icon_window))) - { - /* We already got this notification earlier, ignore this one */ - return; - } - - child = na_tray_child_new (manager->screen, icon_window); - if (child == NULL) /* already gone or other error */ - return; - - g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0, - child); - - /* If the child wasn't attached, then destroy it */ - - if (!GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (child)))) - { - gtk_widget_destroy (child); - return; - } - - g_signal_connect (child, "plug_removed", - G_CALLBACK (na_tray_manager_plug_removed), manager); - - gtk_socket_add_id (GTK_SOCKET (child), icon_window); - - if (!gtk_socket_get_plug_window (GTK_SOCKET (child))) - { - /* Embedding failed, we won't get a plug-removed signal */ - /* This signal destroys the socket */ - g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child); - return; - } - - g_hash_table_insert (manager->socket_table, - GINT_TO_POINTER (icon_window), child); - gtk_widget_show (child); -} - -static void -pending_message_free (PendingMessage *message) -{ - g_free (message->str); - g_free (message); -} - -static void -na_tray_manager_handle_message_data (NaTrayManager *manager, - XClientMessageEvent *xevent) -{ - GList *p; - int len; - - /* Try to see if we can find the pending message in the list */ - for (p = manager->messages; p; p = p->next) - { - PendingMessage *msg = p->data; - - if (xevent->window == msg->window) - { - /* Append the message */ - len = MIN (msg->remaining_len, 20); - - memcpy ((msg->str + msg->len - msg->remaining_len), - &xevent->data, len); - msg->remaining_len -= len; - - if (msg->remaining_len == 0) - { - GtkSocket *socket; - - socket = g_hash_table_lookup (manager->socket_table, - GINT_TO_POINTER (msg->window)); - - if (socket) - g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, - socket, msg->str, msg->id, msg->timeout); - - pending_message_free (msg); - manager->messages = g_list_remove_link (manager->messages, p); - g_list_free_1 (p); - } - - break; - } - } -} - -static void -na_tray_manager_handle_begin_message (NaTrayManager *manager, - XClientMessageEvent *xevent) -{ - GtkSocket *socket; - GList *p; - PendingMessage *msg; - long timeout; - long len; - long id; - - socket = g_hash_table_lookup (manager->socket_table, - GINT_TO_POINTER (xevent->window)); - /* we don't know about this tray icon, so ignore the message */ - if (!socket) - return; - - timeout = xevent->data.l[2]; - len = xevent->data.l[3]; - id = xevent->data.l[4]; - - /* Check if the same message is already in the queue and remove it if so */ - for (p = manager->messages; p; p = p->next) - { - PendingMessage *pmsg = p->data; - - if (xevent->window == pmsg->window && - id == pmsg->id) - { - /* Hmm, we found it, now remove it */ - pending_message_free (pmsg); - manager->messages = g_list_remove_link (manager->messages, p); - g_list_free_1 (p); - break; - } - } - - if (len == 0) - { - g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, - socket, "", id, timeout); - } - else - { - /* Now add the new message to the queue */ - msg = g_new0 (PendingMessage, 1); - msg->window = xevent->window; - msg->timeout = timeout; - msg->len = len; - msg->id = id; - msg->remaining_len = msg->len; - msg->str = g_malloc (msg->len + 1); - msg->str[msg->len] = '\0'; - manager->messages = g_list_prepend (manager->messages, msg); - } -} - -static void -na_tray_manager_handle_cancel_message (NaTrayManager *manager, - XClientMessageEvent *xevent) -{ - GList *p; - GtkSocket *socket; - long id; - - id = xevent->data.l[2]; - - /* Check if the message is in the queue and remove it if so */ - for (p = manager->messages; p; p = p->next) - { - PendingMessage *msg = p->data; - - if (xevent->window == msg->window && - id == msg->id) - { - pending_message_free (msg); - manager->messages = g_list_remove_link (manager->messages, p); - g_list_free_1 (p); - break; - } - } - - socket = g_hash_table_lookup (manager->socket_table, - GINT_TO_POINTER (xevent->window)); - - if (socket) - { - g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0, - socket, xevent->data.l[2]); - } -} - -static GdkFilterReturn -na_tray_manager_window_filter (GdkXEvent *xev, - GdkEvent *event, - gpointer data) -{ - XEvent *xevent = (GdkXEvent *)xev; - NaTrayManager *manager = data; - - if (xevent->type == ClientMessage) - { - /* We handle this client message here. See comment in - * na_tray_manager_handle_client_message_opcode() for details */ - if (xevent->xclient.message_type == manager->opcode_atom && - xevent->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) - { - na_tray_manager_handle_dock_request (manager, - (XClientMessageEvent *) xevent); - return GDK_FILTER_REMOVE; - } - /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_BEGIN_MESSAGE */ - else if (xevent->xclient.message_type == manager->opcode_atom && - xevent->xclient.data.l[1] == SYSTEM_TRAY_BEGIN_MESSAGE) - { - na_tray_manager_handle_begin_message (manager, - (XClientMessageEvent *) event); - return GDK_FILTER_REMOVE; - } - /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_CANCEL_MESSAGE */ - else if (xevent->xclient.message_type == manager->opcode_atom && - xevent->xclient.data.l[1] == SYSTEM_TRAY_CANCEL_MESSAGE) - { - na_tray_manager_handle_cancel_message (manager, - (XClientMessageEvent *) event); - return GDK_FILTER_REMOVE; - } - /* _NET_SYSTEM_TRAY_MESSAGE_DATA */ - else if (xevent->xclient.message_type == manager->message_data_atom) - { - na_tray_manager_handle_message_data (manager, - (XClientMessageEvent *) event); - return GDK_FILTER_REMOVE; - } - } - else if (xevent->type == SelectionClear) - { - g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); - na_tray_manager_unmanage (manager); - } - - return GDK_FILTER_CONTINUE; -} - -#if 0 -//FIXME investigate why this doesn't work -static gboolean -na_tray_manager_selection_clear_event (GtkWidget *widget, - GdkEventSelection *event, - NaTrayManager *manager) -{ - g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); - na_tray_manager_unmanage (manager); - - return FALSE; -} -#endif -#endif - -static void -na_tray_manager_unmanage (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkDisplay *display; - guint32 timestamp; - GtkWidget *invisible; - GdkWindow *window; - - if (manager->invisible == NULL) - return; - - invisible = manager->invisible; - window = gtk_widget_get_window (invisible); - - g_assert (GTK_IS_INVISIBLE (invisible)); - g_assert (gtk_widget_get_realized (invisible)); - g_assert (GDK_IS_WINDOW (window)); - - display = gtk_widget_get_display (invisible); - - if (gdk_selection_owner_get_for_display (display, manager->selection_atom) == - window) - { - timestamp = gdk_x11_get_server_time (window); - gdk_selection_owner_set_for_display (display, - NULL, - manager->selection_atom, - timestamp, - TRUE); - } - - gdk_window_remove_filter (window, - na_tray_manager_window_filter, manager); - - manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */ - gtk_widget_destroy (invisible); - g_object_unref (G_OBJECT (invisible)); -#endif -} - -static void -na_tray_manager_set_orientation_property (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkWindow *window; - GdkDisplay *display; - Atom orientation_atom; - gulong data[1]; - - g_return_if_fail (manager->invisible != NULL); - window = gtk_widget_get_window (manager->invisible); - g_return_if_fail (window != NULL); - - display = gtk_widget_get_display (manager->invisible); - orientation_atom = gdk_x11_get_xatom_by_name_for_display (display, - "_NET_SYSTEM_TRAY_ORIENTATION"); - - data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ? - SYSTEM_TRAY_ORIENTATION_HORZ : - SYSTEM_TRAY_ORIENTATION_VERT; - - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - GDK_WINDOW_XID (window), - orientation_atom, - XA_CARDINAL, 32, - PropModeReplace, - (guchar *) &data, 1); -#endif -} - -static void -na_tray_manager_set_visual_property (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkWindow *window; - GdkDisplay *display; - Visual *xvisual; - Atom visual_atom; - gulong data[1]; - - g_return_if_fail (manager->invisible != NULL); - window = gtk_widget_get_window (manager->invisible); - g_return_if_fail (window != NULL); - - /* The visual property is a hint to the tray icons as to what visual they - * should use for their windows. If the X server has RGBA colormaps, then - * we tell the tray icons to use a RGBA colormap and we'll composite the - * icon onto its parents with real transparency. Otherwise, we just tell - * the icon to use our colormap, and we'll do some hacks with parent - * relative backgrounds to simulate transparency. - */ - - display = gtk_widget_get_display (manager->invisible); - visual_atom = gdk_x11_get_xatom_by_name_for_display (display, - "_NET_SYSTEM_TRAY_VISUAL"); - - if (gdk_screen_get_rgba_visual (manager->screen) != NULL && - gdk_display_supports_composite (display)) - { - xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (manager->screen)); - } - else - { - /* We actually want the visual of the tray where the icons will - * be embedded. In almost all cases, this will be the same as the visual - * of the screen. - */ - xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (manager->screen)); - } - - data[0] = XVisualIDFromVisual (xvisual); - - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - GDK_WINDOW_XID (window), - visual_atom, - XA_VISUALID, 32, - PropModeReplace, - (guchar *) &data, 1); -#endif -} - -static void -na_tray_manager_set_padding_property (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkWindow *window; - GdkDisplay *display; - Atom atom; - gulong data[1]; - - g_return_if_fail (manager->invisible != NULL); - window = gtk_widget_get_window (manager->invisible); - g_return_if_fail (window != NULL); - - display = gtk_widget_get_display (manager->invisible); - atom = gdk_x11_get_xatom_by_name_for_display (display, - "_NET_SYSTEM_TRAY_PADDING"); - - data[0] = manager->padding; - - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - GDK_WINDOW_XID (window), - atom, - XA_CARDINAL, 32, - PropModeReplace, - (guchar *) &data, 1); -#endif -} - -static void -na_tray_manager_set_icon_size_property (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkWindow *window; - GdkDisplay *display; - Atom atom; - gulong data[1]; - - g_return_if_fail (manager->invisible != NULL); - window = gtk_widget_get_window (manager->invisible); - g_return_if_fail (window != NULL); - - display = gtk_widget_get_display (manager->invisible); - atom = gdk_x11_get_xatom_by_name_for_display (display, - "_NET_SYSTEM_TRAY_ICON_SIZE"); - - data[0] = manager->icon_size; - - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - GDK_WINDOW_XID (window), - atom, - XA_CARDINAL, 32, - PropModeReplace, - (guchar *) &data, 1); -#endif -} - -static void -na_tray_manager_set_colors_property (NaTrayManager *manager) -{ -#ifdef GDK_WINDOWING_X11 - GdkWindow *window; - GdkDisplay *display; - Atom atom; - gulong data[12]; - - g_return_if_fail (manager->invisible != NULL); - window = gtk_widget_get_window (manager->invisible); - g_return_if_fail (window != NULL); - - display = gtk_widget_get_display (manager->invisible); - atom = gdk_x11_get_xatom_by_name_for_display (display, - "_NET_SYSTEM_TRAY_COLORS"); - - data[0] = manager->fg.red * 65535; - data[1] = manager->fg.green * 65535; - data[2] = manager->fg.blue * 65535; - data[3] = manager->error.red * 65535; - data[4] = manager->error.green * 65535; - data[5] = manager->error.blue * 65535; - data[6] = manager->warning.red * 65535; - data[7] = manager->warning.green * 65535; - data[8] = manager->warning.blue * 65535; - data[9] = manager->success.red * 65535; - data[10] = manager->success.green * 65535; - data[11] = manager->success.blue * 65535; - - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - GDK_WINDOW_XID (window), - atom, - XA_CARDINAL, 32, - PropModeReplace, - (guchar *) &data, 12); -#endif -} - -#ifdef GDK_WINDOWING_X11 - -static gboolean -na_tray_manager_manage_screen_x11 (NaTrayManager *manager, - GdkScreen *screen) -{ - GdkDisplay *display; - Screen *xscreen; - GtkWidget *invisible; - GdkWindow *window; - char *selection_atom_name; - guint32 timestamp; - - g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE); - g_return_val_if_fail (manager->screen == NULL, FALSE); - - /* If there's already a manager running on the screen - * we can't create another one. - */ -#if 0 - if (na_tray_manager_check_running_screen_x11 (screen)) - return FALSE; -#endif - - manager->screen = screen; - - display = gdk_screen_get_display (screen); - xscreen = GDK_SCREEN_XSCREEN (screen); - - invisible = gtk_invisible_new_for_screen (screen); - gtk_widget_realize (invisible); - - gtk_widget_add_events (invisible, - GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK); - - selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", - gdk_screen_get_number (screen)); - manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE); - g_free (selection_atom_name); - - manager->invisible = invisible; - g_object_ref (G_OBJECT (manager->invisible)); - - na_tray_manager_set_orientation_property (manager); - na_tray_manager_set_visual_property (manager); - na_tray_manager_set_padding_property (manager); - na_tray_manager_set_icon_size_property (manager); - na_tray_manager_set_colors_property (manager); - - window = gtk_widget_get_window (invisible); - - timestamp = gdk_x11_get_server_time (window); - - /* Check if we could set the selection owner successfully */ - if (gdk_selection_owner_set_for_display (display, - window, - manager->selection_atom, - timestamp, - TRUE)) - { - XClientMessageEvent xev; - GdkAtom opcode_atom; - GdkAtom message_data_atom; - - xev.type = ClientMessage; - xev.window = RootWindowOfScreen (xscreen); - xev.message_type = gdk_x11_get_xatom_by_name_for_display (display, - "MANAGER"); - - xev.format = 32; - xev.data.l[0] = timestamp; - xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display, - manager->selection_atom); - xev.data.l[2] = GDK_WINDOW_XID (window); - xev.data.l[3] = 0; /* manager specific data */ - xev.data.l[4] = 0; /* manager specific data */ - - XSendEvent (GDK_DISPLAY_XDISPLAY (display), - RootWindowOfScreen (xscreen), - False, StructureNotifyMask, (XEvent *)&xev); - - opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE); - manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display, - opcode_atom); - - message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA", - FALSE); - - manager->message_data_atom = gdk_x11_atom_to_xatom_for_display (display, - message_data_atom); - - /* Add a window filter */ -#if 0 - /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */ - g_signal_connect (invisible, "selection-clear-event", - G_CALLBACK (na_tray_manager_selection_clear_event), - manager); -#endif - /* This is for SYSTEM_TRAY_REQUEST_DOCK and SelectionClear */ - gdk_window_add_filter (window, - na_tray_manager_window_filter, manager); - return TRUE; - } - else - { - gtk_widget_destroy (invisible); - g_object_unref (invisible); - manager->invisible = NULL; - - manager->screen = NULL; - - return FALSE; - } -} - -#endif - -gboolean -na_tray_manager_manage_screen (NaTrayManager *manager, - GdkScreen *screen) -{ - g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); - g_return_val_if_fail (manager->screen == NULL, FALSE); - -#ifdef GDK_WINDOWING_X11 - return na_tray_manager_manage_screen_x11 (manager, screen); -#else - return FALSE; -#endif -} - -#ifdef GDK_WINDOWING_X11 - -static gboolean -na_tray_manager_check_running_screen_x11 (GdkScreen *screen) -{ - GdkDisplay *display; - Atom selection_atom; - char *selection_atom_name; - - display = gdk_screen_get_display (screen); - selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", - gdk_screen_get_number (screen)); - selection_atom = gdk_x11_get_xatom_by_name_for_display (display, - selection_atom_name); - g_free (selection_atom_name); - - if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), - selection_atom) != None) - return TRUE; - else - return FALSE; -} - -#endif - -gboolean -na_tray_manager_check_running (GdkScreen *screen) -{ - g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); - -#ifdef GDK_WINDOWING_X11 - return na_tray_manager_check_running_screen_x11 (screen); -#else - return FALSE; -#endif -} - -void -na_tray_manager_set_orientation (NaTrayManager *manager, - GtkOrientation orientation) -{ - g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); - - if (manager->orientation != orientation) - { - manager->orientation = orientation; - - na_tray_manager_set_orientation_property (manager); - - g_object_notify (G_OBJECT (manager), "orientation"); - } -} - -void -na_tray_manager_set_padding (NaTrayManager *manager, - gint padding) -{ - g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); - - if (manager->padding != padding) - { - manager->padding = padding; - - na_tray_manager_set_padding_property (manager); - } -} - -void -na_tray_manager_set_icon_size (NaTrayManager *manager, - gint icon_size) -{ - g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); - - if (manager->icon_size != icon_size) - { - manager->icon_size = icon_size; - - na_tray_manager_set_icon_size_property (manager); - } -} - -void -na_tray_manager_set_colors (NaTrayManager *manager, - GdkRGBA *fg, - GdkRGBA *error, - GdkRGBA *warning, - GdkRGBA *success) -{ - g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); - - if (!gdk_rgba_equal (&manager->fg, fg) || - !gdk_rgba_equal (&manager->error, error) || - !gdk_rgba_equal (&manager->warning, warning) || - !gdk_rgba_equal (&manager->success, success)) - { - manager->fg = *fg; - manager->error = *error; - manager->warning = *warning; - manager->success = *success; - - na_tray_manager_set_colors_property (manager); - } -} - -GtkOrientation -na_tray_manager_get_orientation (NaTrayManager *manager) -{ - g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL); - - return manager->orientation; -} diff --git a/applets/notification_area/na-tray-manager.h b/applets/notification_area/na-tray-manager.h deleted file mode 100644 index 811791be..00000000 --- a/applets/notification_area/na-tray-manager.h +++ /dev/null @@ -1,113 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* na-tray-manager.h - * Copyright (C) 2002 Anders Carlsson - * Copyright (C) 2003-2006 Vincent Untz - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Used to be: eggtraymanager.h - */ - -#ifndef __NA_TRAY_MANAGER_H__ -#define __NA_TRAY_MANAGER_H__ - -#ifdef GDK_WINDOWING_X11 -#include -#endif -#include - -#include "na-tray-child.h" - -G_BEGIN_DECLS - -#define NA_TYPE_TRAY_MANAGER (na_tray_manager_get_type ()) -#define NA_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManager)) -#define NA_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass)) -#define NA_IS_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_MANAGER)) -#define NA_IS_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_MANAGER)) -#define NA_TRAY_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass)) - -typedef struct _NaTrayManager NaTrayManager; -typedef struct _NaTrayManagerClass NaTrayManagerClass; - -struct _NaTrayManager -{ - GObject parent_instance; - -#ifdef GDK_WINDOWING_X11 - GdkAtom selection_atom; - Atom opcode_atom; - Atom message_data_atom; -#endif - - GtkWidget *invisible; - GdkScreen *screen; - GtkOrientation orientation; - gint padding; - gint icon_size; - GdkRGBA fg; - GdkRGBA error; - GdkRGBA warning; - GdkRGBA success; - - GList *messages; - GHashTable *socket_table; -}; - -struct _NaTrayManagerClass -{ - GObjectClass parent_class; - - void (* tray_icon_added) (NaTrayManager *manager, - NaTrayChild *child); - void (* tray_icon_removed) (NaTrayManager *manager, - NaTrayChild *child); - - void (* message_sent) (NaTrayManager *manager, - NaTrayChild *child, - const gchar *message, - glong id, - glong timeout); - - void (* message_cancelled) (NaTrayManager *manager, - NaTrayChild *child, - glong id); - - void (* lost_selection) (NaTrayManager *manager); -}; - -GType na_tray_manager_get_type (void); - -gboolean na_tray_manager_check_running (GdkScreen *screen); -NaTrayManager *na_tray_manager_new (void); -gboolean na_tray_manager_manage_screen (NaTrayManager *manager, - GdkScreen *screen); -void na_tray_manager_set_orientation (NaTrayManager *manager, - GtkOrientation orientation); -GtkOrientation na_tray_manager_get_orientation (NaTrayManager *manager); -void na_tray_manager_set_padding (NaTrayManager *manager, - gint padding); -void na_tray_manager_set_icon_size (NaTrayManager *manager, - gint padding); -void na_tray_manager_set_colors (NaTrayManager *manager, - GdkRGBA *fg, - GdkRGBA *error, - GdkRGBA *warning, - GdkRGBA *success); - -G_END_DECLS - -#endif /* __NA_TRAY_MANAGER_H__ */ diff --git a/applets/notification_area/na-tray.c b/applets/notification_area/na-tray.c deleted file mode 100644 index 34fb435e..00000000 --- a/applets/notification_area/na-tray.c +++ /dev/null @@ -1,856 +0,0 @@ -/* - * Copyright (C) 2002 Red Hat, Inc. - * Copyright (C) 2003-2006 Vincent Untz - * Copyright (C) 2007 Christian Persch - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#include -#include - -#include - -#include - -#include "na-tray-manager.h" -#include "fixedtip.h" - -#include "na-tray.h" - -#define ICON_SPACING 1 -#define MIN_BOX_SIZE 3 - -typedef struct -{ - NaTrayManager *tray_manager; - GSList *all_trays; - GHashTable *icon_table; - GHashTable *tip_table; -} TraysScreen; - -struct _NaTrayPrivate -{ - GdkScreen *screen; - TraysScreen *trays_screen; - - GtkWidget *box; - - guint idle_redraw_id; - - GtkOrientation orientation; -}; - -typedef struct -{ - char *text; - glong id; - glong timeout; -} IconTipBuffer; - -typedef struct -{ - NaTray *tray; /* tray containing the tray icon */ - GtkWidget *icon; /* tray icon sending the message */ - GtkWidget *fixedtip; - guint source_id; - glong id; /* id of the current message */ - GSList *buffer; /* buffered messages */ -} IconTip; - -enum -{ - PROP_0, - PROP_ORIENTATION, - PROP_SCREEN -}; - -static gboolean initialized = FALSE; -static TraysScreen *trays_screens = NULL; - -static void icon_tip_show_next (IconTip *icontip); - -/* NaTray */ - -G_DEFINE_TYPE (NaTray, na_tray, GTK_TYPE_BIN) - -static NaTray * -get_tray (TraysScreen *trays_screen) -{ - if (trays_screen->all_trays == NULL) - return NULL; - - return trays_screen->all_trays->data; -} - -const char *ordered_roles[] = { - "keyboard", - "volume", - "bluetooth", - "network", - "battery", - NULL -}; - -const char *wmclass_roles[] = { - "Bluetooth-applet", "bluetooth", - "Mate-volume-control-applet", "volume", - "Nm-applet", "network", - "Mate-power-manager", "battery", - "keyboard", "keyboard", - NULL, -}; - -static const char * -find_role (const char *wmclass) -{ - int i; - - for (i = 0; wmclass_roles[i]; i += 2) - { - if (strcmp (wmclass, wmclass_roles[i]) == 0) - return wmclass_roles[i + 1]; - } - - return NULL; -} - -static int -find_role_position (const char *role) -{ - int i; - - for (i = 0; ordered_roles[i]; i++) - { - if (strcmp (role, ordered_roles[i]) == 0) - break; - } - - return i + 1; -} - -static int -find_icon_position (NaTray *tray, - GtkWidget *icon) -{ - NaTrayPrivate *priv; - int position; - char *class_a; - const char *role; - int role_position; - GList *l, *children; - - /* We insert the icons with a known roles in a specific order (the one - * defined by ordered_roles), and all other icons at the beginning of the box - * (left in LTR). */ - - priv = tray->priv; - position = 0; - - class_a = NULL; - na_tray_child_get_wm_class (NA_TRAY_CHILD (icon), NULL, &class_a); - if (!class_a) - return position; - - role = find_role (class_a); - g_free (class_a); - if (!role) - return position; - - role_position = find_role_position (role); - g_object_set_data (G_OBJECT (icon), "role-position", GINT_TO_POINTER (role_position)); - - children = gtk_container_get_children (GTK_CONTAINER (priv->box)); - for (l = g_list_last (children); l; l = l->prev) - { - GtkWidget *child = l->data; - int rp; - - rp = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "role-position")); - if (rp == 0 || rp < role_position) - { - position = g_list_index (children, child) + 1; - break; - } - } - g_list_free (children); - - /* should never happen, but it doesn't hurt to be on the safe side */ - if (position < 0) - position = 0; - - return position; -} - -static void -tray_added (NaTrayManager *manager, - GtkWidget *icon, - TraysScreen *trays_screen) -{ - NaTray *tray; - NaTrayPrivate *priv; - int position; - - tray = get_tray (trays_screen); - if (tray == NULL) - return; - - priv = tray->priv; - - g_assert (priv->trays_screen == trays_screen); - - g_hash_table_insert (trays_screen->icon_table, icon, tray); - - position = find_icon_position (tray, icon); - gtk_box_pack_start (GTK_BOX (priv->box), icon, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (priv->box), icon, position); - - gtk_widget_show (icon); -} - -static void -tray_removed (NaTrayManager *manager, - GtkWidget *icon, - TraysScreen *trays_screen) -{ - NaTray *tray; - NaTrayPrivate *priv; - - tray = g_hash_table_lookup (trays_screen->icon_table, icon); - if (tray == NULL) - return; - - priv = tray->priv; - - g_assert (tray->priv->trays_screen == trays_screen); - - gtk_container_remove (GTK_CONTAINER (priv->box), icon); - - g_hash_table_remove (trays_screen->icon_table, icon); - /* this will also destroy the tip associated to this icon */ - g_hash_table_remove (trays_screen->tip_table, icon); -} - -static void -icon_tip_buffer_free (gpointer data, - gpointer userdata) -{ - IconTipBuffer *buffer; - - buffer = data; - - g_free (buffer->text); - buffer->text = NULL; - - g_free (buffer); -} - -static void -icon_tip_free (gpointer data) -{ - IconTip *icontip; - - if (data == NULL) - return; - - icontip = data; - - if (icontip->fixedtip != NULL) - gtk_widget_destroy (GTK_WIDGET (icontip->fixedtip)); - icontip->fixedtip = NULL; - - if (icontip->source_id != 0) - g_source_remove (icontip->source_id); - icontip->source_id = 0; - - if (icontip->buffer != NULL) - { - g_slist_foreach (icontip->buffer, icon_tip_buffer_free, NULL); - g_slist_free (icontip->buffer); - } - icontip->buffer = NULL; - - g_free (icontip); -} - -static int -icon_tip_buffer_compare (gconstpointer a, - gconstpointer b) -{ - const IconTipBuffer *buffer_a = a; - const IconTipBuffer *buffer_b = b; - - if (buffer_a == NULL || buffer_b == NULL) - return !(buffer_a == buffer_b); - - return buffer_a->id - buffer_b->id; -} - -static void -icon_tip_show_next_clicked (GtkWidget *widget, - gpointer data) -{ - icon_tip_show_next ((IconTip *) data); -} - -static gboolean -icon_tip_show_next_timeout (gpointer data) -{ - IconTip *icontip = (IconTip *) data; - - icon_tip_show_next (icontip); - - return FALSE; -} - -static void -icon_tip_show_next (IconTip *icontip) -{ - IconTipBuffer *buffer; - - if (icontip->buffer == NULL) - { - /* this will also destroy the tip window */ - g_hash_table_remove (icontip->tray->priv->trays_screen->tip_table, - icontip->icon); - return; - } - - if (icontip->source_id != 0) - g_source_remove (icontip->source_id); - icontip->source_id = 0; - - buffer = icontip->buffer->data; - icontip->buffer = g_slist_remove (icontip->buffer, buffer); - - if (icontip->fixedtip == NULL) - { - icontip->fixedtip = na_fixed_tip_new (icontip->icon, - na_tray_get_orientation (icontip->tray)); - - g_signal_connect (icontip->fixedtip, "clicked", - G_CALLBACK (icon_tip_show_next_clicked), icontip); - } - - na_fixed_tip_set_markup (icontip->fixedtip, buffer->text); - - if (!gtk_widget_get_mapped (icontip->fixedtip)) - gtk_widget_show (icontip->fixedtip); - - icontip->id = buffer->id; - - if (buffer->timeout > 0) - icontip->source_id = g_timeout_add_seconds (buffer->timeout, - icon_tip_show_next_timeout, - icontip); - - icon_tip_buffer_free (buffer, NULL); -} - -static void -message_sent (NaTrayManager *manager, - GtkWidget *icon, - const char *text, - glong id, - glong timeout, - TraysScreen *trays_screen) -{ - IconTip *icontip; - IconTipBuffer find_buffer; - IconTipBuffer *buffer; - gboolean show_now; - - icontip = g_hash_table_lookup (trays_screen->tip_table, icon); - - find_buffer.id = id; - if (icontip && - (icontip->id == id || - g_slist_find_custom (icontip->buffer, &find_buffer, - icon_tip_buffer_compare) != NULL)) - /* we already have this message, so ignore it */ - /* FIXME: in an ideal world, we'd remember all the past ids and ignore them - * too */ - return; - - show_now = FALSE; - - if (icontip == NULL) - { - NaTray *tray; - - tray = g_hash_table_lookup (trays_screen->icon_table, icon); - if (tray == NULL) - { - /* We don't know about the icon sending the message, so ignore it. - * But this should never happen since NaTrayManager shouldn't send - * us the message if there's no socket for it. */ - g_critical ("Ignoring a message sent by a tray icon " - "we don't know: \"%s\".\n", text); - return; - } - - icontip = g_new0 (IconTip, 1); - icontip->tray = tray; - icontip->icon = icon; - - g_hash_table_insert (trays_screen->tip_table, icon, icontip); - - show_now = TRUE; - } - - buffer = g_new0 (IconTipBuffer, 1); - - buffer->text = g_strdup (text); - buffer->id = id; - buffer->timeout = timeout; - - icontip->buffer = g_slist_append (icontip->buffer, buffer); - - if (show_now) - icon_tip_show_next (icontip); -} - -static void -message_cancelled (NaTrayManager *manager, - GtkWidget *icon, - glong id, - TraysScreen *trays_screen) -{ - IconTip *icontip; - IconTipBuffer find_buffer; - GSList *cancel_buffer_l; - IconTipBuffer *cancel_buffer; - - icontip = g_hash_table_lookup (trays_screen->tip_table, icon); - if (icontip == NULL) - return; - - if (icontip->id == id) - { - icon_tip_show_next (icontip); - return; - } - - find_buffer.id = id; - cancel_buffer_l = g_slist_find_custom (icontip->buffer, &find_buffer, - icon_tip_buffer_compare); - if (cancel_buffer_l == NULL) - return; - - cancel_buffer = cancel_buffer_l->data; - icon_tip_buffer_free (cancel_buffer, NULL); - - icontip->buffer = g_slist_remove_link (icontip->buffer, cancel_buffer_l); - g_slist_free_1 (cancel_buffer_l); -} - -static void -update_orientation_for_messages (gpointer key, - gpointer value, - gpointer data) -{ - NaTray *tray; - IconTip *icontip; - - if (value == NULL) - return; - - icontip = value; - tray = data; - if (icontip->tray != tray) - return; - - if (icontip->fixedtip) - na_fixed_tip_set_orientation (icontip->fixedtip, tray->priv->orientation); -} - -static void -update_size_and_orientation (NaTray *tray) -{ - NaTrayPrivate *priv = tray->priv; - - gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), priv->orientation); - - /* This only happens when setting the property during object construction */ - if (!priv->trays_screen) - return; - - g_hash_table_foreach (priv->trays_screen->tip_table, - update_orientation_for_messages, tray); - - if (get_tray (priv->trays_screen) == tray) - na_tray_manager_set_orientation (priv->trays_screen->tray_manager, - priv->orientation); - - /* note, you want this larger if the frame has non-NONE relief by default. */ - switch (priv->orientation) - { - case GTK_ORIENTATION_VERTICAL: - /* Give box a min size so the frame doesn't look dumb */ - gtk_widget_set_size_request (priv->box, MIN_BOX_SIZE, -1); - break; - case GTK_ORIENTATION_HORIZONTAL: - gtk_widget_set_size_request (priv->box, -1, MIN_BOX_SIZE); - break; - } -} - -/* Children with alpha channels have been set to be composited by calling - * gdk_window_set_composited(). We need to paint these children ourselves. - */ -static void -na_tray_draw_icon (GtkWidget *widget, - gpointer data) -{ - cairo_t *cr = (cairo_t *) data; - - if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget))) - { - GtkAllocation allocation; - - gtk_widget_get_allocation (widget, &allocation); - - cairo_save (cr); - gdk_cairo_set_source_window (cr, - gtk_widget_get_window (widget), - allocation.x, - allocation.y); - cairo_rectangle (cr, allocation.x, allocation.y, allocation.width, allocation.height); - cairo_clip (cr); - cairo_paint (cr); - cairo_restore (cr); - } -} - -static void -na_tray_draw_box (GtkWidget *box, - cairo_t *cr) -{ - gtk_container_foreach (GTK_CONTAINER (box), na_tray_draw_icon, cr); -} - -static void -na_tray_init (NaTray *tray) -{ - NaTrayPrivate *priv; - - priv = tray->priv = G_TYPE_INSTANCE_GET_PRIVATE (tray, NA_TYPE_TRAY, NaTrayPrivate); - - priv->screen = NULL; - priv->orientation = GTK_ORIENTATION_HORIZONTAL; - - priv->box = gtk_box_new (priv->orientation, ICON_SPACING); - g_signal_connect (priv->box, "draw", - G_CALLBACK (na_tray_draw_box), NULL); - gtk_container_add (GTK_CONTAINER (tray), priv->box); - gtk_widget_show (priv->box); -} - -static GObject * -na_tray_constructor (GType type, - guint n_construct_properties, - GObjectConstructParam *construct_params) -{ - GObject *object; - NaTray *tray; - NaTrayPrivate *priv; - int screen_number; - - object = G_OBJECT_CLASS (na_tray_parent_class)->constructor (type, - n_construct_properties, - construct_params); - tray = NA_TRAY (object); - priv = tray->priv; - - g_assert (priv->screen != NULL); - - if (!initialized) - { - trays_screens = g_new0 (TraysScreen, 1); - initialized = TRUE; - } - - screen_number = gdk_screen_get_number (priv->screen); - - if (trays_screens [screen_number].tray_manager == NULL) - { - NaTrayManager *tray_manager; - - tray_manager = na_tray_manager_new (); - - if (na_tray_manager_manage_screen (tray_manager, priv->screen)) - { - trays_screens [screen_number].tray_manager = tray_manager; - - g_signal_connect (tray_manager, "tray_icon_added", - G_CALLBACK (tray_added), - &trays_screens [screen_number]); - g_signal_connect (tray_manager, "tray_icon_removed", - G_CALLBACK (tray_removed), - &trays_screens [screen_number]); - g_signal_connect (tray_manager, "message_sent", - G_CALLBACK (message_sent), - &trays_screens [screen_number]); - g_signal_connect (tray_manager, "message_cancelled", - G_CALLBACK (message_cancelled), - &trays_screens [screen_number]); - - trays_screens [screen_number].icon_table = g_hash_table_new (NULL, - NULL); - trays_screens [screen_number].tip_table = g_hash_table_new_full ( - NULL, - NULL, - NULL, - icon_tip_free); - } - else - { - g_printerr ("System tray didn't get the system tray manager selection for screen %d\n", - screen_number); - g_object_unref (tray_manager); - } - } - - priv->trays_screen = &trays_screens [screen_number]; - trays_screens [screen_number].all_trays = g_slist_append (trays_screens [screen_number].all_trays, - tray); - - update_size_and_orientation (tray); - - return object; -} - -static void -na_tray_dispose (GObject *object) -{ - NaTray *tray = NA_TRAY (object); - NaTrayPrivate *priv = tray->priv; - TraysScreen *trays_screen = priv->trays_screen; - - if (trays_screen != NULL) - { - trays_screen->all_trays = g_slist_remove (trays_screen->all_trays, tray); - - if (trays_screen->all_trays == NULL) - { - /* Make sure we drop the manager selection */ - g_object_unref (trays_screen->tray_manager); - trays_screen->tray_manager = NULL; - - g_hash_table_destroy (trays_screen->icon_table); - trays_screen->icon_table = NULL; - - g_hash_table_destroy (trays_screen->tip_table); - trays_screen->tip_table = NULL; - } - else - { - NaTray *new_tray; - - new_tray = get_tray (trays_screen); - if (new_tray != NULL) - na_tray_manager_set_orientation (trays_screen->tray_manager, - na_tray_get_orientation (new_tray)); - } - } - - priv->trays_screen = NULL; - - if (priv->idle_redraw_id != 0) - { - g_source_remove (priv->idle_redraw_id); - priv->idle_redraw_id = 0; - } - - G_OBJECT_CLASS (na_tray_parent_class)->dispose (object); -} - -static void -na_tray_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - NaTray *tray = NA_TRAY (object); - NaTrayPrivate *priv = tray->priv; - - switch (prop_id) - { - case PROP_ORIENTATION: - na_tray_set_orientation (tray, g_value_get_enum (value)); - break; - case PROP_SCREEN: - priv->screen = g_value_get_object (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -na_tray_get_preferred_width (GtkWidget *widget, - gint *minimal_width, - gint *natural_width) -{ - gtk_widget_get_preferred_width (gtk_bin_get_child (GTK_BIN (widget)), - minimal_width, - natural_width); -} - -static void -na_tray_get_preferred_height (GtkWidget *widget, - gint *minimal_height, - gint *natural_height) -{ - gtk_widget_get_preferred_height (gtk_bin_get_child (GTK_BIN (widget)), - minimal_height, - natural_height); -} - -static void -na_tray_size_allocate (GtkWidget *widget, - GtkAllocation *allocation) -{ - gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), allocation); - gtk_widget_set_allocation (widget, allocation); -} - -static void -na_tray_class_init (NaTrayClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - gobject_class->constructor = na_tray_constructor; - gobject_class->set_property = na_tray_set_property; - gobject_class->dispose = na_tray_dispose; - - widget_class->get_preferred_width = na_tray_get_preferred_width; - widget_class->get_preferred_height = na_tray_get_preferred_height; - widget_class->size_allocate = na_tray_size_allocate; - - g_object_class_install_property - (gobject_class, - PROP_ORIENTATION, - g_param_spec_enum ("orientation", "orientation", "orientation", - GTK_TYPE_ORIENTATION, - GTK_ORIENTATION_HORIZONTAL, - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_NICK | - G_PARAM_STATIC_BLURB)); - - g_object_class_install_property - (gobject_class, - PROP_SCREEN, - g_param_spec_object ("screen", "screen", "screen", - GDK_TYPE_SCREEN, - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_NAME | - G_PARAM_STATIC_NICK | - G_PARAM_STATIC_BLURB)); - - g_type_class_add_private (gobject_class, sizeof (NaTrayPrivate)); -} - -NaTray * -na_tray_new_for_screen (GdkScreen *screen, - GtkOrientation orientation) -{ - return g_object_new (NA_TYPE_TRAY, - "screen", screen, - "orientation", orientation, - NULL); -} - -void -na_tray_set_orientation (NaTray *tray, - GtkOrientation orientation) -{ - NaTrayPrivate *priv = tray->priv; - - if (orientation == priv->orientation) - return; - - priv->orientation = orientation; - - update_size_and_orientation (tray); -} - -GtkOrientation -na_tray_get_orientation (NaTray *tray) -{ - return tray->priv->orientation; -} - -static gboolean -idle_redraw_cb (NaTray *tray) -{ - NaTrayPrivate *priv = tray->priv; - - gtk_container_foreach (GTK_CONTAINER (priv->box), (GtkCallback)na_tray_child_force_redraw, tray); - - priv->idle_redraw_id = 0; - - return FALSE; -} - -void -na_tray_set_padding (NaTray *tray, - gint padding) -{ - NaTrayPrivate *priv = tray->priv; - - if (get_tray (priv->trays_screen) == tray) - na_tray_manager_set_padding (priv->trays_screen->tray_manager, padding); -} - -void -na_tray_set_icon_size (NaTray *tray, - gint size) -{ - NaTrayPrivate *priv = tray->priv; - - if (get_tray (priv->trays_screen) == tray) - na_tray_manager_set_icon_size (priv->trays_screen->tray_manager, size); -} - -void -na_tray_set_colors (NaTray *tray, - GdkRGBA *fg, - GdkRGBA *error, - GdkRGBA *warning, - GdkRGBA *success) -{ - NaTrayPrivate *priv = tray->priv; - - if (get_tray (priv->trays_screen) == tray) - na_tray_manager_set_colors (priv->trays_screen->tray_manager, fg, error, warning, success); -} - -void -na_tray_force_redraw (NaTray *tray) -{ - NaTrayPrivate *priv = tray->priv; - - /* Force the icons to redraw their backgrounds. - */ - if (priv->idle_redraw_id == 0) - priv->idle_redraw_id = g_idle_add ((GSourceFunc) idle_redraw_cb, tray); -} diff --git a/applets/notification_area/na-tray.h b/applets/notification_area/na-tray.h deleted file mode 100644 index 8ecac1cb..00000000 --- a/applets/notification_area/na-tray.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* na-tray-tray.h - * Copyright (C) 2002 Anders Carlsson - * Copyright (C) 2003-2006 Vincent Untz - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Used to be: eggtraytray.h - */ - -#ifndef __NA_TRAY_H__ -#define __NA_TRAY_H__ - -#ifdef GDK_WINDOWING_X11 -#include -#endif -#include - -G_BEGIN_DECLS - -#define NA_TYPE_TRAY (na_tray_get_type ()) -#define NA_TRAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY, NaTray)) -#define NA_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY, NaTrayClass)) -#define NA_IS_TRAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY)) -#define NA_IS_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY)) -#define NA_TRAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY, NaTrayClass)) - -typedef struct _NaTray NaTray; -typedef struct _NaTrayPrivate NaTrayPrivate; -typedef struct _NaTrayClass NaTrayClass; - -struct _NaTray -{ - GtkBin parent_instance; - - NaTrayPrivate *priv; -}; - -struct _NaTrayClass -{ - GtkBinClass parent_class; -}; - -GType na_tray_get_type (void); -NaTray *na_tray_new_for_screen (GdkScreen *screen, - GtkOrientation orientation); -void na_tray_set_orientation (NaTray *tray, - GtkOrientation orientation); -GtkOrientation na_tray_get_orientation (NaTray *tray); -void na_tray_set_padding (NaTray *tray, - gint padding); -void na_tray_set_icon_size (NaTray *tray, - gint icon_size); -void na_tray_set_colors (NaTray *tray, - GdkRGBA *fg, - GdkRGBA *error, - GdkRGBA *warning, - GdkRGBA *success); -void na_tray_force_redraw (NaTray *tray); - -G_END_DECLS - -#endif /* __NA_TRAY_H__ */ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 . + */ + +#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 . + */ + +#ifndef SN_DBUS_MENU_ITEM_H +#define SN_DUBS_MENU_ITEM_H + +#include + +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 . + */ + +#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 . + */ + +#ifndef SN_DBUS_MENU_H +#define SN_DBUS_MENU_H + +#include + +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 . + */ + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#ifndef SN_IMAGE_MENU_ITEM_H +#define SN_IMAGE_MENU_ITEM_H + +#include + +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 + * + * 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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#ifndef SN_ITEM_H +#define SN_ITEM_H + +#include + +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 diff --git a/applets/notification_area/system-tray/Makefile.am b/applets/notification_area/system-tray/Makefile.am new file mode 100644 index 00000000..f3e8c360 --- /dev/null +++ b/applets/notification_area/system-tray/Makefile.am @@ -0,0 +1,42 @@ + +noinst_LTLIBRARIES = libsystem-tray.la + +AM_CPPFLAGS = \ + $(NOTIFICATION_AREA_CFLAGS) \ + -I$(srcdir) \ + -I$(srcdir)/.. \ + -DMATELOCALEDIR=\""$(datadir)/locale"\" \ + -DG_LOG_DOMAIN=\""notification-area-applet"\" \ + $(DISABLE_DEPRECATED_CFLAGS) + +AM_CFLAGS = $(WARN_CFLAGS) + +libsystem_tray_la_SOURCES = \ + fixedtip.h \ + fixedtip.c \ + na-marshal.c \ + na-marshal.h \ + na-tray.c \ + na-tray.h \ + na-tray-child.c \ + na-tray-child.h \ + na-tray-manager.c \ + na-tray-manager.h + +libsystem_tray_la_LIBADD = \ + $(X_LIBS) \ + $(NOTIFICATION_AREA_LIBS) + +na-marshal.h: na-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)$(GLIB_GENMARSHAL) $< --header --prefix=_na_marshal > $@ + +na-marshal.c: na-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN)echo "#include \"na-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=_na_marshal >> $@ + +BUILT_SOURCES = na-marshal.c na-marshal.h + +EXTRA_DIST = \ + na-marshal.list + +-include $(top_srcdir)/git.mk diff --git a/applets/notification_area/system-tray/fixedtip.c b/applets/notification_area/system-tray/fixedtip.c new file mode 100644 index 00000000..6356de9b --- /dev/null +++ b/applets/notification_area/system-tray/fixedtip.c @@ -0,0 +1,281 @@ +/* Marco fixed tooltip routine */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003-2006 Vincent Untz + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "fixedtip.h" + +/* Signals */ +enum +{ + CLICKED, + LAST_SIGNAL +}; + +static guint fixedtip_signals[LAST_SIGNAL] = { 0 }; + +struct _NaFixedTipPrivate +{ + GtkWidget *parent; + GtkWidget *label; + GtkOrientation orientation; +}; + +G_DEFINE_TYPE (NaFixedTip, na_fixed_tip, GTK_TYPE_WINDOW) + +static gboolean +button_press_handler (GtkWidget *fixedtip, + GdkEventButton *event, + gpointer data) +{ + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) + g_signal_emit (fixedtip, fixedtip_signals[CLICKED], 0); + + return FALSE; +} + +static gboolean +na_fixed_tip_draw (GtkWidget *widget, cairo_t *cr) +{ + GtkStyleContext *context; + GtkStateFlags state; + int width, height; + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + state = gtk_widget_get_state_flags (widget); + context = gtk_widget_get_style_context (widget); + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOOLTIP); + gtk_style_context_set_state (context, state); + + cairo_save (cr); + gtk_render_background (context, cr, + 0., 0., + (gdouble)width, + (gdouble)height); + cairo_restore (cr); + + gtk_style_context_restore (context); + + return FALSE; +} + +static void +na_fixed_tip_class_init (NaFixedTipClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + widget_class->draw = na_fixed_tip_draw; + + fixedtip_signals[CLICKED] = + g_signal_new ("clicked", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaFixedTipClass, clicked), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (class, sizeof (NaFixedTipPrivate)); +} + +/* Did you already see this code? Yes, it's gtk_tooltips_ force_window() ;-) */ +static void +na_fixed_tip_init (NaFixedTip *fixedtip) +{ + GtkWidget *label; + + fixedtip->priv = G_TYPE_INSTANCE_GET_PRIVATE (fixedtip, NA_TYPE_FIXED_TIP, + NaFixedTipPrivate); + + gtk_window_set_type_hint (GTK_WINDOW (fixedtip), + GDK_WINDOW_TYPE_HINT_TOOLTIP); + + gtk_widget_set_app_paintable (GTK_WIDGET (fixedtip), TRUE); + gtk_window_set_resizable (GTK_WINDOW (fixedtip), FALSE); + gtk_widget_set_name (GTK_WIDGET (fixedtip), "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (fixedtip), 4); + + label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); +#if GTK_CHECK_VERSION (3, 16, 0) + gtk_label_set_xalign (GTK_LABEL (label), 0.5); + gtk_label_set_yalign (GTK_LABEL (label), 0.5); +#else + gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); +#endif + gtk_widget_show (label); + gtk_container_add (GTK_CONTAINER (fixedtip), label); + fixedtip->priv->label = label; + + gtk_widget_add_events (GTK_WIDGET (fixedtip), GDK_BUTTON_PRESS_MASK); + + g_signal_connect (fixedtip, "button_press_event", + G_CALLBACK (button_press_handler), NULL); + + fixedtip->priv->orientation = GTK_ORIENTATION_HORIZONTAL; +} + +static void +na_fixed_tip_position (NaFixedTip *fixedtip) +{ + GdkScreen *screen; + GdkWindow *parent_window; + GtkRequisition req; + int root_x; + int root_y; + int parent_width; + int parent_height; + int screen_width; + int screen_height; + + screen = gtk_widget_get_screen (fixedtip->priv->parent); + parent_window = gtk_widget_get_window (fixedtip->priv->parent); + + gtk_window_set_screen (GTK_WINDOW (fixedtip), screen); + + gtk_widget_get_preferred_size (GTK_WIDGET (fixedtip), &req, NULL); + + gdk_window_get_origin (parent_window, &root_x, &root_y); + parent_width = gdk_window_get_width(parent_window); + parent_height = gdk_window_get_height(parent_window); + + screen_width = gdk_screen_get_width (screen); + screen_height = gdk_screen_get_height (screen); + + /* pad between panel and message window */ +#define PAD 5 + + if (fixedtip->priv->orientation == GTK_ORIENTATION_VERTICAL) + { + if (root_x <= screen_width / 2) + root_x += parent_width + PAD; + else + root_x -= req.width + PAD; + } + else + { + if (root_y <= screen_height / 2) + root_y += parent_height + PAD; + else + root_y -= req.height + PAD; + } + + /* Push onscreen */ + if ((root_x + req.width) > screen_width) + root_x = screen_width - req.width; + + if ((root_y + req.height) > screen_height) + root_y = screen_height - req.height; + + gtk_window_move (GTK_WINDOW (fixedtip), root_x, root_y); +} + +static void +na_fixed_tip_parent_size_allocated (GtkWidget *parent, + GtkAllocation *allocation, + NaFixedTip *fixedtip) +{ + na_fixed_tip_position (fixedtip); +} + +static void +na_fixed_tip_parent_screen_changed (GtkWidget *parent, + GdkScreen *new_screen, + NaFixedTip *fixedtip) +{ + na_fixed_tip_position (fixedtip); +} + +GtkWidget * +na_fixed_tip_new (GtkWidget *parent, + GtkOrientation orientation) +{ + NaFixedTip *fixedtip; + + g_return_val_if_fail (parent != NULL, NULL); + + fixedtip = g_object_new (NA_TYPE_FIXED_TIP, + "type", GTK_WINDOW_POPUP, + NULL); + + fixedtip->priv->parent = parent; + +#if 0 + //FIXME: would be nice to be able to get the toplevel for the tip, but this + //doesn't work + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (parent); + /* + if (toplevel && gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel)) + gtk_window_set_transient_for (GTK_WINDOW (fixedtip), GTK_WINDOW (toplevel)); + */ +#endif + + fixedtip->priv->orientation = orientation; + + //FIXME: would be nice to move the tip when the notification area moves + g_signal_connect_object (parent, "size-allocate", + G_CALLBACK (na_fixed_tip_parent_size_allocated), + fixedtip, 0); + g_signal_connect_object (parent, "screen-changed", + G_CALLBACK (na_fixed_tip_parent_screen_changed), + fixedtip, 0); + + na_fixed_tip_position (fixedtip); + + return GTK_WIDGET (fixedtip); +} + +void +na_fixed_tip_set_markup (GtkWidget *widget, + const char *markup_text) +{ + NaFixedTip *fixedtip; + + g_return_if_fail (NA_IS_FIXED_TIP (widget)); + + fixedtip = NA_FIXED_TIP (widget); + + gtk_label_set_markup (GTK_LABEL (fixedtip->priv->label), + markup_text); + + na_fixed_tip_position (fixedtip); +} + +void +na_fixed_tip_set_orientation (GtkWidget *widget, + GtkOrientation orientation) +{ + NaFixedTip *fixedtip; + + g_return_if_fail (NA_IS_FIXED_TIP (widget)); + + fixedtip = NA_FIXED_TIP (widget); + + if (orientation == fixedtip->priv->orientation) + return; + + fixedtip->priv->orientation = orientation; + + na_fixed_tip_position (fixedtip); +} diff --git a/applets/notification_area/system-tray/fixedtip.h b/applets/notification_area/system-tray/fixedtip.h new file mode 100644 index 00000000..c220f103 --- /dev/null +++ b/applets/notification_area/system-tray/fixedtip.h @@ -0,0 +1,72 @@ +/* Fixed tooltip routine */ + +/* + * Copyright (C) 2001 Havoc Pennington, 2002 Red Hat Inc. + * Copyright (C) 2003-2006 Vincent Untz + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef FIXED_TIP_H +#define FIXED_TIP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NA_TYPE_FIXED_TIP (na_fixed_tip_get_type ()) +#define NA_FIXED_TIP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_FIXED_TIP, NaFixedTip)) +#define NA_FIXED_TIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_FIXED_TIP, NaFixedTipClass)) +#define NA_IS_FIXED_TIP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_FIXED_TIP)) +#define NA_IS_FIXED_TIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_FIXED_TIP)) +#define NA_FIXED_TIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_FIXED_TIP, NaFixedTipClass)) + +typedef struct _NaFixedTip NaFixedTip; +typedef struct _NaFixedTipPrivate NaFixedTipPrivate; +typedef struct _NaFixedTipClass NaFixedTipClass; + +struct _NaFixedTip +{ + GtkWindow parent_instance; + + NaFixedTipPrivate *priv; +}; + +struct _NaFixedTipClass +{ + GtkWindowClass parent_class; + + void (* clicked) (NaFixedTip *fixedtip); +}; + +GType na_fixed_tip_get_type (void); + +GtkWidget *na_fixed_tip_new (GtkWidget *parent, + GtkOrientation orientation); + +void na_fixed_tip_set_markup (GtkWidget *widget, + const char *markup_text); + +void na_fixed_tip_set_orientation (GtkWidget *widget, + GtkOrientation orientation); + +#ifdef __cplusplus +} +#endif + +#endif /* FIXED_TIP_H */ diff --git a/applets/notification_area/system-tray/na-marshal.list b/applets/notification_area/system-tray/na-marshal.list new file mode 100644 index 00000000..e3fc3993 --- /dev/null +++ b/applets/notification_area/system-tray/na-marshal.list @@ -0,0 +1,3 @@ +VOID:OBJECT,OBJECT +VOID:OBJECT,STRING,LONG,LONG +VOID:OBJECT,LONG diff --git a/applets/notification_area/system-tray/na-tray-child.c b/applets/notification_area/system-tray/na-tray-child.c new file mode 100644 index 00000000..2a87f1e1 --- /dev/null +++ b/applets/notification_area/system-tray/na-tray-child.c @@ -0,0 +1,715 @@ +/* na-tray-child.c + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include "na-tray-child.h" + +#include +#include +#include +#include +#include + +#include "na-item.h" + +enum +{ + PROP_0, + PROP_ORIENTATION +}; + +static void na_item_init (NaItemInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NaTrayChild, na_tray_child, GTK_TYPE_SOCKET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) + G_IMPLEMENT_INTERFACE (NA_TYPE_ITEM, na_item_init)) + +static void +na_tray_child_finalize (GObject *object) +{ + NaTrayChild *child = NA_TRAY_CHILD (object); + + g_clear_pointer (&child->id, g_free); + + G_OBJECT_CLASS (na_tray_child_parent_class)->finalize (object); +} + +static void +na_tray_child_realize (GtkWidget *widget) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + GdkVisual *visual = gtk_widget_get_visual (widget); + GdkWindow *window; + + GTK_WIDGET_CLASS (na_tray_child_parent_class)->realize (widget); + + window = gtk_widget_get_window (widget); + + if (child->has_alpha) + { + /* We have real transparency with an ARGB visual and the Composite + * extension. */ + + /* Set a transparent background */ + cairo_pattern_t *transparent = cairo_pattern_create_rgba (0, 0, 0, 0); + gdk_window_set_background_pattern (window, transparent); + gdk_window_set_composited (window, TRUE); + cairo_pattern_destroy (transparent); + + child->parent_relative_bg = FALSE; + } + else if (visual == gdk_window_get_visual(gdk_window_get_parent(window))) + { + /* Otherwise, if the visual matches the visual of the parent window, we + * can use a parent-relative background and fake transparency. */ + gdk_window_set_background_pattern (window, NULL); + + child->parent_relative_bg = TRUE; + } + else + { + /* Nothing to do; the icon will sit on top of an ugly gray box */ + child->parent_relative_bg = FALSE; + } + + gdk_window_set_composited (window, child->composited); + + gtk_widget_set_app_paintable (GTK_WIDGET (child), + child->parent_relative_bg || child->has_alpha); + + /* Double-buffering will interfere with the parent-relative-background fake + * transparency, since the double-buffer code doesn't know how to fill in the + * background of the double-buffer correctly. + */ + gtk_widget_set_double_buffered (GTK_WIDGET (child), + child->parent_relative_bg); +} + +static void +na_tray_child_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + /* The default handler resets the background according to the new style. + * We either use a transparent background or a parent-relative background + * and ignore the style background. So, just don't chain up. + */ +} + +static void +na_tray_child_get_preferred_width (GtkWidget *widget, + gint *minimal_width, + gint *natural_width) +{ + GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_width (widget, + minimal_width, + natural_width); + + if (*minimal_width < 16) + *minimal_width = 16; + + if (*natural_width < 16) + *natural_width = 16; +} + +static void +na_tray_child_get_preferred_height (GtkWidget *widget, + gint *minimal_height, + gint *natural_height) +{ + GTK_WIDGET_CLASS (na_tray_child_parent_class)->get_preferred_height (widget, + minimal_height, + natural_height); + + if (*minimal_height < 16) + *minimal_height = 16; + + if (*natural_height < 16) + *natural_height = 16; +} + +static void +na_tray_child_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + GtkAllocation widget_allocation; + gboolean moved, resized; + + gtk_widget_get_allocation (widget, &widget_allocation); + + moved = (allocation->x != widget_allocation.x || + allocation->y != widget_allocation.y); + resized = (allocation->width != widget_allocation.width || + allocation->height != widget_allocation.height); + + /* When we are allocating the widget while mapped we need special handling + * for both real and fake transparency. + * + * Real transparency: we need to invalidate and trigger a redraw of the old + * and new areas. (GDK really should handle this for us, but doesn't as of + * GTK+-2.14) + * + * Fake transparency: if the widget moved, we need to force the contents to + * be redrawn with the new offset for the parent-relative background. + */ + if ((moved || resized) && gtk_widget_get_mapped (widget)) + { + if (na_tray_child_has_alpha (child)) + gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), + &widget_allocation, FALSE); + } + + GTK_WIDGET_CLASS (na_tray_child_parent_class)->size_allocate (widget, + allocation); + + if ((moved || resized) && gtk_widget_get_mapped (widget)) + { + if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget))) + gdk_window_invalidate_rect (gdk_window_get_parent (gtk_widget_get_window (widget)), + &widget_allocation, FALSE); + else if (moved && child->parent_relative_bg) + na_tray_child_force_redraw (child); + } +} + +/* The plug window should completely occupy the area of the child, so we won't + * get an expose event. But in case we do (the plug unmaps itself, say), this + * expose handler draws with real or fake transparency. + */ +static gboolean +na_tray_child_draw (GtkWidget *widget, + cairo_t *cr) +{ + NaTrayChild *child = NA_TRAY_CHILD (widget); + + if (na_tray_child_has_alpha (child)) + { + /* Clear to transparent */ + cairo_set_source_rgba (cr, 0, 0, 0, 0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + } + else if (child->parent_relative_bg) + { + /* Clear to parent-relative pixmap */ + GdkWindow *window; + cairo_surface_t *target; + GdkRectangle clip_rect; + + window = gtk_widget_get_window (widget); + target = cairo_get_group_target (cr); + + gdk_cairo_get_clip_rectangle (cr, &clip_rect); + + /* Clear to parent-relative pixmap + * We need to use direct X access here because GDK doesn't know about + * the parent relative pixmap. */ + cairo_surface_flush (target); + + XClearArea (GDK_WINDOW_XDISPLAY (window), + GDK_WINDOW_XID (window), + clip_rect.x, clip_rect.y, + clip_rect.width, clip_rect.height, + False); + cairo_surface_mark_dirty_rectangle (target, + clip_rect.x, clip_rect.y, + clip_rect.width, clip_rect.height); + } + + return FALSE; +} + +/* Children with alpha channels have been set to be composited by calling + * gdk_window_set_composited(). We need to paint these children ourselves. + * + * FIXME: is that still needed on GTK3? Seems like it could be done in draw(). + */ +static gboolean +na_tray_child_draw_on_parent (NaItem *item, + GtkWidget *parent, + cairo_t *parent_cr) +{ + if (na_tray_child_has_alpha (NA_TRAY_CHILD (item))) + { + GtkWidget *widget = GTK_WIDGET (item); + GtkAllocation parent_allocation = { 0 }; + GtkAllocation allocation; + + /* if the parent doesn't have a window, our allocation is not relative to + * the context coordinates but to the parent's allocation */ + if (! gtk_widget_get_has_window (parent)) + gtk_widget_get_allocation (parent, &parent_allocation); + + gtk_widget_get_allocation (widget, &allocation); + allocation.x -= parent_allocation.x; + allocation.y -= parent_allocation.y; + + cairo_save (parent_cr); + gdk_cairo_set_source_window (parent_cr, + gtk_widget_get_window (widget), + allocation.x, + allocation.y); + cairo_rectangle (parent_cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_clip (parent_cr); + cairo_paint (parent_cr); + cairo_restore (parent_cr); + } + + return TRUE; +} + +static void +na_tray_child_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_ORIENTATION: + /* whatever */ + g_value_set_enum (value, GTK_ORIENTATION_HORIZONTAL); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +na_tray_child_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_ORIENTATION: + /* we so don't care */ + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static const gchar * +na_tray_child_get_id (NaItem *item) +{ + NaTrayChild *child = NA_TRAY_CHILD (item); + + if (! child->id) + { + char *res_name = NULL; + + na_tray_child_get_wm_class (child, &res_name, NULL); + child->id = g_strdup_printf ("%s-%lu", res_name, child->icon_window); + g_free (res_name); + } + + return child->id; +} + +static NaItemCategory +na_tray_child_get_category (NaItem *item) +{ + const struct + { + const gchar *wm_class; + NaItemCategory category; + } wmclass_categories[] = { +/* FIXME: get the same order as it used to be, without fake categories. + * from right to left: +const char *ordered_roles[] = { + "keyboard", + "volume", + "bluetooth", + "network", + "battery", + NULL +}; + +const char *wmclass_roles[] = { + "Bluetooth-applet", "bluetooth", + "Mate-volume-control-applet", "volume", + "Nm-applet", "network", + "Mate-power-manager", "battery", + "keyboard", "keyboard", + NULL, +}; +*/ + { "Bluetooth-applet", NA_ITEM_CATEGORY_FAKE_BLUETOOTH }, + { "Mate-volume-control-applet", NA_ITEM_CATEGORY_FAKE_VOLUME }, + { "Nm-applet", NA_ITEM_CATEGORY_FAKE_NETWORK }, + { "Mate-power-manager", NA_ITEM_CATEGORY_FAKE_BATTERY }, + { "keyboard", NA_ITEM_CATEGORY_FAKE_KEYBOARD } + }; + guint i; + NaItemCategory category = NA_ITEM_CATEGORY_APPLICATION_STATUS; + char *res_class = NULL; + + na_tray_child_get_wm_class (NA_TRAY_CHILD (item), NULL, &res_class); + + for (i = 0; i < G_N_ELEMENTS (wmclass_categories); i++) + { + if (g_strcmp0 (res_class, wmclass_categories[i].wm_class) == 0) + { + category = wmclass_categories[i].category; + break; + } + } + + return category; +} + +static void +na_item_init (NaItemInterface *iface) +{ + iface->get_id = na_tray_child_get_id; + iface->get_category = na_tray_child_get_category; + + iface->draw_on_parent = na_tray_child_draw_on_parent; +} + +static void +na_tray_child_init (NaTrayChild *child) +{ + child->id = NULL; +} + +static void +na_tray_child_class_init (NaTrayChildClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass *)klass; + widget_class = (GtkWidgetClass *)klass; + + gobject_class->finalize = na_tray_child_finalize; + gobject_class->get_property = na_tray_child_get_property; + gobject_class->set_property = na_tray_child_set_property; + + widget_class->style_set = na_tray_child_style_set; + widget_class->realize = na_tray_child_realize; + widget_class->get_preferred_width = na_tray_child_get_preferred_width; + widget_class->get_preferred_height = na_tray_child_get_preferred_height; + widget_class->size_allocate = na_tray_child_size_allocate; + widget_class->draw = na_tray_child_draw; + + /* we don't really care actually */ + g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); +} + +GtkWidget * +na_tray_child_new (GdkScreen *screen, + Window icon_window) +{ + XWindowAttributes window_attributes; + Display *xdisplay; + NaTrayChild *child; + GdkVisual *visual; + gboolean visual_has_alpha; + int red_prec, green_prec, blue_prec, depth; + int result; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + g_return_val_if_fail (icon_window != None, NULL); + + xdisplay = GDK_SCREEN_XDISPLAY (screen); + + /* We need to determine the visual of the window we are embedding and create + * the socket in the same visual. + */ + + gdk_error_trap_push (); + result = XGetWindowAttributes (xdisplay, icon_window, + &window_attributes); + gdk_error_trap_pop_ignored (); + + if (!result) /* Window already gone */ + return NULL; + + visual = gdk_x11_screen_lookup_visual (screen, + window_attributes.visual->visualid); + if (!visual) /* Icon window is on another screen? */ + return NULL; + + child = g_object_new (NA_TYPE_TRAY_CHILD, NULL); + child->icon_window = icon_window; + + gtk_widget_set_visual (GTK_WIDGET (child), visual); + + /* We have alpha if the visual has something other than red, green, + * and blue */ + gdk_visual_get_red_pixel_details (visual, NULL, NULL, &red_prec); + gdk_visual_get_green_pixel_details (visual, NULL, NULL, &green_prec); + gdk_visual_get_blue_pixel_details (visual, NULL, NULL, &blue_prec); + depth = gdk_visual_get_depth (visual); + + visual_has_alpha = red_prec + blue_prec + green_prec < depth; + child->has_alpha = (visual_has_alpha && + gdk_display_supports_composite (gdk_screen_get_display (screen))); + + child->composited = child->has_alpha; + + return GTK_WIDGET (child); +} + +char * +na_tray_child_get_title (NaTrayChild *child) +{ + char *retval = NULL; + GdkDisplay *display; + Atom utf8_string, atom, type; + int result; + int format; + gulong nitems; + gulong bytes_after; + gchar *val; + + g_return_val_if_fail (NA_IS_TRAY_CHILD (child), NULL); + + display = gtk_widget_get_display (GTK_WIDGET (child)); + + utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"); + + gdk_error_trap_push (); + + result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), + child->icon_window, + atom, + 0, G_MAXLONG, + False, utf8_string, + &type, &format, &nitems, + &bytes_after, (guchar **)&val); + + if (gdk_error_trap_pop () || result != Success) + return NULL; + + if (type != utf8_string || + format != 8 || + nitems == 0) + { + if (val) + XFree (val); + return NULL; + } + + if (!g_utf8_validate (val, nitems, NULL)) + { + XFree (val); + return NULL; + } + + retval = g_strndup (val, nitems); + + XFree (val); + + return retval; +} + +/** + * na_tray_child_has_alpha; + * @child: a #NaTrayChild + * + * Checks if the child has an ARGB visual and real alpha transparence. + * (as opposed to faked alpha transparency with an parent-relative + * background) + * + * Return value: %TRUE if the child has an alpha transparency + */ +gboolean +na_tray_child_has_alpha (NaTrayChild *child) +{ + g_return_val_if_fail (NA_IS_TRAY_CHILD (child), FALSE); + + return child->has_alpha; +} + +/** + * na_tray_child_set_composited; + * @child: a #NaTrayChild + * @composited: %TRUE if the child's window should be redirected + * + * Sets whether the #GdkWindow of the child should be set redirected + * using gdk_window_set_composited(). By default this is based off of + * na_tray_child_has_alpha(), but it may be useful to override it in + * certain circumstances; for example, if the #NaTrayChild is added + * to a parent window and that parent window is composited against the + * background. + */ +void +na_tray_child_set_composited (NaTrayChild *child, + gboolean composited) +{ + g_return_if_fail (NA_IS_TRAY_CHILD (child)); + + if (child->composited == composited) + return; + + child->composited = composited; + if (gtk_widget_get_realized (GTK_WIDGET (child))) + gdk_window_set_composited (gtk_widget_get_window (GTK_WIDGET (child)), + composited); +} + +/* If we are faking transparency with a window-relative background, force a + * redraw of the icon. This should be called if the background changes or if + * the child is shifted with respect to the background. + */ +void +na_tray_child_force_redraw (NaTrayChild *child) +{ + GtkWidget *widget = GTK_WIDGET (child); + + if (gtk_widget_get_mapped (widget) && child->parent_relative_bg) + { +#if 1 + /* Sending an ExposeEvent might cause redraw problems if the + * icon is expecting the server to clear-to-background before + * the redraw. It should be ok for GtkStatusIcon or EggTrayIcon. + */ + Display *xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)); + XEvent xev; + GdkWindow *plug_window; + GtkAllocation allocation; + + plug_window = gtk_socket_get_plug_window (GTK_SOCKET (child)); + gtk_widget_get_allocation (widget, &allocation); + + xev.xexpose.type = Expose; + xev.xexpose.window = GDK_WINDOW_XID (plug_window); + xev.xexpose.x = 0; + xev.xexpose.y = 0; + xev.xexpose.width = allocation.width; + xev.xexpose.height = allocation.height; + xev.xexpose.count = 0; + + gdk_error_trap_push (); + XSendEvent (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)), + xev.xexpose.window, + False, ExposureMask, + &xev); + /* We have to sync to reliably catch errors from the XSendEvent(), + * since that is asynchronous. + */ + XSync (xdisplay, False); + gdk_error_trap_pop_ignored (); +#else + /* Hiding and showing is the safe way to do it, but can result in more + * flickering. + */ + gdk_window_hide (widget->window); + gdk_window_show (widget->window); +#endif + } +} + +/* from libwnck/xutils.c, comes as LGPLv2+ */ +static char * +latin1_to_utf8 (const char *latin1) +{ + GString *str; + const char *p; + + str = g_string_new (NULL); + + p = latin1; + while (*p) + { + g_string_append_unichar (str, (gunichar) *p); + ++p; + } + + return g_string_free (str, FALSE); +} + +/* derived from libwnck/xutils.c, comes as LGPLv2+ */ +static void +_get_wmclass (Display *xdisplay, + Window xwindow, + char **res_class, + char **res_name) +{ + XClassHint ch; + + ch.res_name = NULL; + ch.res_class = NULL; + + gdk_error_trap_push (); + XGetClassHint (xdisplay, xwindow, &ch); + gdk_error_trap_pop_ignored (); + + if (res_class) + *res_class = NULL; + + if (res_name) + *res_name = NULL; + + if (ch.res_name) + { + if (res_name) + *res_name = latin1_to_utf8 (ch.res_name); + + XFree (ch.res_name); + } + + if (ch.res_class) + { + if (res_class) + *res_class = latin1_to_utf8 (ch.res_class); + + XFree (ch.res_class); + } +} + +/** + * na_tray_child_get_wm_class; + * @child: a #NaTrayChild + * @res_name: return location for a string containing the application name of + * @child, or %NULL + * @res_class: return location for a string containing the application class of + * @child, or %NULL + * + * Fetches the resource associated with @child. + */ +void +na_tray_child_get_wm_class (NaTrayChild *child, + char **res_name, + char **res_class) +{ + GdkDisplay *display; + + g_return_if_fail (NA_IS_TRAY_CHILD (child)); + + display = gtk_widget_get_display (GTK_WIDGET (child)); + + _get_wmclass (GDK_DISPLAY_XDISPLAY (display), + child->icon_window, + res_class, + res_name); +} diff --git a/applets/notification_area/system-tray/na-tray-child.h b/applets/notification_area/system-tray/na-tray-child.h new file mode 100644 index 00000000..6641fe88 --- /dev/null +++ b/applets/notification_area/system-tray/na-tray-child.h @@ -0,0 +1,78 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* na-tray-child.h + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __NA_TRAY_CHILD_H__ +#define __NA_TRAY_CHILD_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NA_TYPE_TRAY_CHILD (na_tray_child_get_type ()) +#define NA_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_CHILD, NaTrayChild)) +#define NA_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_CHILD, NaTrayChildClass)) +#define NA_IS_TRAY_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_CHILD)) +#define NA_IS_TRAY_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_CHILD)) +#define NA_TRAY_CHILD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_CHILD, NaTrayChildClass)) + +typedef struct _NaTrayChild NaTrayChild; +typedef struct _NaTrayChildClass NaTrayChildClass; +typedef struct _NaTrayChildChild NaTrayChildChild; + +struct _NaTrayChild +{ + GtkSocket parent_instance; + Window icon_window; + guint has_alpha : 1; + guint composited : 1; + guint parent_relative_bg : 1; + + gchar *id; +}; + +struct _NaTrayChildClass +{ + GtkSocketClass parent_class; +}; + +GType na_tray_child_get_type (void); + +GtkWidget *na_tray_child_new (GdkScreen *screen, + Window icon_window); +char *na_tray_child_get_title (NaTrayChild *child); +gboolean na_tray_child_has_alpha (NaTrayChild *child); +void na_tray_child_set_composited (NaTrayChild *child, + gboolean composited); +void na_tray_child_force_redraw (NaTrayChild *child); +void na_tray_child_get_wm_class (NaTrayChild *child, + char **res_name, + char **res_class); + +#ifdef __cplusplus +} +#endif + +#endif /* __NA_TRAY_CHILD_H__ */ diff --git a/applets/notification_area/system-tray/na-tray-manager.c b/applets/notification_area/system-tray/na-tray-manager.c new file mode 100644 index 00000000..94d3003d --- /dev/null +++ b/applets/notification_area/system-tray/na-tray-manager.c @@ -0,0 +1,988 @@ +/* na-tray-manager.c + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Used to be: eggtraymanager.c + */ + +#include +#include +#include + +#include "na-tray-manager.h" + +#include +#include +#include +#include + +#include "na-marshal.h" + +/* Signals */ +enum +{ + TRAY_ICON_ADDED, + TRAY_ICON_REMOVED, + MESSAGE_SENT, + MESSAGE_CANCELLED, + LOST_SELECTION, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_ORIENTATION +}; + +typedef struct +{ + long id, len; + long remaining_len; + + long timeout; + char *str; +#ifdef GDK_WINDOWING_X11 + Window window; +#endif +} PendingMessage; + +static guint manager_signals[LAST_SIGNAL] = { 0 }; + +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +#define SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define SYSTEM_TRAY_ORIENTATION_VERT 1 + +#ifdef GDK_WINDOWING_X11 +static gboolean na_tray_manager_check_running_screen_x11 (GdkScreen *screen); +#endif + +static void na_tray_manager_finalize (GObject *object); +static void na_tray_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void na_tray_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +static void na_tray_manager_unmanage (NaTrayManager *manager); + +G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT) + +static void +na_tray_manager_init (NaTrayManager *manager) +{ + manager->invisible = NULL; + manager->socket_table = g_hash_table_new (NULL, NULL); + + manager->padding = 0; + manager->icon_size = 0; + + manager->fg.red = 0.0; + manager->fg.green = 0.0; + manager->fg.blue = 0.0; + manager->fg.alpha = 1.0; + + manager->error.red = 1.0; + manager->error.green = 0.0; + manager->error.blue = 0.0; + manager->error.alpha = 1.0; + + manager->warning.red = 1.0; + manager->warning.green = 1.0; + manager->warning.blue = 0.0; + manager->warning.alpha = 1.0; + + manager->success.red = 0.0; + manager->success.green = 1.0; + manager->success.blue = 0.0; + manager->success.alpha = 1.0; +} + +static void +na_tray_manager_class_init (NaTrayManagerClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *)klass; + + gobject_class->finalize = na_tray_manager_finalize; + gobject_class->set_property = na_tray_manager_set_property; + gobject_class->get_property = na_tray_manager_get_property; + + g_object_class_install_property (gobject_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + "orientation", + "orientation", + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + manager_signals[TRAY_ICON_ADDED] = + g_signal_new ("tray_icon_added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_SOCKET); + + manager_signals[TRAY_ICON_REMOVED] = + g_signal_new ("tray_icon_removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_SOCKET); + manager_signals[MESSAGE_SENT] = + g_signal_new ("message_sent", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaTrayManagerClass, message_sent), + NULL, NULL, + _na_marshal_VOID__OBJECT_STRING_LONG_LONG, + G_TYPE_NONE, 4, + GTK_TYPE_SOCKET, + G_TYPE_STRING, + G_TYPE_LONG, + G_TYPE_LONG); + manager_signals[MESSAGE_CANCELLED] = + g_signal_new ("message_cancelled", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled), + NULL, NULL, + _na_marshal_VOID__OBJECT_LONG, + G_TYPE_NONE, 2, + GTK_TYPE_SOCKET, + G_TYPE_LONG); + manager_signals[LOST_SELECTION] = + g_signal_new ("lost_selection", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +na_tray_manager_finalize (GObject *object) +{ + NaTrayManager *manager; + + manager = NA_TRAY_MANAGER (object); + + na_tray_manager_unmanage (manager); + + g_list_free (manager->messages); + g_hash_table_destroy (manager->socket_table); + + G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object); +} + +static void +na_tray_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NaTrayManager *manager = NA_TRAY_MANAGER (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + na_tray_manager_set_orientation (manager, g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +na_tray_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NaTrayManager *manager = NA_TRAY_MANAGER (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, manager->orientation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +NaTrayManager * +na_tray_manager_new (void) +{ + NaTrayManager *manager; + + manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL); + + return manager; +} + +#ifdef GDK_WINDOWING_X11 + +static gboolean +na_tray_manager_plug_removed (GtkSocket *socket, + NaTrayManager *manager) +{ + NaTrayChild *child = NA_TRAY_CHILD (socket); + + g_hash_table_remove (manager->socket_table, + GINT_TO_POINTER (child->icon_window)); + g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child); + + /* This destroys the socket. */ + return FALSE; +} + +static void +na_tray_manager_handle_dock_request (NaTrayManager *manager, + XClientMessageEvent *xevent) +{ + Window icon_window = xevent->data.l[2]; + GtkWidget *child; + + if (g_hash_table_lookup (manager->socket_table, + GINT_TO_POINTER (icon_window))) + { + /* We already got this notification earlier, ignore this one */ + return; + } + + child = na_tray_child_new (manager->screen, icon_window); + if (child == NULL) /* already gone or other error */ + return; + + g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0, + child); + + /* If the child wasn't attached, then destroy it */ + + if (!GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (child)))) + { + gtk_widget_destroy (child); + return; + } + + g_signal_connect (child, "plug_removed", + G_CALLBACK (na_tray_manager_plug_removed), manager); + + gtk_socket_add_id (GTK_SOCKET (child), icon_window); + + if (!gtk_socket_get_plug_window (GTK_SOCKET (child))) + { + /* Embedding failed, we won't get a plug-removed signal */ + /* This signal destroys the socket */ + g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, child); + return; + } + + g_hash_table_insert (manager->socket_table, + GINT_TO_POINTER (icon_window), child); + gtk_widget_show (child); +} + +static void +pending_message_free (PendingMessage *message) +{ + g_free (message->str); + g_free (message); +} + +static void +na_tray_manager_handle_message_data (NaTrayManager *manager, + XClientMessageEvent *xevent) +{ + GList *p; + int len; + + /* Try to see if we can find the pending message in the list */ + for (p = manager->messages; p; p = p->next) + { + PendingMessage *msg = p->data; + + if (xevent->window == msg->window) + { + /* Append the message */ + len = MIN (msg->remaining_len, 20); + + memcpy ((msg->str + msg->len - msg->remaining_len), + &xevent->data, len); + msg->remaining_len -= len; + + if (msg->remaining_len == 0) + { + GtkSocket *socket; + + socket = g_hash_table_lookup (manager->socket_table, + GINT_TO_POINTER (msg->window)); + + if (socket) + g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, + socket, msg->str, msg->id, msg->timeout); + + pending_message_free (msg); + manager->messages = g_list_remove_link (manager->messages, p); + g_list_free_1 (p); + } + + break; + } + } +} + +static void +na_tray_manager_handle_begin_message (NaTrayManager *manager, + XClientMessageEvent *xevent) +{ + GtkSocket *socket; + GList *p; + PendingMessage *msg; + long timeout; + long len; + long id; + + socket = g_hash_table_lookup (manager->socket_table, + GINT_TO_POINTER (xevent->window)); + /* we don't know about this tray icon, so ignore the message */ + if (!socket) + return; + + timeout = xevent->data.l[2]; + len = xevent->data.l[3]; + id = xevent->data.l[4]; + + /* Check if the same message is already in the queue and remove it if so */ + for (p = manager->messages; p; p = p->next) + { + PendingMessage *pmsg = p->data; + + if (xevent->window == pmsg->window && + id == pmsg->id) + { + /* Hmm, we found it, now remove it */ + pending_message_free (pmsg); + manager->messages = g_list_remove_link (manager->messages, p); + g_list_free_1 (p); + break; + } + } + + if (len == 0) + { + g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, + socket, "", id, timeout); + } + else + { + /* Now add the new message to the queue */ + msg = g_new0 (PendingMessage, 1); + msg->window = xevent->window; + msg->timeout = timeout; + msg->len = len; + msg->id = id; + msg->remaining_len = msg->len; + msg->str = g_malloc (msg->len + 1); + msg->str[msg->len] = '\0'; + manager->messages = g_list_prepend (manager->messages, msg); + } +} + +static void +na_tray_manager_handle_cancel_message (NaTrayManager *manager, + XClientMessageEvent *xevent) +{ + GList *p; + GtkSocket *socket; + long id; + + id = xevent->data.l[2]; + + /* Check if the message is in the queue and remove it if so */ + for (p = manager->messages; p; p = p->next) + { + PendingMessage *msg = p->data; + + if (xevent->window == msg->window && + id == msg->id) + { + pending_message_free (msg); + manager->messages = g_list_remove_link (manager->messages, p); + g_list_free_1 (p); + break; + } + } + + socket = g_hash_table_lookup (manager->socket_table, + GINT_TO_POINTER (xevent->window)); + + if (socket) + { + g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0, + socket, xevent->data.l[2]); + } +} + +static GdkFilterReturn +na_tray_manager_window_filter (GdkXEvent *xev, + GdkEvent *event, + gpointer data) +{ + XEvent *xevent = (GdkXEvent *)xev; + NaTrayManager *manager = data; + + if (xevent->type == ClientMessage) + { + /* We handle this client message here. See comment in + * na_tray_manager_handle_client_message_opcode() for details */ + if (xevent->xclient.message_type == manager->opcode_atom && + xevent->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) + { + na_tray_manager_handle_dock_request (manager, + (XClientMessageEvent *) xevent); + return GDK_FILTER_REMOVE; + } + /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_BEGIN_MESSAGE */ + else if (xevent->xclient.message_type == manager->opcode_atom && + xevent->xclient.data.l[1] == SYSTEM_TRAY_BEGIN_MESSAGE) + { + na_tray_manager_handle_begin_message (manager, + (XClientMessageEvent *) event); + return GDK_FILTER_REMOVE; + } + /* _NET_SYSTEM_TRAY_OPCODE: SYSTEM_TRAY_CANCEL_MESSAGE */ + else if (xevent->xclient.message_type == manager->opcode_atom && + xevent->xclient.data.l[1] == SYSTEM_TRAY_CANCEL_MESSAGE) + { + na_tray_manager_handle_cancel_message (manager, + (XClientMessageEvent *) event); + return GDK_FILTER_REMOVE; + } + /* _NET_SYSTEM_TRAY_MESSAGE_DATA */ + else if (xevent->xclient.message_type == manager->message_data_atom) + { + na_tray_manager_handle_message_data (manager, + (XClientMessageEvent *) event); + return GDK_FILTER_REMOVE; + } + } + else if (xevent->type == SelectionClear) + { + g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); + na_tray_manager_unmanage (manager); + } + + return GDK_FILTER_CONTINUE; +} + +#if 0 +//FIXME investigate why this doesn't work +static gboolean +na_tray_manager_selection_clear_event (GtkWidget *widget, + GdkEventSelection *event, + NaTrayManager *manager) +{ + g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); + na_tray_manager_unmanage (manager); + + return FALSE; +} +#endif +#endif + +static void +na_tray_manager_unmanage (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkDisplay *display; + guint32 timestamp; + GtkWidget *invisible; + GdkWindow *window; + + if (manager->invisible == NULL) + return; + + invisible = manager->invisible; + window = gtk_widget_get_window (invisible); + + g_assert (GTK_IS_INVISIBLE (invisible)); + g_assert (gtk_widget_get_realized (invisible)); + g_assert (GDK_IS_WINDOW (window)); + + display = gtk_widget_get_display (invisible); + + if (gdk_selection_owner_get_for_display (display, manager->selection_atom) == + window) + { + timestamp = gdk_x11_get_server_time (window); + gdk_selection_owner_set_for_display (display, + NULL, + manager->selection_atom, + timestamp, + TRUE); + } + + gdk_window_remove_filter (window, + na_tray_manager_window_filter, manager); + + manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */ + gtk_widget_destroy (invisible); + g_object_unref (G_OBJECT (invisible)); +#endif +} + +static void +na_tray_manager_set_orientation_property (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkWindow *window; + GdkDisplay *display; + Atom orientation_atom; + gulong data[1]; + + g_return_if_fail (manager->invisible != NULL); + window = gtk_widget_get_window (manager->invisible); + g_return_if_fail (window != NULL); + + display = gtk_widget_get_display (manager->invisible); + orientation_atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_SYSTEM_TRAY_ORIENTATION"); + + data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ? + SYSTEM_TRAY_ORIENTATION_HORZ : + SYSTEM_TRAY_ORIENTATION_VERT; + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XID (window), + orientation_atom, + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &data, 1); +#endif +} + +static void +na_tray_manager_set_visual_property (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkWindow *window; + GdkDisplay *display; + Visual *xvisual; + Atom visual_atom; + gulong data[1]; + + g_return_if_fail (manager->invisible != NULL); + window = gtk_widget_get_window (manager->invisible); + g_return_if_fail (window != NULL); + + /* The visual property is a hint to the tray icons as to what visual they + * should use for their windows. If the X server has RGBA colormaps, then + * we tell the tray icons to use a RGBA colormap and we'll composite the + * icon onto its parents with real transparency. Otherwise, we just tell + * the icon to use our colormap, and we'll do some hacks with parent + * relative backgrounds to simulate transparency. + */ + + display = gtk_widget_get_display (manager->invisible); + visual_atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_SYSTEM_TRAY_VISUAL"); + + if (gdk_screen_get_rgba_visual (manager->screen) != NULL && + gdk_display_supports_composite (display)) + { + xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_rgba_visual (manager->screen)); + } + else + { + /* We actually want the visual of the tray where the icons will + * be embedded. In almost all cases, this will be the same as the visual + * of the screen. + */ + xvisual = GDK_VISUAL_XVISUAL (gdk_screen_get_system_visual (manager->screen)); + } + + data[0] = XVisualIDFromVisual (xvisual); + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XID (window), + visual_atom, + XA_VISUALID, 32, + PropModeReplace, + (guchar *) &data, 1); +#endif +} + +static void +na_tray_manager_set_padding_property (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkWindow *window; + GdkDisplay *display; + Atom atom; + gulong data[1]; + + g_return_if_fail (manager->invisible != NULL); + window = gtk_widget_get_window (manager->invisible); + g_return_if_fail (window != NULL); + + display = gtk_widget_get_display (manager->invisible); + atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_SYSTEM_TRAY_PADDING"); + + data[0] = manager->padding; + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XID (window), + atom, + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &data, 1); +#endif +} + +static void +na_tray_manager_set_icon_size_property (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkWindow *window; + GdkDisplay *display; + Atom atom; + gulong data[1]; + + g_return_if_fail (manager->invisible != NULL); + window = gtk_widget_get_window (manager->invisible); + g_return_if_fail (window != NULL); + + display = gtk_widget_get_display (manager->invisible); + atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_SYSTEM_TRAY_ICON_SIZE"); + + data[0] = manager->icon_size; + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XID (window), + atom, + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &data, 1); +#endif +} + +static void +na_tray_manager_set_colors_property (NaTrayManager *manager) +{ +#ifdef GDK_WINDOWING_X11 + GdkWindow *window; + GdkDisplay *display; + Atom atom; + gulong data[12]; + + g_return_if_fail (manager->invisible != NULL); + window = gtk_widget_get_window (manager->invisible); + g_return_if_fail (window != NULL); + + display = gtk_widget_get_display (manager->invisible); + atom = gdk_x11_get_xatom_by_name_for_display (display, + "_NET_SYSTEM_TRAY_COLORS"); + + data[0] = manager->fg.red * 65535; + data[1] = manager->fg.green * 65535; + data[2] = manager->fg.blue * 65535; + data[3] = manager->error.red * 65535; + data[4] = manager->error.green * 65535; + data[5] = manager->error.blue * 65535; + data[6] = manager->warning.red * 65535; + data[7] = manager->warning.green * 65535; + data[8] = manager->warning.blue * 65535; + data[9] = manager->success.red * 65535; + data[10] = manager->success.green * 65535; + data[11] = manager->success.blue * 65535; + + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + GDK_WINDOW_XID (window), + atom, + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &data, 12); +#endif +} + +#ifdef GDK_WINDOWING_X11 + +static gboolean +na_tray_manager_manage_screen_x11 (NaTrayManager *manager, + GdkScreen *screen) +{ + GdkDisplay *display; + Screen *xscreen; + GtkWidget *invisible; + GdkWindow *window; + char *selection_atom_name; + guint32 timestamp; + + g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE); + g_return_val_if_fail (manager->screen == NULL, FALSE); + + /* If there's already a manager running on the screen + * we can't create another one. + */ +#if 0 + if (na_tray_manager_check_running_screen_x11 (screen)) + return FALSE; +#endif + + manager->screen = screen; + + display = gdk_screen_get_display (screen); + xscreen = GDK_SCREEN_XSCREEN (screen); + + invisible = gtk_invisible_new_for_screen (screen); + gtk_widget_realize (invisible); + + gtk_widget_add_events (invisible, + GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK); + + selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", + gdk_screen_get_number (screen)); + manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE); + g_free (selection_atom_name); + + manager->invisible = invisible; + g_object_ref (G_OBJECT (manager->invisible)); + + na_tray_manager_set_orientation_property (manager); + na_tray_manager_set_visual_property (manager); + na_tray_manager_set_padding_property (manager); + na_tray_manager_set_icon_size_property (manager); + na_tray_manager_set_colors_property (manager); + + window = gtk_widget_get_window (invisible); + + timestamp = gdk_x11_get_server_time (window); + + /* Check if we could set the selection owner successfully */ + if (gdk_selection_owner_set_for_display (display, + window, + manager->selection_atom, + timestamp, + TRUE)) + { + XClientMessageEvent xev; + GdkAtom opcode_atom; + GdkAtom message_data_atom; + + xev.type = ClientMessage; + xev.window = RootWindowOfScreen (xscreen); + xev.message_type = gdk_x11_get_xatom_by_name_for_display (display, + "MANAGER"); + + xev.format = 32; + xev.data.l[0] = timestamp; + xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display, + manager->selection_atom); + xev.data.l[2] = GDK_WINDOW_XID (window); + xev.data.l[3] = 0; /* manager specific data */ + xev.data.l[4] = 0; /* manager specific data */ + + XSendEvent (GDK_DISPLAY_XDISPLAY (display), + RootWindowOfScreen (xscreen), + False, StructureNotifyMask, (XEvent *)&xev); + + opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE); + manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display, + opcode_atom); + + message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA", + FALSE); + + manager->message_data_atom = gdk_x11_atom_to_xatom_for_display (display, + message_data_atom); + + /* Add a window filter */ +#if 0 + /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */ + g_signal_connect (invisible, "selection-clear-event", + G_CALLBACK (na_tray_manager_selection_clear_event), + manager); +#endif + /* This is for SYSTEM_TRAY_REQUEST_DOCK and SelectionClear */ + gdk_window_add_filter (window, + na_tray_manager_window_filter, manager); + return TRUE; + } + else + { + gtk_widget_destroy (invisible); + g_object_unref (invisible); + manager->invisible = NULL; + + manager->screen = NULL; + + return FALSE; + } +} + +#endif + +gboolean +na_tray_manager_manage_screen (NaTrayManager *manager, + GdkScreen *screen) +{ + g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); + g_return_val_if_fail (manager->screen == NULL, FALSE); + +#ifdef GDK_WINDOWING_X11 + return na_tray_manager_manage_screen_x11 (manager, screen); +#else + return FALSE; +#endif +} + +#ifdef GDK_WINDOWING_X11 + +static gboolean +na_tray_manager_check_running_screen_x11 (GdkScreen *screen) +{ + GdkDisplay *display; + Atom selection_atom; + char *selection_atom_name; + + display = gdk_screen_get_display (screen); + selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", + gdk_screen_get_number (screen)); + selection_atom = gdk_x11_get_xatom_by_name_for_display (display, + selection_atom_name); + g_free (selection_atom_name); + + if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), + selection_atom) != None) + return TRUE; + else + return FALSE; +} + +#endif + +gboolean +na_tray_manager_check_running (GdkScreen *screen) +{ + g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); + +#ifdef GDK_WINDOWING_X11 + return na_tray_manager_check_running_screen_x11 (screen); +#else + return FALSE; +#endif +} + +void +na_tray_manager_set_orientation (NaTrayManager *manager, + GtkOrientation orientation) +{ + g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); + + if (manager->orientation != orientation) + { + manager->orientation = orientation; + + na_tray_manager_set_orientation_property (manager); + + g_object_notify (G_OBJECT (manager), "orientation"); + } +} + +void +na_tray_manager_set_padding (NaTrayManager *manager, + gint padding) +{ + g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); + + if (manager->padding != padding) + { + manager->padding = padding; + + na_tray_manager_set_padding_property (manager); + } +} + +void +na_tray_manager_set_icon_size (NaTrayManager *manager, + gint icon_size) +{ + g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); + + if (manager->icon_size != icon_size) + { + manager->icon_size = icon_size; + + na_tray_manager_set_icon_size_property (manager); + } +} + +void +na_tray_manager_set_colors (NaTrayManager *manager, + GdkRGBA *fg, + GdkRGBA *error, + GdkRGBA *warning, + GdkRGBA *success) +{ + g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); + + if (!gdk_rgba_equal (&manager->fg, fg) || + !gdk_rgba_equal (&manager->error, error) || + !gdk_rgba_equal (&manager->warning, warning) || + !gdk_rgba_equal (&manager->success, success)) + { + manager->fg = *fg; + manager->error = *error; + manager->warning = *warning; + manager->success = *success; + + na_tray_manager_set_colors_property (manager); + } +} + +GtkOrientation +na_tray_manager_get_orientation (NaTrayManager *manager) +{ + g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL); + + return manager->orientation; +} diff --git a/applets/notification_area/system-tray/na-tray-manager.h b/applets/notification_area/system-tray/na-tray-manager.h new file mode 100644 index 00000000..811791be --- /dev/null +++ b/applets/notification_area/system-tray/na-tray-manager.h @@ -0,0 +1,113 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* na-tray-manager.h + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Used to be: eggtraymanager.h + */ + +#ifndef __NA_TRAY_MANAGER_H__ +#define __NA_TRAY_MANAGER_H__ + +#ifdef GDK_WINDOWING_X11 +#include +#endif +#include + +#include "na-tray-child.h" + +G_BEGIN_DECLS + +#define NA_TYPE_TRAY_MANAGER (na_tray_manager_get_type ()) +#define NA_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManager)) +#define NA_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass)) +#define NA_IS_TRAY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY_MANAGER)) +#define NA_IS_TRAY_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY_MANAGER)) +#define NA_TRAY_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY_MANAGER, NaTrayManagerClass)) + +typedef struct _NaTrayManager NaTrayManager; +typedef struct _NaTrayManagerClass NaTrayManagerClass; + +struct _NaTrayManager +{ + GObject parent_instance; + +#ifdef GDK_WINDOWING_X11 + GdkAtom selection_atom; + Atom opcode_atom; + Atom message_data_atom; +#endif + + GtkWidget *invisible; + GdkScreen *screen; + GtkOrientation orientation; + gint padding; + gint icon_size; + GdkRGBA fg; + GdkRGBA error; + GdkRGBA warning; + GdkRGBA success; + + GList *messages; + GHashTable *socket_table; +}; + +struct _NaTrayManagerClass +{ + GObjectClass parent_class; + + void (* tray_icon_added) (NaTrayManager *manager, + NaTrayChild *child); + void (* tray_icon_removed) (NaTrayManager *manager, + NaTrayChild *child); + + void (* message_sent) (NaTrayManager *manager, + NaTrayChild *child, + const gchar *message, + glong id, + glong timeout); + + void (* message_cancelled) (NaTrayManager *manager, + NaTrayChild *child, + glong id); + + void (* lost_selection) (NaTrayManager *manager); +}; + +GType na_tray_manager_get_type (void); + +gboolean na_tray_manager_check_running (GdkScreen *screen); +NaTrayManager *na_tray_manager_new (void); +gboolean na_tray_manager_manage_screen (NaTrayManager *manager, + GdkScreen *screen); +void na_tray_manager_set_orientation (NaTrayManager *manager, + GtkOrientation orientation); +GtkOrientation na_tray_manager_get_orientation (NaTrayManager *manager); +void na_tray_manager_set_padding (NaTrayManager *manager, + gint padding); +void na_tray_manager_set_icon_size (NaTrayManager *manager, + gint padding); +void na_tray_manager_set_colors (NaTrayManager *manager, + GdkRGBA *fg, + GdkRGBA *error, + GdkRGBA *warning, + GdkRGBA *success); + +G_END_DECLS + +#endif /* __NA_TRAY_MANAGER_H__ */ diff --git a/applets/notification_area/system-tray/na-tray.c b/applets/notification_area/system-tray/na-tray.c new file mode 100644 index 00000000..c2488ecf --- /dev/null +++ b/applets/notification_area/system-tray/na-tray.c @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2003-2006 Vincent Untz + * Copyright (C) 2007 Christian Persch + * Copyright (C) 2017 Colomban Wendling + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include +#include + +#include + +#include "na-tray-manager.h" +#include "fixedtip.h" + +#include "na-tray.h" + +typedef struct +{ + NaTrayManager *tray_manager; + GSList *all_trays; + GHashTable *icon_table; + GHashTable *tip_table; +} TraysScreen; + +struct _NaTrayPrivate +{ + GdkScreen *screen; + TraysScreen *trays_screen; + + guint idle_redraw_id; + + GtkOrientation orientation; + gint icon_padding; + gint icon_size; +}; + +typedef struct +{ + char *text; + glong id; + glong timeout; +} IconTipBuffer; + +typedef struct +{ + NaTray *tray; /* tray containing the tray icon */ + GtkWidget *icon; /* tray icon sending the message */ + GtkWidget *fixedtip; + guint source_id; + glong id; /* id of the current message */ + GSList *buffer; /* buffered messages */ +} IconTip; + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_ICON_PADDING, + PROP_ICON_SIZE, + PROP_SCREEN +}; + +static gboolean initialized = FALSE; +static TraysScreen *trays_screens = NULL; + +static void icon_tip_show_next (IconTip *icontip); + +/* NaTray */ +static void na_host_init (NaHostInterface *iface); +static void na_tray_style_updated (NaHost *host, + GtkStyleContext *context); +static void na_tray_force_redraw (NaHost *host); + +G_DEFINE_TYPE_WITH_CODE (NaTray, na_tray, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL) + G_IMPLEMENT_INTERFACE (NA_TYPE_HOST, na_host_init)) + +static void +na_host_init (NaHostInterface *iface) +{ + iface->force_redraw = na_tray_force_redraw; + iface->style_updated = na_tray_style_updated; +} + +static NaTray * +get_tray (TraysScreen *trays_screen) +{ + if (trays_screen->all_trays == NULL) + return NULL; + + return trays_screen->all_trays->data; +} + +static void +tray_added (NaTrayManager *manager, + NaTrayChild *icon, + TraysScreen *trays_screen) +{ + NaTray *tray; + NaTrayPrivate *priv; + + tray = get_tray (trays_screen); + if (tray == NULL) + return; + + priv = tray->priv; + + g_assert (priv->trays_screen == trays_screen); + + g_hash_table_insert (trays_screen->icon_table, icon, tray); + + na_host_emit_item_added (NA_HOST (tray), NA_ITEM (icon)); + + gtk_widget_show (GTK_WIDGET (icon)); +} + +static void +tray_removed (NaTrayManager *manager, + NaTrayChild *icon, + TraysScreen *trays_screen) +{ + NaTray *tray; + + tray = g_hash_table_lookup (trays_screen->icon_table, icon); + if (tray == NULL) + return; + + g_assert (tray->priv->trays_screen == trays_screen); + + na_host_emit_item_removed (NA_HOST (tray), NA_ITEM (icon)); + + g_hash_table_remove (trays_screen->icon_table, icon); + /* this will also destroy the tip associated to this icon */ + g_hash_table_remove (trays_screen->tip_table, icon); +} + +static void +icon_tip_buffer_free (gpointer data, + gpointer userdata) +{ + IconTipBuffer *buffer; + + buffer = data; + + g_free (buffer->text); + buffer->text = NULL; + + g_free (buffer); +} + +static void +icon_tip_free (gpointer data) +{ + IconTip *icontip; + + if (data == NULL) + return; + + icontip = data; + + if (icontip->fixedtip != NULL) + gtk_widget_destroy (GTK_WIDGET (icontip->fixedtip)); + icontip->fixedtip = NULL; + + if (icontip->source_id != 0) + g_source_remove (icontip->source_id); + icontip->source_id = 0; + + if (icontip->buffer != NULL) + { + g_slist_foreach (icontip->buffer, icon_tip_buffer_free, NULL); + g_slist_free (icontip->buffer); + } + icontip->buffer = NULL; + + g_free (icontip); +} + +static int +icon_tip_buffer_compare (gconstpointer a, + gconstpointer b) +{ + const IconTipBuffer *buffer_a = a; + const IconTipBuffer *buffer_b = b; + + if (buffer_a == NULL || buffer_b == NULL) + return !(buffer_a == buffer_b); + + return buffer_a->id - buffer_b->id; +} + +static void +icon_tip_show_next_clicked (GtkWidget *widget, + gpointer data) +{ + icon_tip_show_next ((IconTip *) data); +} + +static gboolean +icon_tip_show_next_timeout (gpointer data) +{ + IconTip *icontip = (IconTip *) data; + + icon_tip_show_next (icontip); + + return FALSE; +} + +static void +icon_tip_show_next (IconTip *icontip) +{ + IconTipBuffer *buffer; + + if (icontip->buffer == NULL) + { + /* this will also destroy the tip window */ + g_hash_table_remove (icontip->tray->priv->trays_screen->tip_table, + icontip->icon); + return; + } + + if (icontip->source_id != 0) + g_source_remove (icontip->source_id); + icontip->source_id = 0; + + buffer = icontip->buffer->data; + icontip->buffer = g_slist_remove (icontip->buffer, buffer); + + if (icontip->fixedtip == NULL) + { + icontip->fixedtip = na_fixed_tip_new (icontip->icon, + gtk_orientable_get_orientation (GTK_ORIENTABLE (icontip->tray))); + + g_signal_connect (icontip->fixedtip, "clicked", + G_CALLBACK (icon_tip_show_next_clicked), icontip); + } + + na_fixed_tip_set_markup (icontip->fixedtip, buffer->text); + + if (!gtk_widget_get_mapped (icontip->fixedtip)) + gtk_widget_show (icontip->fixedtip); + + icontip->id = buffer->id; + + if (buffer->timeout > 0) + icontip->source_id = g_timeout_add_seconds (buffer->timeout, + icon_tip_show_next_timeout, + icontip); + + icon_tip_buffer_free (buffer, NULL); +} + +static void +message_sent (NaTrayManager *manager, + GtkWidget *icon, + const char *text, + glong id, + glong timeout, + TraysScreen *trays_screen) +{ + IconTip *icontip; + IconTipBuffer find_buffer; + IconTipBuffer *buffer; + gboolean show_now; + + icontip = g_hash_table_lookup (trays_screen->tip_table, icon); + + find_buffer.id = id; + if (icontip && + (icontip->id == id || + g_slist_find_custom (icontip->buffer, &find_buffer, + icon_tip_buffer_compare) != NULL)) + /* we already have this message, so ignore it */ + /* FIXME: in an ideal world, we'd remember all the past ids and ignore them + * too */ + return; + + show_now = FALSE; + + if (icontip == NULL) + { + NaTray *tray; + + tray = g_hash_table_lookup (trays_screen->icon_table, icon); + if (tray == NULL) + { + /* We don't know about the icon sending the message, so ignore it. + * But this should never happen since NaTrayManager shouldn't send + * us the message if there's no socket for it. */ + g_critical ("Ignoring a message sent by a tray icon " + "we don't know: \"%s\".\n", text); + return; + } + + icontip = g_new0 (IconTip, 1); + icontip->tray = tray; + icontip->icon = icon; + + g_hash_table_insert (trays_screen->tip_table, icon, icontip); + + show_now = TRUE; + } + + buffer = g_new0 (IconTipBuffer, 1); + + buffer->text = g_strdup (text); + buffer->id = id; + buffer->timeout = timeout; + + icontip->buffer = g_slist_append (icontip->buffer, buffer); + + if (show_now) + icon_tip_show_next (icontip); +} + +static void +message_cancelled (NaTrayManager *manager, + GtkWidget *icon, + glong id, + TraysScreen *trays_screen) +{ + IconTip *icontip; + IconTipBuffer find_buffer; + GSList *cancel_buffer_l; + IconTipBuffer *cancel_buffer; + + icontip = g_hash_table_lookup (trays_screen->tip_table, icon); + if (icontip == NULL) + return; + + if (icontip->id == id) + { + icon_tip_show_next (icontip); + return; + } + + find_buffer.id = id; + cancel_buffer_l = g_slist_find_custom (icontip->buffer, &find_buffer, + icon_tip_buffer_compare); + if (cancel_buffer_l == NULL) + return; + + cancel_buffer = cancel_buffer_l->data; + icon_tip_buffer_free (cancel_buffer, NULL); + + icontip->buffer = g_slist_remove_link (icontip->buffer, cancel_buffer_l); + g_slist_free_1 (cancel_buffer_l); +} + +static void +update_orientation_for_messages (gpointer key, + gpointer value, + gpointer data) +{ + NaTray *tray; + IconTip *icontip; + + if (value == NULL) + return; + + icontip = value; + tray = data; + if (icontip->tray != tray) + return; + + if (icontip->fixedtip) + na_fixed_tip_set_orientation (icontip->fixedtip, tray->priv->orientation); +} + +static void +update_size_and_orientation (NaTray *tray) +{ + NaTrayPrivate *priv = tray->priv; + + /* This only happens when setting the property during object construction */ + if (!priv->trays_screen) + return; + + g_hash_table_foreach (priv->trays_screen->tip_table, + update_orientation_for_messages, tray); + + if (get_tray (priv->trays_screen) == tray) + na_tray_manager_set_orientation (priv->trays_screen->tray_manager, + priv->orientation); +} + +static void +na_tray_init (NaTray *tray) +{ + NaTrayPrivate *priv; + + priv = tray->priv = G_TYPE_INSTANCE_GET_PRIVATE (tray, NA_TYPE_TRAY, NaTrayPrivate); + + priv->screen = NULL; + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->icon_padding = 0; + priv->icon_size = 0; +} + +static GObject * +na_tray_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + NaTray *tray; + NaTrayPrivate *priv; + int screen_number; + + object = G_OBJECT_CLASS (na_tray_parent_class)->constructor (type, + n_construct_properties, + construct_params); + tray = NA_TRAY (object); + priv = tray->priv; + + g_assert (priv->screen != NULL); + + if (!initialized) + { + trays_screens = g_new0 (TraysScreen, 1); + initialized = TRUE; + } + + screen_number = gdk_screen_get_number (priv->screen); + + if (trays_screens [screen_number].tray_manager == NULL) + { + NaTrayManager *tray_manager; + + tray_manager = na_tray_manager_new (); + + if (na_tray_manager_manage_screen (tray_manager, priv->screen)) + { + trays_screens [screen_number].tray_manager = tray_manager; + + g_signal_connect (tray_manager, "tray_icon_added", + G_CALLBACK (tray_added), + &trays_screens [screen_number]); + g_signal_connect (tray_manager, "tray_icon_removed", + G_CALLBACK (tray_removed), + &trays_screens [screen_number]); + g_signal_connect (tray_manager, "message_sent", + G_CALLBACK (message_sent), + &trays_screens [screen_number]); + g_signal_connect (tray_manager, "message_cancelled", + G_CALLBACK (message_cancelled), + &trays_screens [screen_number]); + + trays_screens [screen_number].icon_table = g_hash_table_new (NULL, + NULL); + trays_screens [screen_number].tip_table = g_hash_table_new_full ( + NULL, + NULL, + NULL, + icon_tip_free); + } + else + { + g_printerr ("System tray didn't get the system tray manager selection for screen %d\n", + screen_number); + g_object_unref (tray_manager); + } + } + + priv->trays_screen = &trays_screens [screen_number]; + trays_screens [screen_number].all_trays = g_slist_append (trays_screens [screen_number].all_trays, + tray); + + update_size_and_orientation (tray); + + return object; +} + +static void +na_tray_dispose (GObject *object) +{ + NaTray *tray = NA_TRAY (object); + NaTrayPrivate *priv = tray->priv; + TraysScreen *trays_screen = priv->trays_screen; + + if (trays_screen != NULL) + { + trays_screen->all_trays = g_slist_remove (trays_screen->all_trays, tray); + + if (trays_screen->all_trays == NULL) + { + /* Make sure we drop the manager selection */ + g_object_unref (trays_screen->tray_manager); + trays_screen->tray_manager = NULL; + + g_hash_table_destroy (trays_screen->icon_table); + trays_screen->icon_table = NULL; + + g_hash_table_destroy (trays_screen->tip_table); + trays_screen->tip_table = NULL; + } + else + { + NaTray *new_tray; + + new_tray = get_tray (trays_screen); + if (new_tray != NULL) + na_tray_manager_set_orientation (trays_screen->tray_manager, + gtk_orientable_get_orientation (GTK_ORIENTABLE (new_tray))); + } + } + + priv->trays_screen = NULL; + + if (priv->idle_redraw_id != 0) + { + g_source_remove (priv->idle_redraw_id); + priv->idle_redraw_id = 0; + } + + G_OBJECT_CLASS (na_tray_parent_class)->dispose (object); +} + +static void +na_tray_set_orientation (NaTray *tray, + GtkOrientation orientation) +{ + NaTrayPrivate *priv = tray->priv; + + if (orientation == priv->orientation) + return; + + priv->orientation = orientation; + + update_size_and_orientation (tray); +} + +static void +na_tray_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NaTray *tray = NA_TRAY (object); + NaTrayPrivate *priv = tray->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + na_tray_set_orientation (tray, g_value_get_enum (value)); + break; + case PROP_ICON_PADDING: + na_tray_set_padding (tray, g_value_get_int (value)); + break; + case PROP_ICON_SIZE: + na_tray_set_icon_size (tray, g_value_get_int (value)); + break; + case PROP_SCREEN: + priv->screen = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +na_tray_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NaTray *tray = NA_TRAY (object); + NaTrayPrivate *priv = tray->priv; + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, tray->priv->orientation); + break; + case PROP_ICON_PADDING: + g_value_set_int (value, tray->priv->icon_padding); + break; + case PROP_ICON_SIZE: + g_value_set_int (value, tray->priv->icon_size); + break; + case PROP_SCREEN: + g_value_set_object (value, priv->screen); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +na_tray_class_init (NaTrayClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = na_tray_constructor; + gobject_class->set_property = na_tray_set_property; + gobject_class->get_property = na_tray_get_property; + gobject_class->dispose = na_tray_dispose; + + g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); + + g_object_class_override_property (gobject_class, PROP_ICON_PADDING, "icon-padding"); + g_object_class_override_property (gobject_class, PROP_ICON_SIZE, "icon-size"); + + g_object_class_install_property + (gobject_class, + PROP_SCREEN, + g_param_spec_object ("screen", "screen", "screen", + GDK_TYPE_SCREEN, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (gobject_class, sizeof (NaTrayPrivate)); +} + +NaHost * +na_tray_new_for_screen (GdkScreen *screen, + GtkOrientation orientation) +{ + return g_object_new (NA_TYPE_TRAY, + "screen", screen, + "orientation", orientation, + NULL); +} + +void +na_tray_set_padding (NaTray *tray, + gint padding) +{ + NaTrayPrivate *priv = tray->priv; + + priv->icon_padding = padding; + if (get_tray (priv->trays_screen) == tray) + na_tray_manager_set_padding (priv->trays_screen->tray_manager, padding); +} + +void +na_tray_set_icon_size (NaTray *tray, + gint size) +{ + NaTrayPrivate *priv = tray->priv; + + priv->icon_size = size; + if (get_tray (priv->trays_screen) == tray) + na_tray_manager_set_icon_size (priv->trays_screen->tray_manager, size); +} + +static void +na_tray_set_colors (NaTray *tray, + GdkRGBA *fg, + GdkRGBA *error, + GdkRGBA *warning, + GdkRGBA *success) +{ + NaTrayPrivate *priv = tray->priv; + + if (get_tray (priv->trays_screen) == tray) + na_tray_manager_set_colors (priv->trays_screen->tray_manager, fg, error, warning, success); +} + +static void +na_tray_style_updated (NaHost *host, + GtkStyleContext *context) +{ + GdkRGBA fg; + GdkRGBA error; + GdkRGBA warning; + GdkRGBA success; + + gtk_style_context_save (context); + gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); + + gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg); + + if (!gtk_style_context_lookup_color (context, "error_color", &error)) + error = fg; + if (!gtk_style_context_lookup_color (context, "warning_color", &warning)) + warning = fg; + if (!gtk_style_context_lookup_color (context, "success_color", &success)) + success = fg; + + gtk_style_context_restore (context); + + na_tray_set_colors (NA_TRAY (host), &fg, &error, &warning, &success); +} + +static gboolean +idle_redraw_cb (NaTray *tray) +{ + NaTrayPrivate *priv = tray->priv; + + g_hash_table_foreach (priv->trays_screen->icon_table, + (GHFunc) na_tray_child_force_redraw, NULL); + + priv->idle_redraw_id = 0; + + return FALSE; +} + +static void +na_tray_force_redraw (NaHost *host) +{ + NaTray *tray = NA_TRAY (host); + NaTrayPrivate *priv = tray->priv; + + /* Force the icons to redraw their backgrounds. + */ + if (priv->idle_redraw_id == 0) + priv->idle_redraw_id = g_idle_add ((GSourceFunc) idle_redraw_cb, tray); +} diff --git a/applets/notification_area/system-tray/na-tray.h b/applets/notification_area/system-tray/na-tray.h new file mode 100644 index 00000000..8eaca36e --- /dev/null +++ b/applets/notification_area/system-tray/na-tray.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* na-tray-tray.h + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2003-2006 Vincent Untz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Used to be: eggtraytray.h + */ + +#ifndef __NA_TRAY_H__ +#define __NA_TRAY_H__ + +#ifdef GDK_WINDOWING_X11 +#include +#endif +#include + +#include "na-host.h" + +G_BEGIN_DECLS + +#define NA_TYPE_TRAY (na_tray_get_type ()) +#define NA_TRAY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NA_TYPE_TRAY, NaTray)) +#define NA_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NA_TYPE_TRAY, NaTrayClass)) +#define NA_IS_TRAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NA_TYPE_TRAY)) +#define NA_IS_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NA_TYPE_TRAY)) +#define NA_TRAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NA_TYPE_TRAY, NaTrayClass)) + +typedef struct _NaTray NaTray; +typedef struct _NaTrayPrivate NaTrayPrivate; +typedef struct _NaTrayClass NaTrayClass; + +struct _NaTray +{ + GObject parent_instance; + + NaTrayPrivate *priv; +}; + +struct _NaTrayClass +{ + GObjectClass parent_class; +}; + +GType na_tray_get_type (void); +NaHost *na_tray_new_for_screen (GdkScreen *screen, + GtkOrientation orientation); +void na_tray_set_padding (NaTray *tray, + gint padding); +void na_tray_set_icon_size (NaTray *tray, + gint icon_size); + +G_END_DECLS + +#endif /* __NA_TRAY_H__ */ diff --git a/applets/notification_area/testtray.c b/applets/notification_area/testtray.c index f3724025..b2ce0fa2 100644 --- a/applets/notification_area/testtray.c +++ b/applets/notification_area/testtray.c @@ -23,9 +23,14 @@ #include #include +#include +#include #include -#include "na-tray-manager.h" -#include "na-tray.h" +#include "system-tray/na-tray-manager.h" +#ifdef PROVIDE_WATCHER_SERVICE +# include "libstatus-notifier-watcher/gf-status-notifier-watcher.h" +#endif +#include "na-box.h" #define NOTIFICATION_AREA_ICON "mate-panel-notification-area" @@ -36,8 +41,7 @@ typedef struct GdkScreen *screen; guint screen_num; GtkWidget *window; - NaTray *tray; - GtkWidget *box; + GtkWidget *traybox; GtkLabel *count_label; } TrayData; @@ -56,7 +60,7 @@ update_child_count (TrayData *data) if (!gtk_widget_get_realized (data->window)) return; - gtk_container_foreach (GTK_CONTAINER (data->box), (GtkCallback) do_add, &n_children); + gtk_container_foreach (GTK_CONTAINER (data->traybox), (GtkCallback) do_add, &n_children); g_snprintf (text, sizeof (text), "%u icons", n_children); gtk_label_set_text (data->count_label, text); @@ -66,7 +70,7 @@ static void tray_added_cb (GtkContainer *box, GtkWidget *icon, TrayData *data) { g_print ("[Screen %u tray %p] Child %p added to tray: \"%s\"\n", - data->screen_num, data->tray, icon, "XXX");//na_tray_child_get_title (icon)); + data->screen_num, data->traybox, icon, "XXX");//na_tray_child_get_title (icon)); update_child_count (data); } @@ -75,7 +79,7 @@ static void tray_removed_cb (GtkContainer *box, GtkWidget *icon, TrayData *data) { g_print ("[Screen %u tray %p] Child %p removed from tray\n", - data->screen_num, data->tray, icon); + data->screen_num, data->traybox, icon); update_child_count (data); } @@ -85,9 +89,9 @@ static void orientation_changed_cb (GtkComboBox *combo, TrayData *data) GtkOrientation orientation = (GtkOrientation) gtk_combo_box_get_active (combo); g_print ("[Screen %u tray %p] Setting orientation to \"%s\"\n", - data->screen_num, data->tray, orientation == 0 ? "horizontal" : "vertical"); + data->screen_num, data->traybox, orientation == 0 ? "horizontal" : "vertical"); - na_tray_set_orientation (data->tray, orientation); + gtk_orientable_set_orientation (GTK_ORIENTABLE (data->traybox), orientation); } static void @@ -184,12 +188,11 @@ create_tray_on_screen (GdkScreen *screen, #endif gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); - data->tray = na_tray_new_for_screen (screen, GTK_ORIENTATION_HORIZONTAL); - gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->tray), TRUE, TRUE, 0); + data->traybox = na_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (data->traybox), TRUE, TRUE, 0); - data->box = gtk_bin_get_child (GTK_BIN (gtk_bin_get_child (GTK_BIN (data->tray)))); - g_signal_connect_after (data->box, "add", G_CALLBACK (tray_added_cb), data); - g_signal_connect_after (data->box, "remove", G_CALLBACK (tray_removed_cb), data); + g_signal_connect_after (data->traybox, "add", G_CALLBACK (tray_added_cb), data); + g_signal_connect_after (data->traybox, "remove", G_CALLBACK (tray_removed_cb), data); gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0); @@ -205,14 +208,66 @@ create_tray_on_screen (GdkScreen *screen, return data; } +static gboolean +signal_handler (gpointer data G_GNUC_UNUSED) +{ + gtk_main_quit (); + + return FALSE; +} + +#ifdef PROVIDE_WATCHER_SERVICE +static GfStatusNotifierWatcher * +status_notifier_watcher_maybe_new (void) +{ + GDBusProxy *proxy; + GError *error = NULL; + GfStatusNotifierWatcher *service = NULL; + + /* check if the service already exists + * FIXME: is that a not-too-stupid way of doing it? bah, so long as it works. + * it's for testing purposes only anyway. */ + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + NULL, &error); + if (proxy) + g_object_unref (proxy); + else + { + g_warning ("Failed to connect to org.kde.StatusNotifierWatcher (%s), starting our own.", + error->message); + g_clear_error (&error); + + service = gf_status_notifier_watcher_new (); + } + + return service; +} +#endif + int main (int argc, char *argv[]) { GdkDisplay *display; GdkScreen *screen; +#ifdef PROVIDE_WATCHER_SERVICE + GfStatusNotifierWatcher *service; +#endif gtk_init (&argc, &argv); + g_unix_signal_add (SIGTERM, signal_handler, NULL); + g_unix_signal_add (SIGINT, signal_handler, NULL); + +#ifdef PROVIDE_WATCHER_SERVICE + service = status_notifier_watcher_maybe_new (); +#endif + gtk_window_set_default_icon_name (NOTIFICATION_AREA_ICON); display = gdk_display_get_default (); @@ -222,5 +277,10 @@ main (int argc, char *argv[]) gtk_main (); +#ifdef PROVIDE_WATCHER_SERVICE + if (service) + g_object_unref (service); +#endif + return 0; } -- cgit v1.2.1