/* * Copyright (C) 2019 Alberts Muktupāvels * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include "meta-surface-private.h" #include #include #include "display-private.h" #include "errors.h" #include "frame.h" #include "meta-compositor-private.h" #include "prefs.h" #include "window-private.h" typedef struct { MetaCompositor *compositor; MetaWindow *window; MetaDisplay *display; Display *xdisplay; Damage damage; gboolean damage_received; Pixmap pixmap; int x; int y; gboolean position_changed; int width; int height; XserverRegion shape_region; gboolean shape_region_changed; XserverRegion opaque_region; gboolean opaque_region_changed; /* This is a copy of the original unshaded window so that we can still see * what the window looked like when it is needed for the _get_window_surface * function. */ cairo_surface_t *shaded_surface; } MetaSurfacePrivate; enum { PROP_0, PROP_COMPOSITOR, PROP_WINDOW, LAST_PROP }; static GParamSpec *surface_properties[LAST_PROP] = { NULL }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MetaSurface, meta_surface, G_TYPE_OBJECT) static void add_full_damage (MetaSurface *self) { MetaSurfacePrivate *priv; XserverRegion full_damage; priv = meta_surface_get_instance_private (self); if (priv->shape_region == None) return; full_damage = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesCopyRegion (priv->xdisplay, full_damage, priv->shape_region); XFixesTranslateRegion (priv->xdisplay, full_damage, priv->x, priv->y); meta_compositor_add_damage (priv->compositor, "add_full_damage", full_damage); XFixesDestroyRegion (priv->xdisplay, full_damage); } static XserverRegion get_frame_region (MetaSurface *self, XRectangle *client_rect) { MetaSurfacePrivate *priv; XserverRegion frame_region; XserverRegion client_region; priv = meta_surface_get_instance_private (self); frame_region = XFixesCreateRegion (priv->xdisplay, &(XRectangle) { .width = priv->width, .height = priv->height }, 1); client_region = XFixesCreateRegion (priv->xdisplay, client_rect, 1); XFixesSubtractRegion (priv->xdisplay, frame_region, frame_region, client_region); XFixesDestroyRegion (priv->xdisplay, client_region); return frame_region; } static void clip_shape_region (Display *xdisplay, XserverRegion shape_region, XRectangle *client_rect) { XserverRegion client_region; client_region = XFixesCreateRegion (xdisplay, client_rect, 1); XFixesIntersectRegion (xdisplay, shape_region, shape_region, client_region); XFixesDestroyRegion (xdisplay, client_region); } static gboolean update_shape_region (MetaSurface *self, XserverRegion damage_region) { MetaSurfacePrivate *priv; MetaFrameBorders borders; XRectangle client_rect; XserverRegion shape_region; priv = meta_surface_get_instance_private (self); if (!priv->shape_region_changed) return FALSE; g_assert (priv->shape_region == None); meta_frame_calc_borders (priv->window->frame, &borders); client_rect.x = borders.total.left; client_rect.y = borders.total.top; client_rect.width = priv->width - borders.total.left - borders.total.right; client_rect.height = priv->height - borders.total.top - borders.total.bottom; if (priv->window->frame != NULL && priv->window->shape_region != None) { shape_region = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesCopyRegion (priv->xdisplay, shape_region, priv->window->shape_region); XFixesTranslateRegion (priv->xdisplay, shape_region, client_rect.x, client_rect.y); clip_shape_region (priv->xdisplay, shape_region, &client_rect); } else if (priv->window->shape_region != None) { shape_region = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesCopyRegion (priv->xdisplay, shape_region, priv->window->shape_region); clip_shape_region (priv->xdisplay, shape_region, &client_rect); } else { shape_region = XFixesCreateRegion (priv->xdisplay, &client_rect, 1); } g_assert (shape_region != None); if (priv->window->frame != NULL) { XserverRegion frame_region; frame_region = get_frame_region (self, &client_rect); XFixesUnionRegion (priv->xdisplay, shape_region, shape_region, frame_region); XFixesDestroyRegion (priv->xdisplay, frame_region); } XFixesUnionRegion (priv->xdisplay, damage_region, damage_region, shape_region); priv->shape_region = shape_region; priv->shape_region_changed = FALSE; return TRUE; } static gboolean update_opaque_region (MetaSurface *self, XserverRegion damage_region) { MetaSurfacePrivate *priv; XserverRegion opaque_region; priv = meta_surface_get_instance_private (self); if (!priv->opaque_region_changed) return FALSE; g_assert (priv->opaque_region == None); if (priv->window->frame != NULL && priv->window->opaque_region != None) { MetaFrameBorders borders; meta_frame_calc_borders (priv->window->frame, &borders); opaque_region = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesCopyRegion (priv->xdisplay, opaque_region, priv->window->opaque_region); XFixesTranslateRegion (priv->xdisplay, opaque_region, borders.total.left, borders.total.top); } else if (priv->window->opaque_region != None) { opaque_region = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesCopyRegion (priv->xdisplay, opaque_region, priv->window->opaque_region); } else { opaque_region = None; } if (opaque_region != None) { XFixesUnionRegion (priv->xdisplay, damage_region, damage_region, opaque_region); } priv->opaque_region = opaque_region; priv->opaque_region_changed = FALSE; return TRUE; } static void free_pixmap (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (priv->pixmap == None) return; META_SURFACE_GET_CLASS (self)->free_pixmap (self); meta_error_trap_push (priv->display); XFreePixmap (priv->xdisplay, priv->pixmap); priv->pixmap = None; meta_error_trap_pop (priv->display); } static void ensure_pixmap (MetaSurface *self) { MetaSurfacePrivate *priv; Window xwindow; priv = meta_surface_get_instance_private (self); if (priv->pixmap != None) return; meta_error_trap_push (priv->display); xwindow = meta_window_get_toplevel_xwindow (priv->window); priv->pixmap = XCompositeNameWindowPixmap (priv->xdisplay, xwindow); if (meta_error_trap_pop_with_return (priv->display) != 0) priv->pixmap = None; } static void destroy_damage (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (priv->damage == None) return; meta_error_trap_push (priv->display); XDamageDestroy (priv->xdisplay, priv->damage); priv->damage = None; meta_error_trap_pop (priv->display); } static void create_damage (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); meta_error_trap_push (priv->display); g_assert (priv->damage == None); priv->damage = XDamageCreate (priv->xdisplay, meta_window_get_toplevel_xwindow (priv->window), XDamageReportNonEmpty); meta_error_trap_pop (priv->display); } static void notify_decorated_cb (MetaWindow *window, GParamSpec *pspec, MetaSurface *self) { destroy_damage (self); free_pixmap (self); create_damage (self); } static void notify_shaded_cb (MetaWindow *window, GParamSpec *pspec, MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (priv->shaded_surface != NULL) { cairo_surface_destroy (priv->shaded_surface); priv->shaded_surface = NULL; } if (meta_window_is_shaded (priv->window)) priv->shaded_surface = META_SURFACE_GET_CLASS (self)->get_image (self); } static void meta_surface_constructed (GObject *object) { MetaSurface *self; MetaSurfacePrivate *priv; self = META_SURFACE (object); priv = meta_surface_get_instance_private (self); G_OBJECT_CLASS (meta_surface_parent_class)->constructed (object); priv->display = meta_compositor_get_display (priv->compositor); priv->xdisplay = meta_display_get_xdisplay (priv->display); meta_surface_sync_geometry (self); create_damage (self); g_signal_connect_object (priv->window, "notify::decorated", G_CALLBACK (notify_decorated_cb), self, 0); g_signal_connect_object (priv->window, "notify::shaded", G_CALLBACK (notify_shaded_cb), self, 0); } static void meta_surface_finalize (GObject *object) { MetaSurface *self; MetaSurfacePrivate *priv; self = META_SURFACE (object); priv = meta_surface_get_instance_private (self); destroy_damage (self); free_pixmap (self); if (priv->shape_region != None) { XFixesTranslateRegion (priv->xdisplay, priv->shape_region, priv->x, priv->y); meta_compositor_add_damage (priv->compositor, "meta_surface_finalize", priv->shape_region); XFixesDestroyRegion (priv->xdisplay, priv->shape_region); priv->shape_region = None; } if (priv->opaque_region != None) { XFixesDestroyRegion (priv->xdisplay, priv->opaque_region); priv->opaque_region = None; } if (priv->shaded_surface != NULL) { cairo_surface_destroy (priv->shaded_surface); priv->shaded_surface = NULL; } G_OBJECT_CLASS (meta_surface_parent_class)->finalize (object); } static void meta_surface_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MetaSurface *self; MetaSurfacePrivate *priv; self = META_SURFACE (object); priv = meta_surface_get_instance_private (self); switch (property_id) { case PROP_COMPOSITOR: g_value_set_object (value, priv->compositor); break; case PROP_WINDOW: g_value_set_object (value, priv->window); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void meta_surface_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MetaSurface *self; MetaSurfacePrivate *priv; self = META_SURFACE (object); priv = meta_surface_get_instance_private (self); switch (property_id) { case PROP_COMPOSITOR: g_assert (priv->compositor == NULL); priv->compositor = g_value_get_object (value); break; case PROP_WINDOW: g_assert (priv->window == NULL); priv->window = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void meta_surface_install_properties (GObjectClass *object_class) { surface_properties[PROP_COMPOSITOR] = g_param_spec_object ("compositor", "compositor", "compositor", META_TYPE_COMPOSITOR, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); surface_properties[PROP_WINDOW] = g_param_spec_object ("window", "window", "window", META_TYPE_WINDOW, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, LAST_PROP, surface_properties); } static void meta_surface_class_init (MetaSurfaceClass *self_class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (self_class); object_class->constructed = meta_surface_constructed; object_class->finalize = meta_surface_finalize; object_class->get_property = meta_surface_get_property; object_class->set_property = meta_surface_set_property; meta_surface_install_properties (object_class); } static void meta_surface_init (MetaSurface *self) { } MetaCompositor * meta_surface_get_compositor (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->compositor; } MetaWindow * meta_surface_get_window (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->window; } Pixmap meta_surface_get_pixmap (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->pixmap; } int meta_surface_get_x (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->x; } int meta_surface_get_y (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->y; } int meta_surface_get_width (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->width; } int meta_surface_get_height (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->height; } XserverRegion meta_surface_get_opaque_region (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->opaque_region; } XserverRegion meta_surface_get_shape_region (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); return priv->shape_region; } cairo_surface_t * meta_surface_get_image (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (meta_window_is_shaded (priv->window)) { if (priv->shaded_surface != NULL) return cairo_surface_reference (priv->shaded_surface); else return NULL; } return META_SURFACE_GET_CLASS (self)->get_image (self); } gboolean meta_surface_has_shadow (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); /* Do not add shadows to fullscreen windows */ if (meta_window_is_fullscreen (priv->window)) return FALSE; /* Do not add shadows to maximized windows */ if (meta_window_is_maximized (priv->window)) return FALSE; /* Add shadows to windows with frame */ if (meta_window_get_frame (priv->window) != NULL) { /* Do not add shadows if GTK+ theme is used */ if (meta_prefs_get_theme_type () == META_THEME_TYPE_GTK) return FALSE; return TRUE; } /* Do not add shadows to non-opaque windows */ if (!meta_surface_is_opaque (self)) return FALSE; /* Do not add shadows to client side decorated windows */ if (meta_window_is_client_decorated (priv->window)) return FALSE; /* Never put a shadow around shaped windows */ if (priv->window->shape_region != None) return FALSE; /* Don't put shadow around DND icon windows */ if (priv->window->type == META_WINDOW_DND) return FALSE; /* Don't put shadow around desktop windows */ if (priv->window->type == META_WINDOW_DESKTOP) return FALSE; /* Add shadows to all other windows */ return TRUE; } gboolean meta_surface_is_opaque (MetaSurface *self) { MetaSurfacePrivate *priv; Visual *xvisual; XRenderPictFormat *format; XserverRegion region; int n_rects; XRectangle bounds; XRectangle *rects; priv = meta_surface_get_instance_private (self); if (priv->window->opacity != 0xffffffff) return FALSE; xvisual = meta_window_get_toplevel_xvisual (priv->window); format = XRenderFindVisualFormat (priv->xdisplay, xvisual); if (format->type != PictTypeDirect || !format->direct.alphaMask) return TRUE; if (priv->opaque_region == None) return FALSE; region = XFixesCreateRegion (priv->xdisplay, NULL, 0); XFixesSubtractRegion (priv->xdisplay, region, priv->shape_region, priv->opaque_region); rects = XFixesFetchRegionAndBounds (priv->xdisplay, region, &n_rects, &bounds); XFixesDestroyRegion (priv->xdisplay, region); XFree (rects); return (n_rects == 0 || bounds.width == 0 || bounds.height == 0); } gboolean meta_surface_is_visible (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (!meta_window_is_toplevel_mapped (priv->window) || priv->pixmap == None) return FALSE; return META_SURFACE_GET_CLASS (self)->is_visible (self); } void meta_surface_show (MetaSurface *self) { /* The reason we free pixmap here is so that we will still have * a valid pixmap when the window is unmapped. */ free_pixmap (self); META_SURFACE_GET_CLASS (self)->show (self); } void meta_surface_hide (MetaSurface *self) { META_SURFACE_GET_CLASS (self)->hide (self); add_full_damage (self); } void meta_surface_process_damage (MetaSurface *self, XDamageNotifyEvent *event) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); priv->damage_received = TRUE; meta_compositor_queue_redraw (priv->compositor); } void meta_surface_opacity_changed (MetaSurface *self) { META_SURFACE_GET_CLASS (self)->opacity_changed (self); add_full_damage (self); } void meta_surface_opaque_region_changed (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); if (priv->opaque_region != None) { XFixesTranslateRegion (priv->xdisplay, priv->opaque_region, priv->x, priv->y); meta_compositor_add_damage (priv->compositor, "meta_surface_opaque_region_changed", priv->opaque_region); XFixesDestroyRegion (priv->xdisplay, priv->opaque_region); priv->opaque_region = None; } else { meta_compositor_queue_redraw (priv->compositor); } priv->opaque_region_changed = TRUE; } void meta_surface_shape_region_changed (MetaSurface *self) { MetaSurfacePrivate *priv; priv = meta_surface_get_instance_private (self); meta_compositor_queue_redraw (priv->compositor); if (priv->shape_region != None) { XFixesTranslateRegion (priv->xdisplay, priv->shape_region, priv->x, priv->y); meta_compositor_add_damage (priv->compositor, "meta_surface_shape_region_changed", priv->shape_region); XFixesDestroyRegion (priv->xdisplay, priv->shape_region); priv->shape_region = None; } priv->shape_region_changed = TRUE; } void meta_surface_sync_geometry (MetaSurface *self) { MetaSurfacePrivate *priv; MetaRectangle rect; MetaRectangle old_geometry; gboolean position_changed; gboolean size_changed; priv = meta_surface_get_instance_private (self); meta_window_get_input_rect (priv->window, &rect); old_geometry.x = priv->x; old_geometry.y = priv->y; old_geometry.width = priv->width; old_geometry.height = priv->height; position_changed = FALSE; size_changed = FALSE; if (priv->x != rect.x || priv->y != rect.y) { add_full_damage (self); priv->x = rect.x; priv->y = rect.y; priv->position_changed = TRUE; position_changed = TRUE; } if (priv->width != rect.width || priv->height != rect.height) { free_pixmap (self); meta_surface_opaque_region_changed (self); meta_surface_shape_region_changed (self); priv->width = rect.width; priv->height = rect.height; size_changed = TRUE; } META_SURFACE_GET_CLASS (self)->sync_geometry (self, old_geometry, position_changed, size_changed); } void meta_surface_pre_paint (MetaSurface *self) { MetaSurfacePrivate *priv; XserverRegion damage; gboolean has_damage; priv = meta_surface_get_instance_private (self); damage = XFixesCreateRegion (priv->xdisplay, NULL, 0); has_damage = FALSE; if (priv->damage_received) { meta_error_trap_push (priv->display); XDamageSubtract (priv->xdisplay, priv->damage, None, damage); meta_error_trap_pop (priv->display); priv->damage_received = FALSE; has_damage = TRUE; } ensure_pixmap (self); META_SURFACE_GET_CLASS (self)->pre_paint (self, damage); if (update_shape_region (self, damage)) has_damage = TRUE; if (update_opaque_region (self, damage)) has_damage = TRUE; if (priv->position_changed) { XFixesUnionRegion (priv->xdisplay, damage, damage, priv->shape_region); priv->position_changed = FALSE; has_damage = TRUE; } if (!has_damage) { XFixesDestroyRegion (priv->xdisplay, damage); return; } XFixesTranslateRegion (priv->xdisplay, damage, priv->x, priv->y); meta_compositor_add_damage (priv->compositor, "meta_surface_pre_paint", damage); XFixesDestroyRegion (priv->xdisplay, damage); }