/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /** * SECTION:meta-window-actor * @title: MetaWindowActor * @short_description: An actor representing a top-level window in the scene * graph * * #MetaWindowActor is a #ClutterActor that adds a notion of a window to the * Clutter scene graph. It contains a #MetaWindow which provides the windowing * API, and the #MetaCompositor that handles it. For the actual content of the * window, it contains a #MetaSurfaceActor. * * #MetaWindowActor takes care of the rendering features you need for your * window. For example, it will take the windows' requested opacity and use * that for clutter_actor_set_opacity(). Furthermore, it will also draw a * shadow around the window (using #MetaShadow) and deal with synchronization * between events of the window and the actual render loop. See * MetaWindowActor::first-frame for an example of the latter. */ #include "config.h" #include #include #include #include "backends/meta-screen-cast-window.h" #include "core/frame.h" #include "compositor/compositor-private.h" #include "compositor/meta-cullable.h" #include "compositor/meta-surface-actor-x11.h" #include "compositor/meta-surface-actor.h" #include "compositor/meta-window-actor-private.h" #include "compositor/region-utils.h" #include "meta/meta-enum-types.h" #include "meta/meta-shadow-factory.h" #include "meta/window.h" #ifdef HAVE_WAYLAND #include "compositor/meta-surface-actor-wayland.h" #include "wayland/meta-wayland-surface.h" #endif typedef enum { INITIALLY_FROZEN, DRAWING_FIRST_FRAME, EMITTED_FIRST_FRAME } FirstFrameState; typedef struct _MetaWindowActorPrivate { MetaWindow *window; MetaCompositor *compositor; MetaSurfaceActor *surface; /* MetaShadowFactory only caches shadows that are actually in use; * to avoid unnecessary recomputation we do two things: 1) we store * both a focused and unfocused shadow for the window. If the window * doesn't have different focused and unfocused shadow parameters, * these will be the same. 2) when the shadow potentially changes we * don't immediately unreference the old shadow, we just flag it as * dirty and recompute it when we next need it (recompute_focused_shadow, * recompute_unfocused_shadow.) Because of our extraction of * size-invariant window shape, we'll often find that the new shadow * is the same as the old shadow. */ MetaShadow *focused_shadow; MetaShadow *unfocused_shadow; /* A region that matches the shape of the window, including frame bounds */ cairo_region_t *shape_region; /* The region we should clip to when painting the shadow */ cairo_region_t *shadow_clip; /* Extracted size-invariant shape used for shadows */ MetaWindowShape *shadow_shape; char * shadow_class; MetaShadowMode shadow_mode; int geometry_scale; guint size_changed_id; /* * These need to be counters rather than flags, since more plugins * can implement same effect; the practicality of stacking effects * might be dubious, but we have to at least handle it correctly. */ gint minimize_in_progress; gint unminimize_in_progress; gint size_change_in_progress; gint map_in_progress; gint destroy_in_progress; guint freeze_count; guint visible : 1; guint disposed : 1; guint needs_reshape : 1; guint recompute_focused_shadow : 1; guint recompute_unfocused_shadow : 1; guint needs_destroy : 1; guint updates_frozen : 1; guint first_frame_state : 2; /* FirstFrameState */ } MetaWindowActorPrivate; enum { FIRST_FRAME, EFFECTS_COMPLETED, DAMAGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_META_WINDOW = 1, PROP_SHADOW_MODE, PROP_SHADOW_CLASS }; static void meta_window_actor_dispose (GObject *object); static void meta_window_actor_constructed (GObject *object); static void meta_window_actor_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void meta_window_actor_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void meta_window_actor_real_assign_surface_actor (MetaWindowActor *self, MetaSurfaceActor *surface_actor); static void meta_window_actor_paint (ClutterActor *actor); static gboolean meta_window_actor_get_paint_volume (ClutterActor *actor, ClutterPaintVolume *volume); static gboolean meta_window_actor_has_shadow (MetaWindowActor *self); static void meta_window_actor_handle_updates (MetaWindowActor *self); static void check_needs_reshape (MetaWindowActor *self); static void cullable_iface_init (MetaCullableInterface *iface); static void screen_cast_window_iface_init (MetaScreenCastWindowInterface *iface); G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaWindowActor, meta_window_actor, CLUTTER_TYPE_ACTOR, G_ADD_PRIVATE (MetaWindowActor) G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init) G_IMPLEMENT_INTERFACE (META_TYPE_SCREEN_CAST_WINDOW, screen_cast_window_iface_init)); static void meta_window_actor_class_init (MetaWindowActorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); GParamSpec *pspec; object_class->dispose = meta_window_actor_dispose; object_class->set_property = meta_window_actor_set_property; object_class->get_property = meta_window_actor_get_property; object_class->constructed = meta_window_actor_constructed; actor_class->paint = meta_window_actor_paint; actor_class->get_paint_volume = meta_window_actor_get_paint_volume; klass->assign_surface_actor = meta_window_actor_real_assign_surface_actor; /** * MetaWindowActor::first-frame: * @actor: the #MetaWindowActor instance * * The ::first-frame signal will be emitted the first time a frame * of window contents has been drawn by the application and Mutter * has had the chance to drawn that frame to the screen. If the * window starts off initially hidden, obscured, or on on a * different workspace, the ::first-frame signal will be emitted * even though the user doesn't see the contents. * * MetaDisplay::window-created is a good place to connect to this * signal - at that point, the MetaWindowActor for the window * exists, but the window has reliably not yet been drawn. * Connecting to an existing window that has already been drawn to * the screen is not useful. */ signals[FIRST_FRAME] = g_signal_new ("first-frame", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * MetaWindowActor::effects-completed: * @actor: the #MetaWindowActor instance * * The ::effects-completed signal will be emitted once all pending compositor * effects are completed. */ signals[EFFECTS_COMPLETED] = g_signal_new ("effects-completed", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * MetaWindowActor::damaged: * @actor: the #MetaWindowActor instance * * Notify that one or more of the surfaces of the window have been damaged. */ signals[DAMAGED] = g_signal_new ("damaged", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); pspec = g_param_spec_object ("meta-window", "MetaWindow", "The displayed MetaWindow", META_TYPE_WINDOW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_META_WINDOW, pspec); pspec = g_param_spec_enum ("shadow-mode", "Shadow mode", "Decides when to paint shadows", META_TYPE_SHADOW_MODE, META_SHADOW_MODE_AUTO, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SHADOW_MODE, pspec); pspec = g_param_spec_string ("shadow-class", "Name of the shadow class for this window.", "NULL means to use the default shadow class for this window type", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SHADOW_CLASS, pspec); } static void meta_window_actor_init (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); priv->geometry_scale = 1; } static void window_appears_focused_notify (MetaWindow *mw, GParamSpec *arg1, gpointer data) { clutter_actor_queue_redraw (CLUTTER_ACTOR (data)); } static void surface_size_changed (MetaSurfaceActor *actor, gpointer user_data) { MetaWindowActor *self = META_WINDOW_ACTOR (user_data); meta_window_actor_update_shape (self); } static gboolean is_argb32 (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); /* assume we're argb until we get the window (because in practice we're drawing nothing, so we're fully transparent) */ if (priv->surface) return meta_surface_actor_is_argb32 (priv->surface); else return TRUE; } static gboolean is_opaque (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; return !is_argb32 (self) && (window->opacity == 0xFF); } static gboolean is_frozen (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); return priv->surface == NULL || priv->freeze_count > 0; } static void meta_window_actor_freeze (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->freeze_count == 0 && priv->surface) meta_surface_actor_set_frozen (priv->surface, TRUE); priv->freeze_count ++; } static void meta_window_actor_sync_thawed_state (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->first_frame_state == INITIALLY_FROZEN) priv->first_frame_state = DRAWING_FIRST_FRAME; if (priv->surface) meta_surface_actor_set_frozen (priv->surface, FALSE); /* We sometimes ignore moves and resizes on frozen windows */ meta_window_actor_sync_actor_geometry (self, FALSE); } static void meta_window_actor_thaw (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->freeze_count <= 0) g_error ("Error in freeze/thaw accounting"); priv->freeze_count--; if (priv->freeze_count > 0) return; /* We still might be frozen due to lack of a MetaSurfaceActor */ if (is_frozen (self)) return; meta_window_actor_sync_thawed_state (self); /* We do this now since we might be going right back into the * frozen state */ meta_window_actor_handle_updates (self); } static void meta_window_actor_real_assign_surface_actor (MetaWindowActor *self, MetaSurfaceActor *surface_actor) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); g_assert (!priv->surface); priv->surface = g_object_ref_sink (surface_actor); priv->size_changed_id = g_signal_connect (priv->surface, "size-changed", G_CALLBACK (surface_size_changed), self); meta_window_actor_update_shape (self); if (is_frozen (self)) meta_surface_actor_set_frozen (priv->surface, TRUE); else meta_window_actor_sync_thawed_state (self); } void meta_window_actor_assign_surface_actor (MetaWindowActor *self, MetaSurfaceActor *surface_actor) { META_WINDOW_ACTOR_GET_CLASS (self)->assign_surface_actor (self, surface_actor); } static void init_surface_actor (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; MetaSurfaceActor *surface_actor; if (!meta_is_wayland_compositor ()) surface_actor = meta_surface_actor_x11_new (window); #ifdef HAVE_WAYLAND else if (window->surface) surface_actor = meta_wayland_surface_get_actor (window->surface); #endif else surface_actor = NULL; if (surface_actor) meta_window_actor_assign_surface_actor (self, surface_actor); } static void meta_window_actor_constructed (GObject *object) { MetaWindowActor *self = META_WINDOW_ACTOR (object); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; priv->compositor = window->display->compositor; /* Hang our compositor window state off the MetaWindow for fast retrieval */ meta_window_set_compositor_private (window, object); init_surface_actor (self); meta_window_actor_update_opacity (self); /* Start off with an empty shape region to maintain the invariant * that it's always set */ priv->shape_region = cairo_region_create (); meta_window_actor_sync_updates_frozen (self); if (is_frozen (self)) priv->first_frame_state = INITIALLY_FROZEN; else priv->first_frame_state = DRAWING_FIRST_FRAME; meta_window_actor_sync_actor_geometry (self, priv->window->placed); } static void meta_window_actor_dispose (GObject *object) { MetaWindowActor *self = META_WINDOW_ACTOR (object); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaCompositor *compositor = priv->compositor; if (priv->disposed) { G_OBJECT_CLASS (meta_window_actor_parent_class)->dispose (object); return; } priv->disposed = TRUE; g_clear_pointer (&priv->shape_region, cairo_region_destroy); g_clear_pointer (&priv->shadow_clip, cairo_region_destroy); g_clear_pointer (&priv->shadow_class, g_free); g_clear_pointer (&priv->focused_shadow, meta_shadow_unref); g_clear_pointer (&priv->unfocused_shadow, meta_shadow_unref); g_clear_pointer (&priv->shadow_shape, meta_window_shape_unref); meta_compositor_remove_window_actor (compositor, self); g_clear_object (&priv->window); if (priv->surface) { g_signal_handler_disconnect (priv->surface, priv->size_changed_id); clutter_actor_remove_child (CLUTTER_ACTOR (self), CLUTTER_ACTOR (priv->surface)); g_clear_object (&priv->surface); } G_OBJECT_CLASS (meta_window_actor_parent_class)->dispose (object); } static void meta_window_actor_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaWindowActor *self = META_WINDOW_ACTOR (object); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); switch (prop_id) { case PROP_META_WINDOW: priv->window = g_value_dup_object (value); g_signal_connect_object (priv->window, "notify::appears-focused", G_CALLBACK (window_appears_focused_notify), self, 0); break; case PROP_SHADOW_MODE: { MetaShadowMode newv = g_value_get_enum (value); if (newv == priv->shadow_mode) return; priv->shadow_mode = newv; meta_window_actor_invalidate_shadow (self); } break; case PROP_SHADOW_CLASS: { const char *newv = g_value_get_string (value); if (g_strcmp0 (newv, priv->shadow_class) == 0) return; g_free (priv->shadow_class); priv->shadow_class = g_strdup (newv); meta_window_actor_invalidate_shadow (self); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_window_actor_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaWindowActor *self = META_WINDOW_ACTOR (object); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); switch (prop_id) { case PROP_META_WINDOW: g_value_set_object (value, priv->window); break; case PROP_SHADOW_MODE: g_value_set_enum (value, priv->shadow_mode); break; case PROP_SHADOW_CLASS: g_value_set_string (value, priv->shadow_class); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static const char * meta_window_actor_get_shadow_class (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->shadow_class != NULL) return priv->shadow_class; else { MetaWindowType window_type = meta_window_get_window_type (priv->window); switch (window_type) { case META_WINDOW_DROPDOWN_MENU: case META_WINDOW_COMBO: return "dropdown-menu"; case META_WINDOW_POPUP_MENU: return "popup-menu"; default: { MetaFrameType frame_type = meta_window_get_frame_type (priv->window); return meta_frame_type_to_string (frame_type); } } } } static void meta_window_actor_get_shadow_params (MetaWindowActor *self, gboolean appears_focused, MetaShadowParams *params) { const char *shadow_class = meta_window_actor_get_shadow_class (self); meta_shadow_factory_get_params (meta_shadow_factory_get_default (), shadow_class, appears_focused, params); } void meta_window_actor_get_shape_bounds (MetaWindowActor *self, cairo_rectangle_int_t *bounds) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); cairo_region_get_extents (priv->shape_region, bounds); } static void meta_window_actor_get_shadow_bounds (MetaWindowActor *self, gboolean appears_focused, cairo_rectangle_int_t *bounds) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaShadow *shadow; cairo_rectangle_int_t shape_bounds; MetaShadowParams params; shadow = appears_focused ? priv->focused_shadow : priv->unfocused_shadow; meta_window_actor_get_shape_bounds (self, &shape_bounds); meta_window_actor_get_shadow_params (self, appears_focused, ¶ms); meta_shadow_get_bounds (shadow, params.x_offset + shape_bounds.x, params.y_offset + shape_bounds.y, shape_bounds.width, shape_bounds.height, bounds); } /* If we have an ARGB32 window that we decorate with a frame, it's * probably something like a translucent terminal - something where * the alpha channel represents transparency rather than a shape. We * don't want to show the shadow through the translucent areas since * the shadow is wrong for translucent windows (it should be * translucent itself and colored), and not only that, will /look/ * horribly wrong - a misplaced big black blob. As a hack, what we * want to do is just draw the shadow as normal outside the frame, and * inside the frame draw no shadow. This is also not even close to * the right result, but looks OK. We also apply this approach to * windows set to be partially translucent with _NET_WM_WINDOW_OPACITY. */ static gboolean clip_shadow_under_window (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->window->frame) return TRUE; return is_opaque (self); } static void meta_window_actor_paint (ClutterActor *actor) { MetaWindowActor *self = META_WINDOW_ACTOR (actor); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); gboolean appears_focused = meta_window_appears_focused (priv->window); MetaShadow *shadow; CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer (); shadow = appears_focused ? priv->focused_shadow : priv->unfocused_shadow; if (shadow != NULL) { MetaShadowParams params; cairo_rectangle_int_t shape_bounds; cairo_region_t *clip = priv->shadow_clip; MetaWindow *window = priv->window; meta_window_actor_get_shape_bounds (self, &shape_bounds); meta_window_actor_get_shadow_params (self, appears_focused, ¶ms); /* The frame bounds are already subtracted from priv->shadow_clip * if that exists. */ if (!clip && clip_shadow_under_window (self)) { cairo_region_t *frame_bounds = meta_window_get_frame_bounds (priv->window); cairo_rectangle_int_t bounds; meta_window_actor_get_shadow_bounds (self, appears_focused, &bounds); clip = cairo_region_create_rectangle (&bounds); if (frame_bounds) cairo_region_subtract (clip, frame_bounds); } meta_shadow_paint (shadow, framebuffer, params.x_offset + shape_bounds.x, params.y_offset + shape_bounds.y, shape_bounds.width, shape_bounds.height, (clutter_actor_get_paint_opacity (actor) * params.opacity * window->opacity) / (255 * 255), clip, clip_shadow_under_window (self)); /* clip_strictly - not just as an optimization */ if (clip && clip != priv->shadow_clip) cairo_region_destroy (clip); } CLUTTER_ACTOR_CLASS (meta_window_actor_parent_class)->paint (actor); } static gboolean meta_window_actor_get_paint_volume (ClutterActor *actor, ClutterPaintVolume *volume) { MetaWindowActor *self = META_WINDOW_ACTOR (actor); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); gboolean appears_focused = meta_window_appears_focused (priv->window); /* The paint volume is computed before paint functions are called * so our bounds might not be updated yet. Force an update. */ meta_window_actor_handle_updates (self); if (appears_focused ? priv->focused_shadow : priv->unfocused_shadow) { cairo_rectangle_int_t shadow_bounds; ClutterActorBox shadow_box; /* We could compute an full clip region as we do for the window * texture, but the shadow is relatively cheap to draw, and * a little more complex to clip, so we just catch the case where * the shadow is completely obscured and doesn't need to be drawn * at all. */ meta_window_actor_get_shadow_bounds (self, appears_focused, &shadow_bounds); shadow_box.x1 = shadow_bounds.x; shadow_box.x2 = shadow_bounds.x + shadow_bounds.width; shadow_box.y1 = shadow_bounds.y; shadow_box.y2 = shadow_bounds.y + shadow_bounds.height; clutter_paint_volume_union_box (volume, &shadow_box); } if (priv->surface) { const ClutterPaintVolume *child_volume; child_volume = clutter_actor_get_transformed_paint_volume (CLUTTER_ACTOR (priv->surface), actor); if (!child_volume) return FALSE; clutter_paint_volume_union (volume, child_volume); } return TRUE; } static gboolean meta_window_actor_has_shadow (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->shadow_mode == META_SHADOW_MODE_FORCED_OFF) return FALSE; if (priv->shadow_mode == META_SHADOW_MODE_FORCED_ON) return TRUE; /* Leaving out shadows for maximized and fullscreen windows is an effeciency * win and also prevents the unsightly effect of the shadow of maximized * window appearing on an adjacent window */ if ((meta_window_get_maximized (priv->window) == META_MAXIMIZE_BOTH) || meta_window_is_fullscreen (priv->window)) return FALSE; /* * If we have two snap-tiled windows, we don't want the shadow to obstruct * the other window. */ if (meta_window_get_tile_match (priv->window)) return FALSE; /* * Always put a shadow around windows with a frame - This should override * the restriction about not putting a shadow around ARGB windows. */ if (meta_window_get_frame (priv->window)) return TRUE; /* * Do not add shadows to non-opaque (ARGB32) windows, as we can't easily * generate shadows for them. */ if (!is_opaque (self)) return FALSE; /* * If a window specifies that it has custom frame extents, that likely * means that it is drawing a shadow itself. Don't draw our own. */ if (priv->window->has_custom_frame_extents) return FALSE; /* * Generate shadows for all other windows. */ return TRUE; } /** * meta_window_actor_get_meta_window: * @self: a #MetaWindowActor * * Gets the #MetaWindow object that the the #MetaWindowActor is displaying * * Return value: (transfer none): the displayed #MetaWindow */ MetaWindow * meta_window_actor_get_meta_window (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); return priv->window; } /** * meta_window_actor_get_texture: * @self: a #MetaWindowActor * * Gets the ClutterActor that is used to display the contents of the window, * or NULL if no texture is shown yet, because the window is not mapped. * * Return value: (transfer none): the #ClutterActor for the contents */ MetaShapedTexture * meta_window_actor_get_texture (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->surface) return meta_surface_actor_get_texture (priv->surface); else return NULL; } /** * meta_window_actor_get_surface: * @self: a #MetaWindowActor * * Gets the MetaSurfaceActor that draws the content of this window, * or NULL if there is no surface yet associated with this window. * * Return value: (transfer none): the #MetaSurfaceActor for the contents */ MetaSurfaceActor * meta_window_actor_get_surface (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); return priv->surface; } /** * meta_window_actor_is_destroyed: * @self: a #MetaWindowActor * * Gets whether the X window that the actor was displaying has been destroyed * * Return value: %TRUE when the window is destroyed, otherwise %FALSE */ gboolean meta_window_actor_is_destroyed (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); return priv->disposed || priv->needs_destroy; } void meta_window_actor_queue_frame_drawn (MetaWindowActor *self, gboolean no_delay_frame) { META_WINDOW_ACTOR_GET_CLASS (self)->queue_frame_drawn (self, no_delay_frame); } gboolean meta_window_actor_effect_in_progress (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); return (priv->minimize_in_progress || priv->size_change_in_progress || priv->map_in_progress || priv->destroy_in_progress); } static gboolean is_freeze_thaw_effect (MetaPluginEffect event) { switch (event) { case META_PLUGIN_DESTROY: case META_PLUGIN_SIZE_CHANGE: return TRUE; break; default: return FALSE; } } static gboolean start_simple_effect (MetaWindowActor *self, MetaPluginEffect event) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaCompositor *compositor = priv->compositor; MetaPluginManager *plugin_mgr = meta_compositor_get_plugin_manager (compositor); gint *counter = NULL; gboolean use_freeze_thaw = FALSE; g_assert (plugin_mgr != NULL); switch (event) { case META_PLUGIN_NONE: return FALSE; case META_PLUGIN_MINIMIZE: counter = &priv->minimize_in_progress; break; case META_PLUGIN_UNMINIMIZE: counter = &priv->unminimize_in_progress; break; case META_PLUGIN_MAP: counter = &priv->map_in_progress; break; case META_PLUGIN_DESTROY: counter = &priv->destroy_in_progress; break; case META_PLUGIN_SIZE_CHANGE: case META_PLUGIN_SWITCH_WORKSPACE: g_assert_not_reached (); break; } g_assert (counter); use_freeze_thaw = is_freeze_thaw_effect (event); if (use_freeze_thaw) meta_window_actor_freeze (self); (*counter)++; if (!meta_plugin_manager_event_simple (plugin_mgr, self, event)) { (*counter)--; if (use_freeze_thaw) meta_window_actor_thaw (self); return FALSE; } return TRUE; } static void meta_window_actor_after_effects (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->needs_destroy) { clutter_actor_destroy (CLUTTER_ACTOR (self)); return; } g_signal_emit (self, signals[EFFECTS_COMPLETED], 0); meta_window_actor_sync_visibility (self); meta_window_actor_sync_actor_geometry (self, FALSE); } void meta_window_actor_effect_completed (MetaWindowActor *self, MetaPluginEffect event) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); gboolean inconsistent = FALSE; /* NB: Keep in mind that when effects get completed it possible * that the corresponding MetaWindow may have be been destroyed. * In this case priv->window will == NULL */ switch (event) { case META_PLUGIN_NONE: break; case META_PLUGIN_MINIMIZE: { priv->minimize_in_progress--; if (priv->minimize_in_progress < 0) { g_warning ("Error in minimize accounting."); priv->minimize_in_progress = 0; inconsistent = TRUE; } } break; case META_PLUGIN_UNMINIMIZE: { priv->unminimize_in_progress--; if (priv->unminimize_in_progress < 0) { g_warning ("Error in unminimize accounting."); priv->unminimize_in_progress = 0; inconsistent = TRUE; } } break; case META_PLUGIN_MAP: /* * Make sure that the actor is at the correct place in case * the plugin fscked. */ priv->map_in_progress--; if (priv->map_in_progress < 0) { g_warning ("Error in map accounting."); priv->map_in_progress = 0; inconsistent = TRUE; } break; case META_PLUGIN_DESTROY: priv->destroy_in_progress--; if (priv->destroy_in_progress < 0) { g_warning ("Error in destroy accounting."); priv->destroy_in_progress = 0; inconsistent = TRUE; } break; case META_PLUGIN_SIZE_CHANGE: priv->size_change_in_progress--; if (priv->size_change_in_progress < 0) { g_warning ("Error in size change accounting."); priv->size_change_in_progress = 0; inconsistent = TRUE; } break; case META_PLUGIN_SWITCH_WORKSPACE: g_assert_not_reached (); break; } if (is_freeze_thaw_effect (event) && !inconsistent) meta_window_actor_thaw (self); if (!meta_window_actor_effect_in_progress (self)) meta_window_actor_after_effects (self); } gboolean meta_window_actor_should_unredirect (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (!meta_window_actor_is_destroyed (self) && priv->surface) return meta_surface_actor_should_unredirect (priv->surface); else return FALSE; } void meta_window_actor_set_unredirected (MetaWindowActor *self, gboolean unredirected) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); g_assert (priv->surface); /* because otherwise should_unredirect() is FALSE */ meta_surface_actor_set_unredirected (priv->surface, unredirected); } void meta_window_actor_queue_destroy (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; MetaWindowType window_type = meta_window_get_window_type (window); meta_window_set_compositor_private (window, NULL); META_WINDOW_ACTOR_GET_CLASS (self)->queue_destroy (self); if (window_type == META_WINDOW_DROPDOWN_MENU || window_type == META_WINDOW_POPUP_MENU || window_type == META_WINDOW_TOOLTIP || window_type == META_WINDOW_NOTIFICATION || window_type == META_WINDOW_COMBO || window_type == META_WINDOW_DND || window_type == META_WINDOW_OVERRIDE_OTHER) { /* * No effects, just kill it. */ clutter_actor_destroy (CLUTTER_ACTOR (self)); return; } priv->needs_destroy = TRUE; if (!meta_window_actor_effect_in_progress (self)) clutter_actor_destroy (CLUTTER_ACTOR (self)); } MetaWindowActorChanges meta_window_actor_sync_actor_geometry (MetaWindowActor *self, gboolean did_placement) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaRectangle window_rect; ClutterActor *actor = CLUTTER_ACTOR (self); MetaWindowActorChanges changes = 0; meta_window_get_buffer_rect (priv->window, &window_rect); /* When running as a Wayland compositor we catch size changes when new * buffers are attached */ if (META_IS_SURFACE_ACTOR_X11 (priv->surface)) meta_surface_actor_x11_set_size (META_SURFACE_ACTOR_X11 (priv->surface), window_rect.width, window_rect.height); /* Normally we want freezing a window to also freeze its position; this allows * windows to atomically move and resize together, either under app control, * or because the user is resizing from the left/top. But on initial placement * we need to assign a position, since immediately after the window * is shown, the map effect will go into effect and prevent further geometry * updates. */ if (is_frozen (self) && !did_placement) return META_WINDOW_ACTOR_CHANGE_POSITION | META_WINDOW_ACTOR_CHANGE_SIZE; if (meta_window_actor_effect_in_progress (self)) return META_WINDOW_ACTOR_CHANGE_POSITION | META_WINDOW_ACTOR_CHANGE_SIZE; if (clutter_actor_has_allocation (actor)) { ClutterActorBox box; float old_x, old_y; float old_width, old_height; clutter_actor_get_allocation_box (actor, &box); old_x = box.x1; old_y = box.y1; old_width = box.x2 - box.x1; old_height = box.y2 - box.y1; if (old_x != window_rect.x || old_y != window_rect.y) changes |= META_WINDOW_ACTOR_CHANGE_POSITION; if (old_width != window_rect.width || old_height != window_rect.height) changes |= META_WINDOW_ACTOR_CHANGE_SIZE; } else { changes = META_WINDOW_ACTOR_CHANGE_POSITION | META_WINDOW_ACTOR_CHANGE_SIZE; } if (changes & META_WINDOW_ACTOR_CHANGE_POSITION) clutter_actor_set_position (actor, window_rect.x, window_rect.y); if (changes & META_WINDOW_ACTOR_CHANGE_SIZE) clutter_actor_set_size (actor, window_rect.width, window_rect.height); return changes; } void meta_window_actor_show (MetaWindowActor *self, MetaCompEffect effect) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaCompositor *compositor = priv->compositor; MetaPluginEffect event; g_return_if_fail (!priv->visible); priv->visible = TRUE; switch (effect) { case META_COMP_EFFECT_CREATE: event = META_PLUGIN_MAP; break; case META_COMP_EFFECT_UNMINIMIZE: event = META_PLUGIN_UNMINIMIZE; break; case META_COMP_EFFECT_NONE: event = META_PLUGIN_NONE; break; default: g_assert_not_reached(); } if (meta_compositor_is_switching_workspace (compositor) || !start_simple_effect (self, event)) { clutter_actor_show (CLUTTER_ACTOR (self)); } } void meta_window_actor_hide (MetaWindowActor *self, MetaCompEffect effect) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaCompositor *compositor = priv->compositor; MetaPluginEffect event; g_return_if_fail (priv->visible); priv->visible = FALSE; /* If a plugin is animating a workspace transition, we have to * hold off on hiding the window, and do it after the workspace * switch completes */ if (meta_compositor_is_switching_workspace (compositor)) return; switch (effect) { case META_COMP_EFFECT_DESTROY: event = META_PLUGIN_DESTROY; break; case META_COMP_EFFECT_MINIMIZE: event = META_PLUGIN_MINIMIZE; break; case META_COMP_EFFECT_NONE: event = META_PLUGIN_NONE; break; default: g_assert_not_reached(); } if (!start_simple_effect (self, event)) clutter_actor_hide (CLUTTER_ACTOR (self)); } void meta_window_actor_size_change (MetaWindowActor *self, MetaSizeChange which_change, MetaRectangle *old_frame_rect, MetaRectangle *old_buffer_rect) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaCompositor *compositor = priv->compositor; MetaPluginManager *plugin_mgr = meta_compositor_get_plugin_manager (compositor); priv->size_change_in_progress++; meta_window_actor_freeze (self); if (!meta_plugin_manager_event_size_change (plugin_mgr, self, which_change, old_frame_rect, old_buffer_rect)) { priv->size_change_in_progress--; meta_window_actor_thaw (self); } } #if 0 /* Print out a region; useful for debugging */ static void print_region (cairo_region_t *region) { int n_rects; int i; n_rects = cairo_region_num_rectangles (region); g_print ("["); for (i = 0; i < n_rects; i++) { cairo_rectangle_int_t rect; cairo_region_get_rectangle (region, i, &rect); g_print ("+%d+%dx%dx%d ", rect.x, rect.y, rect.width, rect.height); } g_print ("]\n"); } #endif #if 0 /* Dump a region to a PNG file; useful for debugging */ static void see_region (cairo_region_t *region, int width, int height, char *filename) { cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height); cairo_t *cr = cairo_create (surface); gdk_cairo_region (cr, region); cairo_fill (cr); cairo_surface_write_to_png (surface, filename); cairo_destroy (cr); cairo_surface_destroy (surface); } #endif /** * meta_window_actor_set_clip_region_beneath: * @self: a #MetaWindowActor * @clip_region: the region of the screen that isn't completely * obscured beneath the main window texture. * * Provides a hint as to what areas need to be drawn *beneath* * the main window texture. This is the relevant clip region * when drawing the shadow, properly accounting for areas of the * shadow hid by the window itself. This will be set before painting * then unset afterwards. */ static void meta_window_actor_set_clip_region_beneath (MetaWindowActor *self, cairo_region_t *beneath_region) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); gboolean appears_focused = meta_window_appears_focused (priv->window); if (appears_focused ? priv->focused_shadow : priv->unfocused_shadow) { g_clear_pointer (&priv->shadow_clip, cairo_region_destroy); if (beneath_region) { priv->shadow_clip = cairo_region_copy (beneath_region); if (clip_shadow_under_window (self)) { cairo_region_t *frame_bounds = meta_window_get_frame_bounds (priv->window); if (frame_bounds) cairo_region_subtract (priv->shadow_clip, frame_bounds); } } else priv->shadow_clip = NULL; } } static void meta_window_actor_cull_out (MetaCullable *cullable, cairo_region_t *unobscured_region, cairo_region_t *clip_region) { MetaWindowActor *self = META_WINDOW_ACTOR (cullable); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); meta_cullable_cull_out_children (cullable, unobscured_region, clip_region); meta_window_actor_set_clip_region_beneath (self, clip_region); if (unobscured_region && is_opaque (self)) { cairo_region_t *region = meta_window_get_frame_bounds (priv->window); if (region) { cairo_region_subtract (unobscured_region, region); } else { cairo_rectangle_int_t rect; meta_window_get_frame_rect (priv->window, &rect); rect.x = rect.y = 0; cairo_region_subtract_rectangle (unobscured_region, &rect); } } } static void meta_window_actor_reset_culling (MetaCullable *cullable) { MetaWindowActor *self = META_WINDOW_ACTOR (cullable); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); g_clear_pointer (&priv->shadow_clip, cairo_region_destroy); meta_cullable_reset_culling_children (cullable); } static void cullable_iface_init (MetaCullableInterface *iface) { iface->cull_out = meta_window_actor_cull_out; iface->reset_culling = meta_window_actor_reset_culling; } static void check_needs_shadow (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaShadow *old_shadow = NULL; MetaShadow **shadow_location; gboolean recompute_shadow; gboolean should_have_shadow; gboolean appears_focused; /* Calling meta_window_actor_has_shadow() here at every pre-paint is cheap * and avoids the need to explicitly handle window type changes, which * we would do if tried to keep track of when we might be adding or removing * a shadow more explicitly. We only keep track of changes to the *shape* of * the shadow with priv->recompute_shadow. */ should_have_shadow = meta_window_actor_has_shadow (self); appears_focused = meta_window_appears_focused (priv->window); if (appears_focused) { recompute_shadow = priv->recompute_focused_shadow; priv->recompute_focused_shadow = FALSE; shadow_location = &priv->focused_shadow; } else { recompute_shadow = priv->recompute_unfocused_shadow; priv->recompute_unfocused_shadow = FALSE; shadow_location = &priv->unfocused_shadow; } if (!should_have_shadow || recompute_shadow) { if (*shadow_location != NULL) { old_shadow = *shadow_location; *shadow_location = NULL; } } if (*shadow_location == NULL && should_have_shadow) { if (priv->shadow_shape == NULL) priv->shadow_shape = meta_window_shape_new (priv->shape_region); MetaShadowFactory *factory = meta_shadow_factory_get_default (); const char *shadow_class = meta_window_actor_get_shadow_class (self); cairo_rectangle_int_t shape_bounds; meta_window_actor_get_shape_bounds (self, &shape_bounds); *shadow_location = meta_shadow_factory_get_shadow (factory, priv->shadow_shape, shape_bounds.width, shape_bounds.height, shadow_class, appears_focused); } if (old_shadow != NULL) meta_shadow_unref (old_shadow); } void meta_window_actor_process_x11_damage (MetaWindowActor *self, XDamageNotifyEvent *event) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (priv->surface) meta_surface_actor_process_damage (priv->surface, event->area.x, event->area.y, event->area.width, event->area.height); meta_window_actor_notify_damaged (self); } void meta_window_actor_sync_visibility (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (CLUTTER_ACTOR_IS_VISIBLE (self) != priv->visible) { if (priv->visible) clutter_actor_show (CLUTTER_ACTOR (self)); else clutter_actor_hide (CLUTTER_ACTOR (self)); } } static cairo_region_t * scan_visible_region (guchar *mask_data, int stride, cairo_region_t *scan_area) { int i, n_rects = cairo_region_num_rectangles (scan_area); MetaRegionBuilder builder; meta_region_builder_init (&builder); for (i = 0; i < n_rects; i++) { int x, y; cairo_rectangle_int_t rect; cairo_region_get_rectangle (scan_area, i, &rect); for (y = rect.y; y < (rect.y + rect.height); y++) { for (x = rect.x; x < (rect.x + rect.width); x++) { int x2 = x; while (mask_data[y * stride + x2] == 255 && x2 < (rect.x + rect.width)) x2++; if (x2 > x) { meta_region_builder_add_rectangle (&builder, x, y, x2 - x, 1); x = x2; } } } } return meta_region_builder_finish (&builder); } static void build_and_scan_frame_mask (MetaWindowActor *self, cairo_rectangle_int_t *client_area, cairo_region_t *shape_region) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); ClutterBackend *backend = clutter_get_default_backend (); CoglContext *ctx = clutter_backend_get_cogl_context (backend); guchar *mask_data; guint tex_width, tex_height; MetaShapedTexture *stex; CoglTexture *paint_tex, *mask_texture; int stride; cairo_t *cr; cairo_surface_t *surface; GError *error = NULL; stex = meta_surface_actor_get_texture (priv->surface); g_return_if_fail (stex); meta_shaped_texture_set_mask_texture (stex, NULL); paint_tex = meta_shaped_texture_get_texture (stex); if (paint_tex == NULL) return; tex_width = cogl_texture_get_width (paint_tex); tex_height = cogl_texture_get_height (paint_tex); stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8, tex_width); /* Create data for an empty image */ mask_data = g_malloc0 (stride * tex_height); surface = cairo_image_surface_create_for_data (mask_data, CAIRO_FORMAT_A8, tex_width, tex_height, stride); cr = cairo_create (surface); gdk_cairo_region (cr, shape_region); cairo_fill (cr); if (priv->window->frame != NULL) { cairo_region_t *frame_paint_region, *scanned_region; cairo_rectangle_int_t rect = { 0, 0, tex_width, tex_height }; /* Make sure we don't paint the frame over the client window. */ frame_paint_region = cairo_region_create_rectangle (&rect); cairo_region_subtract_rectangle (frame_paint_region, client_area); gdk_cairo_region (cr, frame_paint_region); cairo_clip (cr); meta_frame_get_mask (priv->window->frame, cr); cairo_surface_flush (surface); scanned_region = scan_visible_region (mask_data, stride, frame_paint_region); cairo_region_union (shape_region, scanned_region); cairo_region_destroy (scanned_region); cairo_region_destroy (frame_paint_region); } cairo_destroy (cr); cairo_surface_destroy (surface); mask_texture = COGL_TEXTURE (cogl_texture_2d_new_from_data (ctx, tex_width, tex_height, COGL_PIXEL_FORMAT_A_8, stride, mask_data, &error)); if (error) { g_warning ("Failed to allocate mask texture: %s", error->message); g_error_free (error); } meta_shaped_texture_set_mask_texture (stex, mask_texture); if (mask_texture) cogl_object_unref (mask_texture); g_free (mask_data); } static void meta_window_actor_update_shape_region (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); cairo_region_t *region = NULL; cairo_rectangle_int_t client_area; meta_window_get_client_area_rect (priv->window, &client_area); if (priv->window->frame != NULL && priv->window->shape_region != NULL) { region = cairo_region_copy (priv->window->shape_region); cairo_region_translate (region, client_area.x, client_area.y); } else if (priv->window->shape_region != NULL) { region = cairo_region_reference (priv->window->shape_region); } else { /* If we don't have a shape on the server, that means that * we have an implicit shape of one rectangle covering the * entire window. */ region = cairo_region_create_rectangle (&client_area); } if ((priv->window->shape_region != NULL) || (priv->window->frame != NULL)) build_and_scan_frame_mask (self, &client_area, region); g_clear_pointer (&priv->shape_region, cairo_region_destroy); priv->shape_region = region; g_clear_pointer (&priv->shadow_shape, meta_window_shape_unref); meta_window_actor_invalidate_shadow (self); } static void meta_window_actor_update_input_region (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; cairo_region_t *region; if (window->shape_region && window->input_region) { region = cairo_region_copy (window->shape_region); cairo_region_intersect (region, window->input_region); } else if (window->shape_region) region = cairo_region_reference (window->shape_region); else if (window->input_region) region = cairo_region_reference (window->input_region); else region = NULL; meta_surface_actor_set_input_region (priv->surface, region); cairo_region_destroy (region); } static void meta_window_actor_update_opaque_region (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); cairo_region_t *opaque_region; gboolean argb32 = is_argb32 (self); if (argb32 && priv->window->opaque_region != NULL) { cairo_rectangle_int_t client_area; meta_window_get_client_area_rect (priv->window, &client_area); /* The opaque region is defined to be a part of the * window which ARGB32 will always paint with opaque * pixels. For these regions, we want to avoid painting * windows and shadows beneath them. * * If the client gives bad coordinates where it does not * fully paint, the behavior is defined by the specification * to be undefined, and considered a client bug. In mutter's * case, graphical glitches will occur. */ opaque_region = cairo_region_copy (priv->window->opaque_region); cairo_region_translate (opaque_region, client_area.x, client_area.y); cairo_region_intersect (opaque_region, priv->shape_region); } else if (argb32) opaque_region = NULL; else opaque_region = cairo_region_reference (priv->shape_region); meta_surface_actor_set_opaque_region (priv->surface, opaque_region); cairo_region_destroy (opaque_region); } static void check_needs_reshape (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (!priv->needs_reshape) return; meta_window_actor_update_shape_region (self); if (priv->window->client_type == META_WINDOW_CLIENT_TYPE_X11) { meta_window_actor_update_input_region (self); meta_window_actor_update_opaque_region (self); } priv->needs_reshape = FALSE; } void meta_window_actor_update_shape (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); priv->needs_reshape = TRUE; if (is_frozen (self)) return; clutter_actor_queue_redraw (CLUTTER_ACTOR (priv->surface)); } static void meta_window_actor_handle_updates (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); if (is_frozen (self)) { /* The window is frozen due to a pending animation: we'll wait until * the animation finishes to reshape and repair the window */ return; } if (meta_surface_actor_is_unredirected (priv->surface)) return; meta_surface_actor_pre_paint (priv->surface); if (!meta_surface_actor_is_visible (priv->surface)) return; check_needs_reshape (self); check_needs_shadow (self); } void meta_window_actor_pre_paint (MetaWindowActor *self) { if (meta_window_actor_is_destroyed (self)) return; meta_window_actor_handle_updates (self); META_WINDOW_ACTOR_GET_CLASS (self)->pre_paint (self); } void meta_window_actor_post_paint (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); META_WINDOW_ACTOR_GET_CLASS (self)->post_paint (self); if (meta_window_actor_is_destroyed (self)) return; if (priv->first_frame_state == DRAWING_FIRST_FRAME) { priv->first_frame_state = EMITTED_FIRST_FRAME; g_signal_emit (self, signals[FIRST_FRAME], 0); } } void meta_window_actor_frame_complete (MetaWindowActor *self, ClutterFrameInfo *frame_info, gint64 presentation_time) { META_WINDOW_ACTOR_GET_CLASS (self)->frame_complete (self, frame_info, presentation_time); } void meta_window_actor_invalidate_shadow (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); priv->recompute_focused_shadow = TRUE; priv->recompute_unfocused_shadow = TRUE; if (is_frozen (self)) return; clutter_actor_queue_redraw (CLUTTER_ACTOR (self)); } void meta_window_actor_update_opacity (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; if (priv->surface) clutter_actor_set_opacity (CLUTTER_ACTOR (priv->surface), window->opacity); } static void meta_window_actor_set_updates_frozen (MetaWindowActor *self, gboolean updates_frozen) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); updates_frozen = updates_frozen != FALSE; if (priv->updates_frozen != updates_frozen) { priv->updates_frozen = updates_frozen; if (updates_frozen) meta_window_actor_freeze (self); else meta_window_actor_thaw (self); } } void meta_window_actor_sync_updates_frozen (MetaWindowActor *self) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); MetaWindow *window = priv->window; meta_window_actor_set_updates_frozen (self, meta_window_updates_are_frozen (window)); } MetaWindowActor * meta_window_actor_from_window (MetaWindow *window) { return META_WINDOW_ACTOR (meta_window_get_compositor_private (window)); } void meta_window_actor_set_geometry_scale (MetaWindowActor *window_actor, int geometry_scale) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (window_actor); CoglMatrix child_transform; if (priv->geometry_scale == geometry_scale) return; priv->geometry_scale = geometry_scale; cogl_matrix_init_identity (&child_transform); cogl_matrix_scale (&child_transform, geometry_scale, geometry_scale, 1); clutter_actor_set_child_transform (CLUTTER_ACTOR (window_actor), &child_transform); } int meta_window_actor_get_geometry_scale (MetaWindowActor *window_actor) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (window_actor); return priv->geometry_scale; } static void meta_window_actor_get_frame_bounds (MetaScreenCastWindow *screen_cast_window, MetaRectangle *bounds) { MetaWindowActor *window_actor = META_WINDOW_ACTOR (screen_cast_window); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (window_actor); MetaWindow *window; MetaShapedTexture *stex; MetaRectangle buffer_rect; MetaRectangle frame_rect; int buffer_scale; stex = meta_surface_actor_get_texture (priv->surface); buffer_scale = meta_shaped_texture_get_buffer_scale (stex); window = priv->window; meta_window_get_buffer_rect (window, &buffer_rect); meta_window_get_frame_rect (window, &frame_rect); bounds->x = (int) floor ((frame_rect.x - buffer_rect.x) / (float) buffer_scale); bounds->y = (int) floor ((frame_rect.y - buffer_rect.y) / (float) buffer_scale); bounds->width = (int) ceil (frame_rect.width / (float) buffer_scale); bounds->height = (int) ceil (frame_rect.height / (float) buffer_scale); } static void meta_window_actor_transform_relative_position (MetaScreenCastWindow *screen_cast_window, double x, double y, double *x_out, double *y_out) { MetaWindowActor *window_actor = META_WINDOW_ACTOR (screen_cast_window); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (window_actor); MetaRectangle bounds; ClutterVertex v1 = { 0.f, }, v2 = { 0.f, }; meta_window_actor_get_frame_bounds (screen_cast_window, &bounds); v1.x = CLAMP ((float) x, bounds.x, bounds.x + bounds.width); v1.y = CLAMP ((float) y, bounds.y, bounds.y + bounds.height); clutter_actor_apply_transform_to_point (CLUTTER_ACTOR (priv->surface), &v1, &v2); *x_out = (double) v2.x; *y_out = (double) v2.y; } static gboolean meta_window_actor_transform_cursor_position (MetaScreenCastWindow *screen_cast_window, MetaCursorSprite *cursor_sprite, ClutterPoint *cursor_position, float *out_cursor_scale, ClutterPoint *out_relative_cursor_position) { MetaWindowActor *window_actor = META_WINDOW_ACTOR (screen_cast_window); MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (window_actor); MetaWindow *window; window = priv->window; if (!meta_window_has_pointer (window)) return FALSE; if (cursor_sprite && meta_cursor_sprite_get_cogl_texture (cursor_sprite) && out_cursor_scale) { MetaShapedTexture *stex; double texture_scale; float cursor_texture_scale; stex = meta_surface_actor_get_texture (priv->surface); texture_scale = meta_shaped_texture_get_buffer_scale (stex); cursor_texture_scale = meta_cursor_sprite_get_texture_scale (cursor_sprite); *out_cursor_scale = texture_scale / cursor_texture_scale; } if (out_relative_cursor_position) { clutter_actor_transform_stage_point (CLUTTER_ACTOR (priv->surface), cursor_position->x, cursor_position->y, &out_relative_cursor_position->x, &out_relative_cursor_position->y); } return TRUE; } static void meta_window_actor_capture_into (MetaScreenCastWindow *screen_cast_window, MetaRectangle *bounds, uint8_t *data) { MetaWindowActor *window_actor = META_WINDOW_ACTOR (screen_cast_window); cairo_surface_t *image; uint8_t *cr_data; int cr_stride; int cr_width; int cr_height; int bpp = 4; if (meta_window_actor_is_destroyed (window_actor)) return; image = meta_window_actor_get_image (window_actor, bounds); cr_data = cairo_image_surface_get_data (image); cr_width = cairo_image_surface_get_width (image); cr_height = cairo_image_surface_get_height (image); cr_stride = cairo_image_surface_get_stride (image); if (cr_width < bounds->width || cr_height < bounds->height) { uint8_t *src, *dst; src = cr_data; dst = data; for (int i = 0; i < cr_height; i++) { memcpy (dst, src, cr_stride); if (cr_width < bounds->width) memset (dst + cr_stride, 0, (bounds->width * bpp) - cr_stride); src += cr_stride; dst += bounds->width * bpp; } for (int i = cr_height; i < bounds->height; i++) { memset (dst, 0, bounds->width * bpp); dst += bounds->width * bpp; } } else { memcpy (data, cr_data, cr_height * cr_stride); } cairo_surface_destroy (image); } static gboolean meta_window_actor_has_damage (MetaScreenCastWindow *screen_cast_window) { return clutter_actor_has_damage (CLUTTER_ACTOR (screen_cast_window)); } static void screen_cast_window_iface_init (MetaScreenCastWindowInterface *iface) { iface->get_frame_bounds = meta_window_actor_get_frame_bounds; iface->transform_relative_position = meta_window_actor_transform_relative_position; iface->transform_cursor_position = meta_window_actor_transform_cursor_position; iface->capture_into = meta_window_actor_capture_into; iface->has_damage = meta_window_actor_has_damage; } MetaWindowActor * meta_window_actor_from_actor (ClutterActor *actor) { if (!META_IS_SURFACE_ACTOR (actor)) return NULL; do { actor = clutter_actor_get_parent (actor); if (META_IS_WINDOW_ACTOR (actor)) return META_WINDOW_ACTOR (actor); } while (actor != NULL); return NULL; } void meta_window_actor_notify_damaged (MetaWindowActor *window_actor) { g_signal_emit (window_actor, signals[DAMAGED], 0); } cairo_surface_t * meta_window_actor_get_image (MetaWindowActor *self, MetaRectangle *clip) { MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); ClutterActor *actor = CLUTTER_ACTOR (self); MetaBackend *backend = meta_get_backend (); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend); float resource_scale; float width, height; CoglTexture2D *texture; g_autoptr (GError) error = NULL; CoglOffscreen *offscreen; CoglFramebuffer *framebuffer; CoglColor clear_color; float x, y; MetaRectangle scaled_clip; cairo_surface_t *surface; if (!priv->surface) return NULL; if (clutter_actor_get_n_children (actor) == 1) { MetaShapedTexture *stex; stex = meta_surface_actor_get_texture (priv->surface); return meta_shaped_texture_get_image (stex, clip); } clutter_actor_get_size (actor, &width, &height); if (width == 0 || height == 0) return NULL; if (!clutter_actor_get_resource_scale (actor, &resource_scale)) return NULL; width = ceilf (width * resource_scale); height = ceilf (height * resource_scale); texture = cogl_texture_2d_new_with_size (cogl_context, width, height); if (!texture) return NULL; cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (texture), FALSE); offscreen = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture)); framebuffer = COGL_FRAMEBUFFER (offscreen); cogl_object_unref (texture); if (!cogl_framebuffer_allocate (framebuffer, &error)) { g_warning ("Failed to allocate framebuffer for screenshot: %s", error->message); cogl_object_unref (framebuffer); cogl_object_unref (texture); return NULL; } cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0); clutter_actor_get_position (actor, &x, &y); cogl_push_framebuffer (framebuffer); cogl_framebuffer_clear (framebuffer, COGL_BUFFER_BIT_COLOR, &clear_color); cogl_framebuffer_orthographic (framebuffer, 0, 0, width, height, 0, 1.0); cogl_framebuffer_scale (framebuffer, resource_scale, resource_scale, 1); cogl_framebuffer_translate (framebuffer, -x, -y, 0); clutter_actor_paint (actor); cogl_pop_framebuffer (); if (clip) { meta_rectangle_scale_double (clip, resource_scale, META_ROUNDING_STRATEGY_GROW, &scaled_clip); meta_rectangle_intersect (&scaled_clip, &(MetaRectangle) { .width = width, .height = height, }, &scaled_clip); } else { scaled_clip = (MetaRectangle) { .width = width, .height = height, }; } surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, scaled_clip.width, scaled_clip.height); cogl_framebuffer_read_pixels (framebuffer, scaled_clip.x, scaled_clip.y, scaled_clip.width, scaled_clip.height, CLUTTER_CAIRO_FORMAT_ARGB32, cairo_image_surface_get_data (surface)); cogl_object_unref (framebuffer); cairo_surface_mark_dirty (surface); return surface; }