From 9a2c8b2592a417051a262a98ddad7f912fe783bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 24 May 2023 14:37:18 +0200 Subject: [PATCH] window: Add suspend state The 'suspend state' is meant to track whether a window is likely to be visible any time soon. The hueristics for this are as follows: * If a window is hidden, it will enter the 'hidden' state. * If a window is visible, and unobscured, it will enter the 'active' state. * If a window is visible, but obscured by another window, it will enter the 'hidden' state. * If there is a mapped clone of a window, it will enter the 'active' state. * If the window has been in the 'hidden' state for 3 seconds, it will enter the 'suspended' state. This will eventually be communicated to Wayland clients so that they can change their behaviour to e.g. save power. Part-of: --- src/compositor/meta-compositor-view-native.c | 2 +- src/compositor/meta-surface-actor.c | 87 +++++++++++++- src/compositor/meta-surface-actor.h | 3 + src/compositor/meta-window-actor-private.h | 6 + src/compositor/meta-window-actor-wayland.c | 8 ++ src/compositor/meta-window-actor-x11.c | 2 +- src/compositor/meta-window-actor.c | 115 ++++++++++++++++++ src/core/window-private.h | 13 ++ src/core/window.c | 118 ++++++++++++++++++- src/meson.build | 4 +- 10 files changed, 346 insertions(+), 12 deletions(-) diff --git a/src/compositor/meta-compositor-view-native.c b/src/compositor/meta-compositor-view-native.c index e9f1ac20c..f2c565864 100644 --- a/src/compositor/meta-compositor-view-native.c +++ b/src/compositor/meta-compositor-view-native.c @@ -161,7 +161,7 @@ find_scanout_candidate (MetaCompositorView *compositor_view, return FALSE; } - if (meta_surface_actor_is_obscured (surface_actor)) + if (meta_surface_actor_is_effectively_obscured (surface_actor)) { meta_topic (META_DEBUG_RENDER, "No direct scanout candidate: surface-actor is obscured"); diff --git a/src/compositor/meta-surface-actor.c b/src/compositor/meta-surface-actor.c index 929763719..a1ffdbe9c 100644 --- a/src/compositor/meta-surface-actor.c +++ b/src/compositor/meta-surface-actor.c @@ -27,6 +27,17 @@ #include "compositor/region-utils.h" #include "meta/meta-shaped-texture.h" +enum +{ + PROP_0, + + PROP_IS_OBSCURED, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + typedef struct _MetaSurfaceActorPrivate { MetaShapedTexture *texture; @@ -35,6 +46,7 @@ typedef struct _MetaSurfaceActorPrivate /* MetaCullable regions, see that documentation for more details */ cairo_region_t *unobscured_region; + gboolean is_obscured; /* Freeze/thaw accounting */ cairo_region_t *pending_damage; @@ -77,6 +89,29 @@ effective_unobscured_region (MetaSurfaceActor *surface_actor) return priv->unobscured_region; } +static void +update_is_obscured (MetaSurfaceActor *surface_actor) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + cairo_region_t *unobscured_region; + gboolean is_obscured; + + unobscured_region = priv->unobscured_region; + + if (unobscured_region) + is_obscured = cairo_region_is_empty (unobscured_region); + else + is_obscured = FALSE; + + if (priv->is_obscured == is_obscured) + return; + + priv->is_obscured = is_obscured; + g_object_notify_by_pspec (G_OBJECT (surface_actor), + obj_props[PROP_IS_OBSCURED]); +} + static void set_unobscured_region (MetaSurfaceActor *surface_actor, cairo_region_t *unobscured_region) @@ -109,6 +144,8 @@ set_unobscured_region (MetaSurfaceActor *surface_actor, cairo_region_intersect_rectangle (priv->unobscured_region, &bounds); } } + + update_is_obscured (surface_actor); } static void @@ -190,6 +227,27 @@ meta_surface_actor_get_paint_volume (ClutterActor *actor, return clutter_paint_volume_set_from_allocation (volume, actor); } +static void +meta_surface_actor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (object); + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + + switch (prop_id) + { + case PROP_IS_OBSCURED: + g_value_set_boolean (value, priv->is_obscured); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void meta_surface_actor_dispose (GObject *object) { @@ -212,9 +270,19 @@ meta_surface_actor_class_init (MetaSurfaceActorClass *klass) ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); object_class->dispose = meta_surface_actor_dispose; + object_class->get_property = meta_surface_actor_get_property; + actor_class->pick = meta_surface_actor_pick; actor_class->get_paint_volume = meta_surface_actor_get_paint_volume; + obj_props[PROP_IS_OBSCURED] = + g_param_spec_boolean ("is-obscured", + "is obscured", + "If the surface actor is fully obscured", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, obj_props); + signals[REPAINT_SCHEDULED] = g_signal_new ("repaint-scheduled", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, @@ -303,6 +371,7 @@ meta_surface_actor_init (MetaSurfaceActor *self) MetaSurfaceActorPrivate *priv = meta_surface_actor_get_instance_private (self); + priv->is_obscured = TRUE; priv->texture = meta_shaped_texture_new (); g_signal_connect_object (priv->texture, "size-changed", G_CALLBACK (texture_size_changed), self, 0); @@ -381,14 +450,22 @@ meta_surface_actor_update_area (MetaSurfaceActor *self, gboolean meta_surface_actor_is_obscured (MetaSurfaceActor *self) { - cairo_region_t *unobscured_region; + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); - unobscured_region = effective_unobscured_region (self); + return priv->is_obscured; +} - if (unobscured_region) - return cairo_region_is_empty (unobscured_region); - else +gboolean +meta_surface_actor_is_effectively_obscured (MetaSurfaceActor *surface_actor) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + + if (clutter_actor_has_mapped_clones (CLUTTER_ACTOR (surface_actor))) return FALSE; + else + return priv->is_obscured; } gboolean diff --git a/src/compositor/meta-surface-actor.h b/src/compositor/meta-surface-actor.h index ff9bedbfe..70eded69d 100644 --- a/src/compositor/meta-surface-actor.h +++ b/src/compositor/meta-surface-actor.h @@ -35,6 +35,9 @@ void meta_surface_actor_update_area (MetaSurfaceActor *self, int height); gboolean meta_surface_actor_is_obscured (MetaSurfaceActor *self); + +gboolean meta_surface_actor_is_effectively_obscured (MetaSurfaceActor *self); + gboolean meta_surface_actor_is_obscured_on_stage_view (MetaSurfaceActor *self, ClutterStageView *stage_view, float *unobscurred_fraction); diff --git a/src/compositor/meta-window-actor-private.h b/src/compositor/meta-window-actor-private.h index a20068e77..766039f7d 100644 --- a/src/compositor/meta-window-actor-private.h +++ b/src/compositor/meta-window-actor-private.h @@ -111,3 +111,9 @@ gboolean meta_window_actor_is_opaque (MetaWindowActor *self); void meta_window_actor_update_regions (MetaWindowActor *self); gboolean meta_window_actor_can_freeze_commits (MetaWindowActor *self); + +void meta_window_actor_add_surface_actor (MetaWindowActor *window_actor, + MetaSurfaceActor *surface_actor); + +void meta_window_actor_remove_surface_actor (MetaWindowActor *window_actor, + MetaSurfaceActor *surface_actor); diff --git a/src/compositor/meta-window-actor-wayland.c b/src/compositor/meta-window-actor-wayland.c index 58b208966..88e1f26f3 100644 --- a/src/compositor/meta-window-actor-wayland.c +++ b/src/compositor/meta-window-actor-wayland.c @@ -199,6 +199,9 @@ set_surface_actor_index (GNode *node, ClutterActor *container = traverse_data->surface_container; ClutterActor *surface_actor = CLUTTER_ACTOR (meta_wayland_surface_get_actor (surface)); + MetaSurfaceContainerActorWayland *surface_container = + META_SURFACE_CONTAINER_ACTOR_WAYLAND (container); + MetaWindowActor *window_actor = surface_container->window_actor; if (clutter_actor_contains (container, surface_actor)) { @@ -212,6 +215,8 @@ set_surface_actor_index (GNode *node, } else { + meta_window_actor_add_surface_actor (window_actor, + META_SURFACE_ACTOR (surface_actor)); clutter_actor_insert_child_at_index (container, surface_actor, traverse_data->index); @@ -250,6 +255,9 @@ meta_window_actor_wayland_rebuild_surface_tree (MetaWindowActor *actor) if (!g_list_find (surface_actors, child_actor)) { + MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (child_actor); + + meta_window_actor_remove_surface_actor (actor, surface_actor); clutter_actor_remove_child (CLUTTER_ACTOR (self->surface_container), child_actor); } diff --git a/src/compositor/meta-window-actor-x11.c b/src/compositor/meta-window-actor-x11.c index bb07a926a..c6cce0ce9 100644 --- a/src/compositor/meta-window-actor-x11.c +++ b/src/compositor/meta-window-actor-x11.c @@ -355,7 +355,7 @@ meta_window_actor_x11_queue_frame_drawn (MetaWindowActor *actor, surface = meta_window_actor_get_surface (META_WINDOW_ACTOR (actor_x11)); if (surface) - is_obscured = meta_surface_actor_is_obscured (surface); + is_obscured = meta_surface_actor_is_effectively_obscured (surface); else is_obscured = FALSE; diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c index 1ac039f4c..e0e736536 100644 --- a/src/compositor/meta-window-actor.c +++ b/src/compositor/meta-window-actor.c @@ -54,6 +54,8 @@ typedef struct _MetaWindowActorPrivate MetaSurfaceActor *surface; + GPtrArray *surface_actors; + int geometry_scale; /* @@ -118,6 +120,11 @@ static MetaSurfaceActor * meta_window_actor_real_get_scanout_candidate (MetaWind static void meta_window_actor_real_assign_surface_actor (MetaWindowActor *self, MetaSurfaceActor *surface_actor); +static void on_cloned (ClutterActor *actor, + ClutterClone *clone); +static void on_decloned (ClutterActor *actor, + ClutterClone *clone); + static void screen_cast_window_iface_init (MetaScreenCastWindowInterface *iface); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaWindowActor, meta_window_actor, CLUTTER_TYPE_ACTOR, @@ -218,7 +225,13 @@ meta_window_actor_init (MetaWindowActor *self) MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); + priv->surface_actors = g_ptr_array_new (); priv->geometry_scale = 1; + + g_signal_connect (self, "cloned", + G_CALLBACK (on_cloned), NULL); + g_signal_connect (self, "decloned", + G_CALLBACK (on_decloned), NULL); } static void @@ -357,6 +370,8 @@ meta_window_actor_real_assign_surface_actor (MetaWindowActor *self, g_clear_object (&priv->surface); priv->surface = g_object_ref_sink (surface_actor); + meta_window_actor_add_surface_actor (self, surface_actor); + if (meta_window_actor_is_frozen (self)) meta_window_actor_set_frozen (self, TRUE); else @@ -371,6 +386,102 @@ meta_window_actor_assign_surface_actor (MetaWindowActor *self, surface_actor); } +static void +is_surface_actor_obscured_changed (MetaSurfaceActor *surface_actor, + GParamSpec *pspec, + MetaWindowActor *window_actor) +{ + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + if (meta_surface_actor_is_obscured (surface_actor)) + meta_window_uninhibit_suspend_state (priv->window); + else + meta_window_inhibit_suspend_state (priv->window); +} + +static void +disconnect_surface_actor_from (MetaSurfaceActor *surface_actor, + MetaWindowActor *window_actor) +{ + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + g_signal_handlers_disconnect_by_func (surface_actor, + is_surface_actor_obscured_changed, + window_actor); + if (!meta_surface_actor_is_obscured (surface_actor)) + meta_window_uninhibit_suspend_state (priv->window); +} + +void +meta_window_actor_add_surface_actor (MetaWindowActor *window_actor, + MetaSurfaceActor *surface_actor) +{ + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + g_signal_connect (surface_actor, + "notify::is-obscured", + G_CALLBACK (is_surface_actor_obscured_changed), + window_actor); + if (!meta_surface_actor_is_obscured (surface_actor)) + meta_window_inhibit_suspend_state (priv->window); + g_ptr_array_add (priv->surface_actors, surface_actor); +} + +void +meta_window_actor_remove_surface_actor (MetaWindowActor *window_actor, + MetaSurfaceActor *surface_actor) +{ + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + disconnect_surface_actor_from (surface_actor, window_actor); + g_ptr_array_remove (priv->surface_actors, surface_actor); +} + +static void +on_clone_notify_mapped (ClutterClone *clone, + GParamSpec *pspec, + MetaWindowActor *window_actor) +{ + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + if (clutter_actor_is_mapped (CLUTTER_ACTOR (clone))) + meta_window_inhibit_suspend_state (priv->window); + else + meta_window_uninhibit_suspend_state (priv->window); +} + +static void +on_cloned (ClutterActor *actor, + ClutterClone *clone) +{ + MetaWindowActor *window_actor = META_WINDOW_ACTOR (actor); + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + g_signal_connect (clone, "notify::mapped", + G_CALLBACK (on_clone_notify_mapped), actor); + if (clutter_actor_is_mapped (CLUTTER_ACTOR (clone))) + meta_window_inhibit_suspend_state (priv->window); +} + +static void +on_decloned (ClutterActor *actor, + ClutterClone *clone) +{ + MetaWindowActor *window_actor = META_WINDOW_ACTOR (actor); + MetaWindowActorPrivate *priv = + meta_window_actor_get_instance_private (window_actor); + + g_signal_handlers_disconnect_by_func (clone, on_clone_notify_mapped, actor); + if (clutter_actor_is_mapped (CLUTTER_ACTOR (clone))) + meta_window_uninhibit_suspend_state (priv->window); +} + static void init_surface_actor (MetaWindowActor *self) { @@ -453,6 +564,10 @@ meta_window_actor_dispose (GObject *object) priv->disposed = TRUE; + g_ptr_array_foreach (priv->surface_actors, + (GFunc) disconnect_surface_actor_from, + self); + g_clear_pointer (&priv->surface_actors, g_ptr_array_unref); g_clear_signal_handler (&priv->stage_views_changed_id, self); meta_compositor_remove_window_actor (compositor, self); diff --git a/src/core/window-private.h b/src/core/window-private.h index 049712911..eca352298 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -119,6 +119,13 @@ typedef enum _MetaWindowUpdateMonitorFlags META_WINDOW_UPDATE_MONITOR_FLAGS_FORCE = 1 << 1, } MetaWindowUpdateMonitorFlags; +typedef enum _MetaWindowSuspendState +{ + META_WINDOW_SUSPEND_STATE_ACTIVE = 1, + META_WINDOW_SUSPEND_STATE_HIDDEN, + META_WINDOW_SUSPEND_STATE_SUSPENDED, +} MetaWindowSuspendState; + typedef struct _MetaPlacementRule { MetaRectangle anchor_rect; @@ -887,3 +894,9 @@ void meta_window_set_frame_xwindow (MetaWindow *window, void meta_window_maybe_apply_size_hints (MetaWindow *window, MetaRectangle *target_rect); + +void meta_window_inhibit_suspend_state (MetaWindow *window); + +void meta_window_uninhibit_suspend_state (MetaWindow *window); + +gboolean meta_window_is_suspended (MetaWindow *window); diff --git a/src/core/window.c b/src/core/window.c index 8d75ba06f..0660bf69f 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -104,6 +104,8 @@ #include #endif +#include "meta-private-enum-types.h" + /* Windows that unmaximize to a size bigger than that fraction of the workarea * will be scaled down to that size (while maintaining aspect ratio). * Windows that cover an area greater then this size are automaximized on map. @@ -112,6 +114,8 @@ #define SNAP_SECURITY_LABEL_PREFIX "snap." +#define SUSPEND_HIDDEN_TIMEOUT_S 3 + /* Each window has a "stamp" which is a non-recycled 64-bit ID. They * start after the end of the XID space so that, for stacking * we can keep a guint64 that represents one or the other @@ -166,6 +170,10 @@ static void initable_iface_init (GInitableIface *initable_iface); typedef struct _MetaWindowPrivate { MetaQueueType queued_types; + + MetaWindowSuspendState suspend_state; + int suspend_state_inhibitors; + guint suspend_timoeut_id; } MetaWindowPrivate; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaWindow, meta_window, G_TYPE_OBJECT, @@ -206,6 +214,7 @@ enum PROP_DISPLAY, PROP_EFFECT, PROP_XWINDOW, + PROP_SUSPEND_STATE, PROP_LAST, }; @@ -350,6 +359,7 @@ meta_window_get_property(GObject *object, GParamSpec *pspec) { MetaWindow *win = META_WINDOW (object); + MetaWindowPrivate *priv = meta_window_get_instance_private (win); switch (prop_id) { @@ -437,6 +447,9 @@ meta_window_get_property(GObject *object, case PROP_XWINDOW: g_value_set_ulong (value, win->xwindow); break; + case PROP_SUSPEND_STATE: + g_value_set_enum (value, priv->suspend_state); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -610,6 +623,14 @@ meta_window_class_init (MetaWindowClass *klass) 0, G_MAXULONG, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + obj_props[PROP_SUSPEND_STATE] = + g_param_spec_enum ("suspend-state", + "Suspend state", + "The suspend state of the window", + META_TYPE_WINDOW_SUSPEND_STATE, + META_WINDOW_SUSPEND_STATE_SUSPENDED, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, PROP_LAST, obj_props); window_signals[WORKSPACE_CHANGED] = @@ -721,11 +742,14 @@ meta_window_class_init (MetaWindowClass *klass) } static void -meta_window_init (MetaWindow *self) +meta_window_init (MetaWindow *window) { - self->stamp = next_window_stamp++; - meta_prefs_add_listener (prefs_changed_callback, self); - self->is_alive = TRUE; + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + priv->suspend_state = META_WINDOW_SUSPEND_STATE_SUSPENDED; + window->stamp = next_window_stamp++; + meta_prefs_add_listener (prefs_changed_callback, window); + window->is_alive = TRUE; } static gboolean @@ -1398,12 +1422,14 @@ void meta_window_unmanage (MetaWindow *window, guint32 timestamp) { + MetaWindowPrivate *priv = meta_window_get_instance_private (window); MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; GList *tmp; meta_verbose ("Unmanaging %s", window->desc); window->unmanaging = TRUE; + g_clear_handle_id (&priv->suspend_timoeut_id, g_source_remove); g_clear_handle_id (&window->unmanage_idle_id, g_source_remove); g_clear_handle_id (&window->close_dialog_timeout_id, g_source_remove); @@ -2082,6 +2108,86 @@ meta_window_force_placement (MetaWindow *window, window->denied_focus_and_not_transient = FALSE; } +static gboolean +enter_suspend_state_cb (gpointer user_data) +{ + MetaWindow *window = META_WINDOW (user_data); + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + priv->suspend_timoeut_id = 0; + + g_return_val_if_fail (priv->suspend_state == META_WINDOW_SUSPEND_STATE_HIDDEN, + G_SOURCE_REMOVE); + + priv->suspend_state = META_WINDOW_SUSPEND_STATE_SUSPENDED; + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_SUSPEND_STATE]); + + return G_SOURCE_REMOVE; +} + +static void +update_suspend_state (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + if (!window->hidden && + priv->suspend_state_inhibitors > 0) + { + priv->suspend_state = META_WINDOW_SUSPEND_STATE_ACTIVE; + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_SUSPEND_STATE]); + g_clear_handle_id (&priv->suspend_timoeut_id, g_source_remove); + } + else if (priv->suspend_state == META_WINDOW_SUSPEND_STATE_ACTIVE) + { + priv->suspend_state = META_WINDOW_SUSPEND_STATE_HIDDEN; + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_SUSPEND_STATE]); + g_return_if_fail (!priv->suspend_timoeut_id); + priv->suspend_timoeut_id = + g_timeout_add_seconds (SUSPEND_HIDDEN_TIMEOUT_S, + enter_suspend_state_cb, + window); + } +} + +void +meta_window_inhibit_suspend_state (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + priv->suspend_state_inhibitors++; + if (priv->suspend_state_inhibitors == 1) + update_suspend_state (window); +} + +void +meta_window_uninhibit_suspend_state (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + g_return_if_fail (priv->suspend_state_inhibitors > 0); + + priv->suspend_state_inhibitors--; + if (priv->suspend_state_inhibitors == 0) + update_suspend_state (window); +} + +gboolean +meta_window_is_suspended (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + switch (priv->suspend_state) + { + case META_WINDOW_SUSPEND_STATE_ACTIVE: + case META_WINDOW_SUSPEND_STATE_HIDDEN: + return FALSE; + case META_WINDOW_SUSPEND_STATE_SUSPENDED: + return TRUE; + } + + g_assert_not_reached (); +} + static void meta_window_show (MetaWindow *window) { @@ -2302,6 +2408,8 @@ meta_window_show (MetaWindow *window) window); } + update_suspend_state (window); + if (did_show) g_signal_emit (window, window_signals[SHOWN], 0); } @@ -2391,6 +2499,8 @@ meta_window_hide (MetaWindow *window) if (did_hide) meta_display_queue_check_fullscreen (window->display); + + update_suspend_state (window); } static gboolean diff --git a/src/meson.build b/src/meson.build index e03b5d4bf..f7af74aff 100644 --- a/src/meson.build +++ b/src/meson.build @@ -881,7 +881,9 @@ if have_wayland_eglstream ] endif -mutter_private_enum_sources = [] +mutter_private_enum_sources = [ + 'core/window-private.h', +] if have_remote_desktop mutter_private_enum_sources += [