356bab1121
The callback would schedule a clutter_actor_destroy() on the first
argument. Unless told otherwise, this is the same actor that is
already being destroyed, so this is a no-op.
Instead, the intent is to make the clone follow up destruction of
the window actor so that it results in correct removal of this
window from the ShellWindowPreviewLayout. Use a swapped connection
to pass the clone actor as the first argument of the signal callback.
Fixes: 04c781674c
("Move WindowPreviewLayout from JS to C")
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6570
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2756>
496 lines
15 KiB
C
496 lines
15 KiB
C
#include "config.h"
|
|
|
|
#include <clutter/clutter.h>
|
|
#include <meta/window.h>
|
|
#include "shell-window-preview-layout.h"
|
|
|
|
typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate;
|
|
struct _ShellWindowPreviewLayoutPrivate
|
|
{
|
|
ClutterActor *container;
|
|
GHashTable *windows;
|
|
|
|
ClutterActorBox bounding_box;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_BOUNDING_BOX,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (ShellWindowPreviewLayout, shell_window_preview_layout,
|
|
CLUTTER_TYPE_LAYOUT_MANAGER);
|
|
|
|
typedef struct _WindowInfo
|
|
{
|
|
MetaWindow *window;
|
|
ClutterActor *window_actor;
|
|
|
|
gulong size_changed_id;
|
|
gulong position_changed_id;
|
|
gulong window_actor_destroy_id;
|
|
gulong destroy_id;
|
|
} WindowInfo;
|
|
|
|
static void
|
|
shell_window_preview_layout_get_property (GObject *object,
|
|
unsigned int property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (object);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_BOUNDING_BOX:
|
|
g_value_set_boxed (value, &priv->bounding_box);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_set_container (ClutterLayoutManager *layout,
|
|
ClutterContainer *container)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
ClutterLayoutManagerClass *parent_class;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
priv->container = CLUTTER_ACTOR (container);
|
|
|
|
parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (shell_window_preview_layout_parent_class);
|
|
parent_class->set_container (layout, container);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_get_preferred_width (ClutterLayoutManager *layout,
|
|
ClutterContainer *container,
|
|
float for_height,
|
|
float *min_width_p,
|
|
float *natural_width_p)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
if (min_width_p)
|
|
*min_width_p = 0;
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = clutter_actor_box_get_width (&priv->bounding_box);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_get_preferred_height (ClutterLayoutManager *layout,
|
|
ClutterContainer *container,
|
|
float for_width,
|
|
float *min_height_p,
|
|
float *natural_height_p)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
if (min_height_p)
|
|
*min_height_p = 0;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = clutter_actor_box_get_height (&priv->bounding_box);
|
|
}
|
|
|
|
|
|
static void
|
|
shell_window_preview_layout_allocate (ClutterLayoutManager *layout,
|
|
ClutterContainer *container,
|
|
const ClutterActorBox *box)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
float scale_x, scale_y;
|
|
float bounding_box_width, bounding_box_height;
|
|
ClutterActorIter iter;
|
|
ClutterActor *child;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
bounding_box_width = clutter_actor_box_get_width (&priv->bounding_box);
|
|
bounding_box_height = clutter_actor_box_get_height (&priv->bounding_box);
|
|
|
|
if (bounding_box_width == 0)
|
|
scale_x = 1.f;
|
|
else
|
|
scale_x = clutter_actor_box_get_width (box) / bounding_box_width;
|
|
|
|
if (bounding_box_height == 0)
|
|
scale_y = 1.f;
|
|
else
|
|
scale_y = clutter_actor_box_get_height (box) / bounding_box_height;
|
|
|
|
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
|
|
while (clutter_actor_iter_next (&iter, &child))
|
|
{
|
|
ClutterActorBox child_box = { 0, };
|
|
WindowInfo *window_info;
|
|
|
|
if (!clutter_actor_is_visible (child))
|
|
continue;
|
|
|
|
window_info = g_hash_table_lookup (priv->windows, child);
|
|
|
|
if (window_info)
|
|
{
|
|
MetaRectangle buffer_rect;
|
|
float child_nat_width, child_nat_height;
|
|
|
|
meta_window_get_buffer_rect (window_info->window, &buffer_rect);
|
|
|
|
clutter_actor_box_set_origin (&child_box,
|
|
buffer_rect.x - priv->bounding_box.x1,
|
|
buffer_rect.y - priv->bounding_box.y1);
|
|
|
|
clutter_actor_get_preferred_size (child, NULL, NULL,
|
|
&child_nat_width, &child_nat_height);
|
|
|
|
clutter_actor_box_set_size (&child_box, child_nat_width, child_nat_height);
|
|
|
|
child_box.x1 *= scale_x;
|
|
child_box.x2 *= scale_x;
|
|
child_box.y1 *= scale_y;
|
|
child_box.y2 *= scale_y;
|
|
|
|
clutter_actor_allocate (child, &child_box);
|
|
}
|
|
else
|
|
{
|
|
float x, y;
|
|
|
|
clutter_actor_get_fixed_position (child, &x, &y);
|
|
clutter_actor_allocate_preferred_size (child, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_layout_changed (ShellWindowPreviewLayout *self)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
gboolean first_rect = TRUE;
|
|
MetaRectangle bounding_rect = { 0, };
|
|
ClutterActorBox old_bounding_box;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
old_bounding_box =
|
|
(ClutterActorBox) CLUTTER_ACTOR_BOX_INIT (priv->bounding_box.x1,
|
|
priv->bounding_box.y1,
|
|
priv->bounding_box.x2,
|
|
priv->bounding_box.y2);
|
|
|
|
g_hash_table_iter_init (&iter, priv->windows);
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
WindowInfo *window_info = value;
|
|
MetaRectangle frame_rect;
|
|
|
|
meta_window_get_frame_rect (window_info->window, &frame_rect);
|
|
|
|
if (first_rect)
|
|
{
|
|
bounding_rect = frame_rect;
|
|
first_rect = FALSE;
|
|
continue;
|
|
}
|
|
|
|
meta_rectangle_union (&frame_rect, &bounding_rect, &bounding_rect);
|
|
}
|
|
|
|
clutter_actor_box_set_origin (&priv->bounding_box,
|
|
(float) bounding_rect.x,
|
|
(float) bounding_rect.y);
|
|
clutter_actor_box_set_size (&priv->bounding_box,
|
|
(float) bounding_rect.width,
|
|
(float) bounding_rect.height);
|
|
|
|
if (!clutter_actor_box_equal (&priv->bounding_box, &old_bounding_box))
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_BOUNDING_BOX]);
|
|
|
|
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self));
|
|
}
|
|
|
|
static void
|
|
on_window_size_position_changed (MetaWindow *window,
|
|
ShellWindowPreviewLayout *self)
|
|
{
|
|
on_layout_changed (self);
|
|
}
|
|
|
|
static void
|
|
on_window_destroyed (ClutterActor *actor)
|
|
{
|
|
clutter_actor_destroy (actor);
|
|
}
|
|
|
|
static void
|
|
on_actor_destroyed (ClutterActor *actor,
|
|
ShellWindowPreviewLayout *self)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
WindowInfo *window_info;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
window_info = g_hash_table_lookup (priv->windows, actor);
|
|
g_assert (window_info != NULL);
|
|
|
|
shell_window_preview_layout_remove_window (self, window_info->window);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_dispose (GObject *gobject)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
g_hash_table_iter_init (&iter, priv->windows);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
ClutterActor *actor = key;
|
|
WindowInfo *info = value;
|
|
|
|
g_clear_signal_handler (&info->size_changed_id, info->window);
|
|
g_clear_signal_handler (&info->position_changed_id, info->window);
|
|
g_clear_signal_handler (&info->window_actor_destroy_id, info->window_actor);
|
|
g_clear_signal_handler (&info->destroy_id, actor);
|
|
|
|
clutter_actor_remove_child (priv->container, actor);
|
|
}
|
|
|
|
g_hash_table_remove_all (priv->windows);
|
|
|
|
G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_finalize (GObject *gobject)
|
|
{
|
|
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject);
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
g_hash_table_destroy (priv->windows);
|
|
|
|
G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_init (ShellWindowPreviewLayout *self)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
priv->windows = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) g_free);
|
|
}
|
|
|
|
static void
|
|
shell_window_preview_layout_class_init (ShellWindowPreviewLayoutClass *klass)
|
|
{
|
|
ClutterLayoutManagerClass *layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
layout_class->get_preferred_width = shell_window_preview_layout_get_preferred_width;
|
|
layout_class->get_preferred_height = shell_window_preview_layout_get_preferred_height;
|
|
layout_class->allocate = shell_window_preview_layout_allocate;
|
|
layout_class->set_container = shell_window_preview_layout_set_container;
|
|
|
|
gobject_class->dispose = shell_window_preview_layout_dispose;
|
|
gobject_class->finalize = shell_window_preview_layout_finalize;
|
|
gobject_class->get_property = shell_window_preview_layout_get_property;
|
|
|
|
/**
|
|
* ShellWindowPreviewLayout:bounding-box:
|
|
*/
|
|
obj_props[PROP_BOUNDING_BOX] =
|
|
g_param_spec_boxed ("bounding-box",
|
|
"Bounding Box",
|
|
"Bounding Box",
|
|
CLUTTER_TYPE_ACTOR_BOX,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
|
|
}
|
|
|
|
/**
|
|
* shell_window_preview_layout_add_window:
|
|
* @self: a #ShellWindowPreviewLayout
|
|
* @window: the #MetaWindow
|
|
*
|
|
* Creates a ClutterActor drawing the texture of @window and adds it
|
|
* to the container. If @window is already part of the preview, this
|
|
* function will do nothing.
|
|
*
|
|
* Returns: (nullable) (transfer none): The newly created actor drawing @window
|
|
*/
|
|
ClutterActor *
|
|
shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self,
|
|
MetaWindow *window)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
ClutterActor *window_actor, *actor;
|
|
WindowInfo *window_info;
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
|
|
g_return_val_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self), NULL);
|
|
g_return_val_if_fail (META_IS_WINDOW (window), NULL);
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
g_hash_table_iter_init (&iter, priv->windows);
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
WindowInfo *info = value;
|
|
|
|
if (info->window == window)
|
|
return NULL;
|
|
}
|
|
|
|
window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window));
|
|
actor = clutter_clone_new (window_actor);
|
|
|
|
window_info = g_new0 (WindowInfo, 1);
|
|
|
|
window_info->window = window;
|
|
window_info->window_actor = window_actor;
|
|
window_info->size_changed_id =
|
|
g_signal_connect (window, "size-changed",
|
|
G_CALLBACK (on_window_size_position_changed), self);
|
|
window_info->position_changed_id =
|
|
g_signal_connect (window, "position-changed",
|
|
G_CALLBACK (on_window_size_position_changed), self);
|
|
window_info->window_actor_destroy_id =
|
|
g_signal_connect_swapped (window_actor, "destroy",
|
|
G_CALLBACK (on_window_destroyed), actor);
|
|
window_info->destroy_id =
|
|
g_signal_connect (actor, "destroy",
|
|
G_CALLBACK (on_actor_destroyed), self);
|
|
|
|
g_hash_table_insert (priv->windows, actor, window_info);
|
|
|
|
clutter_actor_add_child (priv->container, actor);
|
|
|
|
on_layout_changed (self);
|
|
|
|
return actor;
|
|
}
|
|
|
|
/**
|
|
* shell_window_preview_layout_remove_window:
|
|
* @self: a #ShellWindowPreviewLayout
|
|
* @window: the #MetaWindow
|
|
*
|
|
* Removes a MetaWindow @window from the preview which has been added
|
|
* previously using shell_window_preview_layout_add_window().
|
|
* If @window is not part of preview, this function will do nothing.
|
|
*/
|
|
void
|
|
shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self,
|
|
MetaWindow *window)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
ClutterActor *actor;
|
|
WindowInfo *window_info = NULL;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_return_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self));
|
|
g_return_if_fail (META_IS_WINDOW (window));
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
g_hash_table_iter_init (&iter, priv->windows);
|
|
while (g_hash_table_iter_next (&iter, &key, &value))
|
|
{
|
|
WindowInfo *info = value;
|
|
|
|
if (info->window == window)
|
|
{
|
|
actor = CLUTTER_ACTOR (key);
|
|
window_info = info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (window_info == NULL)
|
|
return;
|
|
|
|
g_clear_signal_handler (&window_info->size_changed_id, window);
|
|
g_clear_signal_handler (&window_info->position_changed_id, window);
|
|
g_clear_signal_handler (&window_info->window_actor_destroy_id, window_info->window_actor);
|
|
g_clear_signal_handler (&window_info->destroy_id, actor);
|
|
|
|
g_hash_table_remove (priv->windows, actor);
|
|
|
|
clutter_actor_remove_child (priv->container, actor);
|
|
|
|
on_layout_changed (self);
|
|
}
|
|
|
|
/**
|
|
* shell_window_preview_layout_get_windows:
|
|
* @self: a #ShellWindowPreviewLayout
|
|
*
|
|
* Gets an array of all MetaWindows that were added to the layout
|
|
* using shell_window_preview_layout_add_window(), ordered by the
|
|
* insertion order.
|
|
*
|
|
* Returns: (transfer container) (element-type Meta.Window): The list of windows
|
|
*/
|
|
GList *
|
|
shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self)
|
|
{
|
|
ShellWindowPreviewLayoutPrivate *priv;
|
|
GList *windows = NULL;
|
|
GHashTableIter iter;
|
|
gpointer value;
|
|
|
|
g_return_val_if_fail (SHELL_IS_WINDOW_PREVIEW_LAYOUT (self), NULL);
|
|
|
|
priv = shell_window_preview_layout_get_instance_private (self);
|
|
|
|
g_hash_table_iter_init (&iter, priv->windows);
|
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
|
{
|
|
WindowInfo *window_info = value;
|
|
|
|
windows = g_list_prepend (windows, window_info->window);
|
|
}
|
|
|
|
return windows;
|
|
}
|