diff options
Diffstat (limited to 'src/shell-tray-manager.c')
-rw-r--r-- | src/shell-tray-manager.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/shell-tray-manager.c b/src/shell-tray-manager.c new file mode 100644 index 0000000..c8e3259 --- /dev/null +++ b/src/shell-tray-manager.c @@ -0,0 +1,374 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + +#include <clutter/clutter.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_free (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_new0 (ShellTrayManagerChild, 1); + 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); +} |