gnome-shell/src/shell-window-preview-layout.c
Sebastian Keller 8c40b48a09 shell/window-preview-layout: Fix memory leaks
The WindowInfo allocated when adding a window was not getting free'd
when the window was getting removed again or the layout was getting
disposed. Also the hash table in which the WindowInfos are stored was
not getting free'd on destruction either. Both could result in small
leaks after closing the overview.

Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5238
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2256>
2022-04-22 15:02:29 +00:00

488 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: (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;
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 (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;
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;
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;
}