4ff94f80a0
Now that Xwayland startup is asynchronous, the function may be called before X11 is available, resulting in a crash. Fix this by only managing the tray immediately if we already have an X11 display, and wait for it to be set up otherwise. Likewise, unmanage the screen when X11 becomes unavailable. https://gitlab.gnome.org/GNOME/gnome-shell/issues/2308
376 lines
11 KiB
C
376 lines
11 KiB
C
/* -*- 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);
|
|
}
|