summaryrefslogtreecommitdiffstats
path: root/src/shell-window-preview-layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell-window-preview-layout.c')
-rw-r--r--src/shell-window-preview-layout.c495
1 files changed, 495 insertions, 0 deletions
diff --git a/src/shell-window-preview-layout.c b/src/shell-window-preview-layout.c
new file mode 100644
index 0000000..fa3cc1f
--- /dev/null
+++ b/src/shell-window-preview-layout.c
@@ -0,0 +1,495 @@
+#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;
+}