gnome-shell/src/shell-window-preview-layout.c
Carlos Garnacho 356bab1121 shell: Use swapped signal connection for MetaWindowActor::destroy
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>
2023-05-03 14:00:33 +00:00

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;
}