/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2019 Red Hat, Inc * * 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 "core/frame.h" #include "core/stack.h" #include "core/window-private.h" #include "x11/meta-x11-display-private.h" #include "x11/meta-x11-stack-private.h" struct _MetaX11Stack { GObject parent; MetaX11Display *x11_display; /* * A sequence of all the Windows (X handles, not MetaWindows) of the windows * we manage, sorted in order. Suitable to be passed into _NET_CLIENT_LIST. */ GArray *xwindows; /* * MetaWindows waiting to be added to the xwindows list, after * being added to the MetaStack. * * The order of the elements in this list is not important; what is important * is the stack_position element of each window. */ GList *added; /* * Windows (X handles, not MetaWindows) waiting to be removed from the * xwindows list, after being removed from the MetaStack. * * The order of the elements in this list is not important. */ GList *removed; }; enum { PROP_DISPLAY = 1, N_PROPS }; static GParamSpec *pspecs[N_PROPS] = { 0 }; G_DEFINE_TYPE (MetaX11Stack, meta_x11_stack, G_TYPE_OBJECT) static void meta_x11_stack_init (MetaX11Stack *x11_stack) { x11_stack->xwindows = g_array_new (FALSE, FALSE, sizeof (Window)); } static void meta_x11_stack_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaX11Stack *x11_stack = META_X11_STACK (object); switch (prop_id) { case PROP_DISPLAY: x11_stack->x11_display = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void meta_x11_stack_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaX11Stack *x11_stack = META_X11_STACK (object); switch (prop_id) { case PROP_DISPLAY: g_value_set_object (value, x11_stack->x11_display); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void stack_window_added_cb (MetaStack *stack, MetaWindow *window, MetaX11Stack *x11_stack) { if (window->client_type != META_WINDOW_CLIENT_TYPE_X11) return; x11_stack->added = g_list_prepend (x11_stack->added, window); } static void stack_window_removed_cb (MetaStack *stack, MetaWindow *window, MetaX11Stack *x11_stack) { if (window->client_type != META_WINDOW_CLIENT_TYPE_X11) return; x11_stack->added = g_list_remove (x11_stack->added, window); x11_stack->removed = g_list_prepend (x11_stack->removed, GUINT_TO_POINTER (window->xwindow)); if (window->frame) { x11_stack->removed = g_list_prepend (x11_stack->removed, GUINT_TO_POINTER (window->frame->xwindow)); } } /** * stack_do_window_deletions: * * Go through "deleted" and take the matching windows * out of "windows". */ static void x11_stack_do_window_deletions (MetaX11Stack *x11_stack) { GList *tmp; int i; tmp = x11_stack->removed; while (tmp != NULL) { Window xwindow; xwindow = GPOINTER_TO_UINT (tmp->data); /* We go from the end figuring removals are more * likely to be recent. */ i = x11_stack->xwindows->len; while (i > 0) { --i; /* there's no guarantee we'll actually find windows to * remove, e.g. the same xwindow could have been * added/removed before we ever synced, and we put * both the window->xwindow and window->frame->xwindow * in the removal list. */ if (xwindow == g_array_index (x11_stack->xwindows, Window, i)) { g_array_remove_index (x11_stack->xwindows, i); goto next; } } next: tmp = tmp->next; } g_clear_pointer (&x11_stack->removed, g_list_free); } static void x11_stack_do_window_additions (MetaX11Stack *x11_stack) { GList *tmp; gint n_added; n_added = g_list_length (x11_stack->added); if (n_added > 0) { meta_topic (META_DEBUG_STACK, "Adding %d windows to sorted list\n", n_added); /* stack->added has the most recent additions at the * front of the list, so we need to reverse it */ x11_stack->added = g_list_reverse (x11_stack->added); tmp = x11_stack->added; while (tmp != NULL) { MetaWindow *w; w = tmp->data; g_array_append_val (x11_stack->xwindows, w->xwindow); tmp = tmp->next; } } g_clear_pointer (&x11_stack->added, g_list_free); } /** * x11_stack_sync_to_server: * * Order the windows on the X server to be the same as in our structure. * We do this using XRestackWindows if we don't know the previous order, * or XConfigureWindow on a few particular windows if we do and can figure * out the minimum set of changes. After that, we set __NET_CLIENT_LIST * and __NET_CLIENT_LIST_STACKING. * * FIXME: Now that we have a good view of the stacking order on the server * with MetaStackTracker it should be possible to do a simpler and better * job of computing the minimal set of stacking requests needed. */ static void x11_stack_sync_to_xserver (MetaX11Stack *x11_stack) { MetaX11Display *x11_display = x11_stack->x11_display; MetaStack *stack = x11_display->display->stack; GArray *x11_stacked; GArray *all_root_children_stacked; /* wayland OR x11 */ GList *tmp; GArray *hidden_stack_ids; uint64_t guard_window_id; GList *sorted; meta_topic (META_DEBUG_STACK, "Syncing window stack to server\n"); /* Create stacked xwindow arrays, in bottom-to-top order */ x11_stacked = g_array_new (FALSE, FALSE, sizeof (Window)); all_root_children_stacked = g_array_new (FALSE, FALSE, sizeof (guint64)); hidden_stack_ids = g_array_new (FALSE, FALSE, sizeof (guint64)); meta_topic (META_DEBUG_STACK, "Bottom to top: "); meta_push_no_msg_prefix (); sorted = meta_stack_list_windows (stack, NULL); for (tmp = sorted; tmp; tmp = tmp->next) { MetaWindow *w = tmp->data; guint64 top_level_window; guint64 stack_id; if (w->unmanaging) continue; meta_topic (META_DEBUG_STACK, "%u:%d - %s ", w->layer, w->stack_position, w->desc); if (w->client_type == META_WINDOW_CLIENT_TYPE_X11) g_array_append_val (x11_stacked, w->xwindow); if (w->frame) top_level_window = w->frame->xwindow; else top_level_window = w->xwindow; if (w->client_type == META_WINDOW_CLIENT_TYPE_X11) stack_id = top_level_window; else stack_id = w->stamp; /* We don't restack hidden windows along with the rest, though they are * reflected in the _NET hints. Hidden windows all get pushed below * the screens fullscreen guard_window. */ if (w->hidden) { g_array_append_val (hidden_stack_ids, stack_id); continue; } g_array_append_val (all_root_children_stacked, stack_id); } meta_topic (META_DEBUG_STACK, "\n"); meta_pop_no_msg_prefix (); /* The screen guard window sits above all hidden windows and acts as * a barrier to input reaching these windows. */ guard_window_id = x11_stack->x11_display->guard_window; g_array_append_val (hidden_stack_ids, guard_window_id); /* Sync to server */ meta_topic (META_DEBUG_STACK, "Restacking %u windows\n", all_root_children_stacked->len); meta_stack_tracker_restack_managed (x11_display->display->stack_tracker, (guint64 *)all_root_children_stacked->data, all_root_children_stacked->len); meta_stack_tracker_restack_at_bottom (x11_display->display->stack_tracker, (guint64 *)hidden_stack_ids->data, hidden_stack_ids->len); /* Sync _NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING */ XChangeProperty (x11_stack->x11_display->xdisplay, x11_stack->x11_display->xroot, x11_stack->x11_display->atom__NET_CLIENT_LIST, XA_WINDOW, 32, PropModeReplace, (unsigned char *) x11_stack->xwindows->data, x11_stack->xwindows->len); XChangeProperty (x11_stack->x11_display->xdisplay, x11_stack->x11_display->xroot, x11_stack->x11_display->atom__NET_CLIENT_LIST_STACKING, XA_WINDOW, 32, PropModeReplace, (unsigned char *) x11_stacked->data, x11_stacked->len); g_array_free (x11_stacked, TRUE); g_array_free (hidden_stack_ids, TRUE); g_array_free (all_root_children_stacked, TRUE); g_list_free (sorted); } static void stack_changed_cb (MetaX11Stack *x11_stack) { /* Do removals before adds, with paranoid idea that we might re-add * the same window IDs. */ x11_stack_do_window_deletions (x11_stack); x11_stack_do_window_additions (x11_stack); x11_stack_sync_to_xserver (x11_stack); } static void meta_x11_stack_constructed (GObject *object) { MetaX11Stack *x11_stack = META_X11_STACK (object); MetaX11Display *x11_display = x11_stack->x11_display; G_OBJECT_CLASS (meta_x11_stack_parent_class)->constructed (object); g_signal_connect (x11_display->display->stack, "window-added", G_CALLBACK (stack_window_added_cb), x11_stack); g_signal_connect (x11_display->display->stack, "window-removed", G_CALLBACK (stack_window_removed_cb), x11_stack); g_signal_connect_swapped (x11_display->display->stack, "changed", G_CALLBACK (stack_changed_cb), x11_stack); } static void meta_x11_stack_finalize (GObject *object) { MetaX11Stack *x11_stack = META_X11_STACK (object); MetaX11Display *x11_display = x11_stack->x11_display; if (x11_display->display && x11_display->display->stack) { g_signal_handlers_disconnect_by_data (x11_display->display->stack, x11_stack); } g_array_free (x11_stack->xwindows, TRUE); g_list_free (x11_stack->added); g_list_free (x11_stack->removed); G_OBJECT_CLASS (meta_x11_stack_parent_class)->finalize (object); } static void meta_x11_stack_class_init (MetaX11StackClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = meta_x11_stack_set_property; object_class->get_property = meta_x11_stack_get_property; object_class->constructed = meta_x11_stack_constructed; object_class->finalize = meta_x11_stack_finalize; pspecs[PROP_DISPLAY] = g_param_spec_object ("display", "Display", "Display", META_TYPE_X11_DISPLAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_properties (object_class, N_PROPS, pspecs); } MetaX11Stack * meta_x11_stack_new (MetaX11Display *x11_display) { return g_object_new (META_TYPE_X11_STACK, "display", x11_display, NULL); }