diff options
Diffstat (limited to 'src/shell-gtk-embed.c')
-rw-r--r-- | src/shell-gtk-embed.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/src/shell-gtk-embed.c b/src/shell-gtk-embed.c new file mode 100644 index 0000000..2ad18a1 --- /dev/null +++ b/src/shell-gtk-embed.c @@ -0,0 +1,364 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + +#include "shell-embedded-window-private.h" +#include "shell-global.h" +#include "shell-util.h" + +#include <gdk/gdkx.h> +#include <meta/display.h> +#include <meta/window.h> + +enum { + PROP_0, + + PROP_WINDOW +}; + +typedef struct _ShellGtkEmbedPrivate ShellGtkEmbedPrivate; + +struct _ShellGtkEmbedPrivate +{ + ShellEmbeddedWindow *window; + + ClutterActor *window_actor; + gulong window_actor_destroyed_handler; + + gulong window_created_handler; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (ShellGtkEmbed, shell_gtk_embed, CLUTTER_TYPE_CLONE); + +static void shell_gtk_embed_set_window (ShellGtkEmbed *embed, + ShellEmbeddedWindow *window); + +static void +shell_gtk_embed_on_window_destroy (GtkWidget *object, + ShellGtkEmbed *embed) +{ + shell_gtk_embed_set_window (embed, NULL); +} + +static void +shell_gtk_embed_remove_window_actor (ShellGtkEmbed *embed) +{ + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + if (priv->window_actor) + { + g_clear_signal_handler (&priv->window_actor_destroyed_handler, + priv->window_actor); + + g_object_unref (priv->window_actor); + priv->window_actor = NULL; + } + + clutter_clone_set_source (CLUTTER_CLONE (embed), NULL); +} + +static void +shell_gtk_embed_window_created_cb (MetaDisplay *display, + MetaWindow *window, + ShellGtkEmbed *embed) +{ + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + Window xwindow = meta_window_get_xwindow (window); + GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (priv->window)); + + if (gdk_window && xwindow == gdk_x11_window_get_xid (gdk_window)) + { + ClutterActor *window_actor = + CLUTTER_ACTOR (meta_window_get_compositor_private (window)); + GCallback remove_cb = G_CALLBACK (shell_gtk_embed_remove_window_actor); + cairo_region_t *empty_region; + + clutter_clone_set_source (CLUTTER_CLONE (embed), window_actor); + + /* We want to explicitly clear the clone source when the window + actor is destroyed because otherwise we might end up keeping + it alive after it has been disposed. Otherwise this can cause + a crash if there is a paint after mutter notices that the top + level window has been destroyed, which causes it to dispose + the window, and before the tray manager notices that the + window is gone which would otherwise reset the window and + unref the clone */ + priv->window_actor = g_object_ref (window_actor); + priv->window_actor_destroyed_handler = + g_signal_connect_swapped (window_actor, + "destroy", + remove_cb, + embed); + + /* Hide the original actor otherwise it will appear in the scene + as a normal window */ + clutter_actor_set_opacity (window_actor, 0); + + /* Also make sure it (or any of its children) doesn't block + events on wayland */ + shell_util_set_hidden_from_pick (window_actor, TRUE); + + /* Set an empty input shape on the window so that it can't get + any input. This probably isn't the ideal way to achieve this. + It would probably be better to force the window to go behind + Mutter's guard window, but this is quite difficult to do as + Mutter doesn't manage the stacking for override redirect + windows and the guard window is repeatedly lowered to the + bottom of the stack. */ + empty_region = cairo_region_create (); + gdk_window_input_shape_combine_region (gdk_window, + empty_region, + 0, 0 /* offset x/y */); + cairo_region_destroy (empty_region); + + gdk_window_lower (gdk_window); + + /* Now that we've found the window we don't need to listen for + new windows anymore */ + g_clear_signal_handler (&priv->window_created_handler, + display); + } +} + +static void +shell_gtk_embed_on_window_mapped (GtkWidget *object, + ShellGtkEmbed *embed) +{ + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + MetaDisplay *display = shell_global_get_display (shell_global_get ()); + + if (priv->window_created_handler == 0 && priv->window_actor == NULL) + /* Listen for new windows so we can detect when Mutter has + created a MutterWindow for this window */ + priv->window_created_handler = + g_signal_connect (display, + "window-created", + G_CALLBACK (shell_gtk_embed_window_created_cb), + embed); +} + +static void +shell_gtk_embed_set_window (ShellGtkEmbed *embed, + ShellEmbeddedWindow *window) +{ + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + MetaDisplay *display = shell_global_get_display (shell_global_get ()); + + if (priv->window) + { + g_clear_signal_handler (&priv->window_created_handler, display); + + shell_gtk_embed_remove_window_actor (embed); + + _shell_embedded_window_set_actor (priv->window, NULL); + + g_object_unref (priv->window); + + g_signal_handlers_disconnect_by_func (priv->window, + (gpointer)shell_gtk_embed_on_window_destroy, + embed); + + g_signal_handlers_disconnect_by_func (priv->window, + (gpointer)shell_gtk_embed_on_window_mapped, + embed); + } + + priv->window = window; + + if (priv->window) + { + g_object_ref (priv->window); + + _shell_embedded_window_set_actor (priv->window, embed); + + g_signal_connect (priv->window, "destroy", + G_CALLBACK (shell_gtk_embed_on_window_destroy), embed); + + g_signal_connect (priv->window, "map", + G_CALLBACK (shell_gtk_embed_on_window_mapped), embed); + } + + clutter_actor_queue_relayout (CLUTTER_ACTOR (embed)); +} + +static void +shell_gtk_embed_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (object); + + switch (prop_id) + { + case PROP_WINDOW: + shell_gtk_embed_set_window (embed, (ShellEmbeddedWindow *)g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +shell_gtk_embed_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (object); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, priv->window); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +shell_gtk_embed_get_preferred_width (ClutterActor *actor, + float for_height, + float *min_width_p, + float *natural_width_p) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + if (priv->window + && gtk_widget_get_visible (GTK_WIDGET (priv->window))) + { + GtkRequisition min_req, natural_req; + gtk_widget_get_preferred_size (GTK_WIDGET (priv->window), &min_req, &natural_req); + + *min_width_p = min_req.width; + *natural_width_p = natural_req.width; + } + else + *min_width_p = *natural_width_p = 0; +} + +static void +shell_gtk_embed_get_preferred_height (ClutterActor *actor, + float for_width, + float *min_height_p, + float *natural_height_p) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + if (priv->window + && gtk_widget_get_visible (GTK_WIDGET (priv->window))) + { + GtkRequisition min_req, natural_req; + gtk_widget_get_preferred_size (GTK_WIDGET (priv->window), &min_req, &natural_req); + + *min_height_p = min_req.height; + *natural_height_p = natural_req.height; + } + else + *min_height_p = *natural_height_p = 0; +} + +static void +shell_gtk_embed_allocate (ClutterActor *actor, + const ClutterActorBox *box) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + float wx, wy; + + CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)-> + allocate (actor, box); + + /* Find the actor's new coordinates in terms of the stage (which is + * priv->window's parent window. + */ + clutter_actor_get_transformed_position (actor, &wx, &wy); + + _shell_embedded_window_allocate (priv->window, + (int)(0.5 + wx), (int)(0.5 + wy), + box->x2 - box->x1, + box->y2 - box->y1); +} + +static void +shell_gtk_embed_map (ClutterActor *actor) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + _shell_embedded_window_map (priv->window); + + CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)->map (actor); +} + +static void +shell_gtk_embed_unmap (ClutterActor *actor) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (actor); + ShellGtkEmbedPrivate *priv = shell_gtk_embed_get_instance_private (embed); + + _shell_embedded_window_unmap (priv->window); + + CLUTTER_ACTOR_CLASS (shell_gtk_embed_parent_class)->unmap (actor); +} + +static void +shell_gtk_embed_dispose (GObject *object) +{ + ShellGtkEmbed *embed = SHELL_GTK_EMBED (object); + + G_OBJECT_CLASS (shell_gtk_embed_parent_class)->dispose (object); + + shell_gtk_embed_set_window (embed, NULL); +} + +static void +shell_gtk_embed_class_init (ShellGtkEmbedClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + object_class->get_property = shell_gtk_embed_get_property; + object_class->set_property = shell_gtk_embed_set_property; + object_class->dispose = shell_gtk_embed_dispose; + + actor_class->get_preferred_width = shell_gtk_embed_get_preferred_width; + actor_class->get_preferred_height = shell_gtk_embed_get_preferred_height; + actor_class->allocate = shell_gtk_embed_allocate; + actor_class->map = shell_gtk_embed_map; + actor_class->unmap = shell_gtk_embed_unmap; + + g_object_class_install_property (object_class, + PROP_WINDOW, + g_param_spec_object ("window", + "Window", + "ShellEmbeddedWindow to embed", + SHELL_TYPE_EMBEDDED_WINDOW, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +shell_gtk_embed_init (ShellGtkEmbed *embed) +{ +} + +/* + * Public API + */ +ClutterActor * +shell_gtk_embed_new (ShellEmbeddedWindow *window) +{ + g_return_val_if_fail (SHELL_IS_EMBEDDED_WINDOW (window), NULL); + + return g_object_new (SHELL_TYPE_GTK_EMBED, + "window", window, + NULL); +} |