summaryrefslogtreecommitdiffstats
path: root/src/shell-tray-manager.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/shell-tray-manager.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/shell-tray-manager.c b/src/shell-tray-manager.c
new file mode 100644
index 0000000..e23179f
--- /dev/null
+++ b/src/shell-tray-manager.c
@@ -0,0 +1,375 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include <clutter/clutter.h>
+#include <clutter/x11/clutter-x11.h>
+#include <girepository.h>
+#include <gtk/gtk.h>
+#include <meta/display.h>
+
+#include "shell-tray-manager.h"
+#include "na-tray-manager.h"
+
+#include "shell-tray-icon.h"
+#include "shell-embedded-window.h"
+#include "shell-global.h"
+
+typedef struct _ShellTrayManagerPrivate ShellTrayManagerPrivate;
+
+struct _ShellTrayManager
+{
+ GObject parent_instance;
+
+ ShellTrayManagerPrivate *priv;
+};
+
+struct _ShellTrayManagerPrivate {
+ NaTrayManager *na_manager;
+ ClutterColor bg_color;
+
+ GHashTable *icons;
+ StWidget *theme_widget;
+};
+
+typedef struct {
+ ShellTrayManager *manager;
+ GtkWidget *socket;
+ GtkWidget *window;
+ ClutterActor *actor;
+} ShellTrayManagerChild;
+
+enum {
+ PROP_0,
+
+ PROP_BG_COLOR
+};
+
+/* Signals */
+enum
+{
+ TRAY_ICON_ADDED,
+ TRAY_ICON_REMOVED,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (ShellTrayManager, shell_tray_manager, G_TYPE_OBJECT);
+
+static guint shell_tray_manager_signals [LAST_SIGNAL] = { 0 };
+
+static const ClutterColor default_color = { 0x00, 0x00, 0x00, 0xff };
+
+static void shell_tray_manager_release_resources (ShellTrayManager *manager);
+
+static void na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+static void na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *child, gpointer manager);
+
+static void
+free_tray_icon (gpointer data)
+{
+ ShellTrayManagerChild *child = data;
+
+ gtk_widget_destroy (child->window);
+ if (child->actor)
+ {
+ g_signal_handlers_disconnect_matched (child->actor, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, child);
+ g_object_unref (child->actor);
+ }
+ g_slice_free (ShellTrayManagerChild, child);
+}
+
+static void
+shell_tray_manager_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BG_COLOR:
+ {
+ ClutterColor *color = g_value_get_boxed (value);
+ if (color)
+ manager->priv->bg_color = *color;
+ else
+ manager->priv->bg_color = default_color;
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_tray_manager_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BG_COLOR:
+ g_value_set_boxed (value, &manager->priv->bg_color);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+shell_tray_manager_init (ShellTrayManager *manager)
+{
+ manager->priv = shell_tray_manager_get_instance_private (manager);
+
+ manager->priv->bg_color = default_color;
+}
+
+static void
+shell_tray_manager_finalize (GObject *object)
+{
+ ShellTrayManager *manager = SHELL_TRAY_MANAGER (object);
+
+ shell_tray_manager_release_resources (manager);
+
+ G_OBJECT_CLASS (shell_tray_manager_parent_class)->finalize (object);
+}
+
+static void
+shell_tray_manager_class_init (ShellTrayManagerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = shell_tray_manager_finalize;
+ gobject_class->set_property = shell_tray_manager_set_property;
+ gobject_class->get_property = shell_tray_manager_get_property;
+
+ shell_tray_manager_signals[TRAY_ICON_ADDED] =
+ g_signal_new ("tray-icon-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CLUTTER_TYPE_ACTOR);
+ shell_tray_manager_signals[TRAY_ICON_REMOVED] =
+ g_signal_new ("tray-icon-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ CLUTTER_TYPE_ACTOR);
+
+ /* Lifting the CONSTRUCT_ONLY here isn't hard; you just need to
+ * iterate through the icons, reset the background pixmap, and
+ * call na_tray_child_force_redraw()
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_BG_COLOR,
+ g_param_spec_boxed ("bg-color",
+ "BG Color",
+ "Background color (only if we don't have transparency)",
+ CLUTTER_TYPE_COLOR,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+ShellTrayManager *
+shell_tray_manager_new (void)
+{
+ return g_object_new (SHELL_TYPE_TRAY_MANAGER, NULL);
+}
+
+static void
+shell_tray_manager_ensure_resources (ShellTrayManager *manager)
+{
+ if (manager->priv->na_manager != NULL)
+ return;
+
+ manager->priv->icons = g_hash_table_new_full (NULL, NULL,
+ NULL, free_tray_icon);
+
+ manager->priv->na_manager = na_tray_manager_new ();
+
+ g_signal_connect (manager->priv->na_manager, "tray-icon-added",
+ G_CALLBACK (na_tray_icon_added), manager);
+ g_signal_connect (manager->priv->na_manager, "tray-icon-removed",
+ G_CALLBACK (na_tray_icon_removed), manager);
+}
+
+static void
+shell_tray_manager_release_resources (ShellTrayManager *manager)
+{
+ g_clear_object (&manager->priv->na_manager);
+ g_clear_pointer (&manager->priv->icons, g_hash_table_destroy);
+}
+
+static void
+shell_tray_manager_style_changed (StWidget *theme_widget,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ StThemeNode *theme_node;
+ StIconColors *icon_colors;
+
+ if (manager->priv->na_manager == NULL)
+ return;
+
+ theme_node = st_widget_get_theme_node (theme_widget);
+ icon_colors = st_theme_node_get_icon_colors (theme_node);
+ na_tray_manager_set_colors (manager->priv->na_manager,
+ &icon_colors->foreground, &icon_colors->warning,
+ &icon_colors->error, &icon_colors->success);
+}
+
+static void
+shell_tray_manager_manage_screen_internal (ShellTrayManager *manager)
+{
+ shell_tray_manager_ensure_resources (manager);
+ na_tray_manager_manage_screen (manager->priv->na_manager);
+}
+
+void
+shell_tray_manager_manage_screen (ShellTrayManager *manager,
+ StWidget *theme_widget)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ g_set_weak_pointer (&manager->priv->theme_widget, theme_widget);
+
+ if (meta_display_get_x11_display (display) != NULL)
+ shell_tray_manager_manage_screen_internal (manager);
+
+ g_signal_connect_object (display, "x11-display-setup",
+ G_CALLBACK (shell_tray_manager_manage_screen_internal),
+ manager, G_CONNECT_SWAPPED);
+ g_signal_connect_object (display, "x11-display-closing",
+ G_CALLBACK (shell_tray_manager_release_resources),
+ manager, G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (theme_widget, "style-changed",
+ G_CALLBACK (shell_tray_manager_style_changed),
+ manager, 0);
+ shell_tray_manager_style_changed (theme_widget, manager);
+}
+
+void
+shell_tray_manager_unmanage_screen (ShellTrayManager *manager)
+{
+ MetaDisplay *display = shell_global_get_display (shell_global_get ());
+
+ g_signal_handlers_disconnect_by_data (display, manager);
+
+ if (manager->priv->theme_widget != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (manager->priv->theme_widget,
+ G_CALLBACK (shell_tray_manager_style_changed),
+ manager);
+ }
+ g_set_weak_pointer (&manager->priv->theme_widget, NULL);
+
+ shell_tray_manager_release_resources (manager);
+}
+
+static void
+shell_tray_manager_child_on_realize (GtkWidget *widget,
+ ShellTrayManagerChild *child)
+{
+ /* If the tray child is using an RGBA colormap (and so we have real
+ * transparency), we don't need to worry about the background. If
+ * not, we obey the bg-color property by creating a cairo pattern of
+ * that color and setting it as our background. Then "parent-relative"
+ * background on the socket and the plug within that will cause
+ * the icons contents to appear on top of our background color.
+ */
+ if (!na_tray_child_has_alpha (NA_TRAY_CHILD (child->socket)))
+ {
+ ClutterColor color = child->manager->priv->bg_color;
+ cairo_pattern_t *bg_pattern;
+
+ bg_pattern = cairo_pattern_create_rgb (color.red / 255.,
+ color.green / 255.,
+ color.blue / 255.);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gdk_window_set_background_pattern (gtk_widget_get_window (widget),
+ bg_pattern);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+ cairo_pattern_destroy (bg_pattern);
+ }
+}
+
+static void
+on_plug_added (GtkSocket *socket,
+ ShellTrayManager *manager)
+{
+ ShellTrayManagerChild *child;
+
+ g_signal_handlers_disconnect_by_func (socket, on_plug_added, manager);
+
+ child = g_hash_table_lookup (manager->priv->icons, socket);
+
+ child->actor = shell_tray_icon_new (SHELL_EMBEDDED_WINDOW (child->window));
+ g_object_ref_sink (child->actor);
+
+ g_signal_emit (manager, shell_tray_manager_signals[TRAY_ICON_ADDED], 0,
+ child->actor);
+}
+
+static void
+na_tray_icon_added (NaTrayManager *na_manager, GtkWidget *socket,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ GtkWidget *win;
+ ShellTrayManagerChild *child;
+
+ win = shell_embedded_window_new ();
+ gtk_container_add (GTK_CONTAINER (win), socket);
+
+ /* The visual of the socket matches that of its contents; make
+ * the window we put it in match that as well */
+ gtk_widget_set_visual (win, gtk_widget_get_visual (socket));
+
+ child = g_slice_new0 (ShellTrayManagerChild);
+ child->manager = manager;
+ child->window = win;
+ child->socket = socket;
+
+ g_signal_connect (win, "realize",
+ G_CALLBACK (shell_tray_manager_child_on_realize), child);
+
+ gtk_widget_show_all (win);
+
+ g_hash_table_insert (manager->priv->icons, socket, child);
+
+ g_signal_connect (socket, "plug-added", G_CALLBACK (on_plug_added), manager);
+}
+
+static void
+na_tray_icon_removed (NaTrayManager *na_manager, GtkWidget *socket,
+ gpointer user_data)
+{
+ ShellTrayManager *manager = user_data;
+ ShellTrayManagerChild *child;
+
+ child = g_hash_table_lookup (manager->priv->icons, socket);
+ g_return_if_fail (child != NULL);
+
+ if (child->actor != NULL)
+ {
+ /* Only emit signal if a corresponding tray-icon-added signal was emitted,
+ that is, if embedding did not fail and we got a plug-added
+ */
+ g_signal_emit (manager,
+ shell_tray_manager_signals[TRAY_ICON_REMOVED], 0,
+ child->actor);
+ }
+ g_hash_table_remove (manager->priv->icons, socket);
+}