mirror of
https://github.com/brl/mutter.git
synced 2024-11-24 00:50:42 -05:00
1289 lines
38 KiB
C
1289 lines
38 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
|
|
/*
|
|
* SECTION:stack-tracker
|
|
* @short_description: Track stacking order for compositor
|
|
*
|
|
* #MetaStackTracker maintains the most accurate view we have at a
|
|
* given point of time of the ordering of the children of the root
|
|
* window (including override-redirect windows.) This is used to order
|
|
* the windows when the compositor draws them.
|
|
*
|
|
* By contrast, #MetaStack is responsible for keeping track of how we
|
|
* think that windows *should* be ordered. For windows we manage
|
|
* (non-override-redirect windows), the two stacking orders will be
|
|
* the same.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2009 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "frame.h"
|
|
#include "screen-private.h"
|
|
#include "stack-tracker.h"
|
|
#include <meta/util.h>
|
|
|
|
#include <meta/compositor.h>
|
|
|
|
/* The complexity here comes from resolving two competing factors:
|
|
*
|
|
* - We need to have a view of the stacking order that takes into
|
|
* account everything we have done without waiting for events
|
|
* back from the X server; we don't want to draw intermediate
|
|
* partially-stacked stack states just because we haven't received
|
|
* some notification yet.
|
|
*
|
|
* - Only the X server has an accurate view of the complete stacking;
|
|
* when we make a request to restack windows, we don't know how
|
|
* it will affect override-redirect windows, because at any point
|
|
* applications may restack these windows without our involvement.
|
|
*
|
|
* The technique we use is that we keep three sets of information:
|
|
*
|
|
* - The stacking order on the server as known from the last
|
|
* event we received.
|
|
* - A queue of stacking requests that *we* made subsequent to
|
|
* that last event.
|
|
* - A predicted stacking order, derived from applying the queued
|
|
* requests to the last state from the server.
|
|
*
|
|
* When we receive a new event: a) we compare the serial in the event to
|
|
* the serial of the queued requests and remove any that are now
|
|
* no longer pending b) if necessary, drop the predicted stacking
|
|
* order to recompute it at the next opportunity.
|
|
*
|
|
* Possible optimizations:
|
|
* Keep the stacks as an array + reverse-mapping hash table to avoid
|
|
* linear lookups.
|
|
* Keep the stacks as a GList + reverse-mapping hash table to avoid
|
|
* linear lookups and to make restacking constant-time.
|
|
*/
|
|
|
|
typedef union _MetaStackOp MetaStackOp;
|
|
|
|
typedef enum {
|
|
STACK_OP_ADD,
|
|
STACK_OP_REMOVE,
|
|
STACK_OP_RAISE_ABOVE,
|
|
STACK_OP_LOWER_BELOW
|
|
} MetaStackOpType;
|
|
|
|
/* MetaStackOp represents a "stacking operation" - a change to
|
|
* apply to a window stack. Depending on the context, it could
|
|
* either reflect a request we have sent to the server, or a
|
|
* notification event we received from the X server.
|
|
*/
|
|
union _MetaStackOp
|
|
{
|
|
struct {
|
|
MetaStackOpType type;
|
|
gulong serial;
|
|
MetaStackWindow window;
|
|
} any;
|
|
struct {
|
|
MetaStackOpType type;
|
|
gulong serial;
|
|
MetaStackWindow window;
|
|
} add;
|
|
struct {
|
|
MetaStackOpType type;
|
|
gulong serial;
|
|
MetaStackWindow window;
|
|
} remove;
|
|
struct {
|
|
MetaStackOpType type;
|
|
gulong serial;
|
|
MetaStackWindow window;
|
|
MetaStackWindow sibling;
|
|
} raise_above;
|
|
struct {
|
|
MetaStackOpType type;
|
|
gulong serial;
|
|
MetaStackWindow window;
|
|
MetaStackWindow sibling;
|
|
} lower_below;
|
|
};
|
|
|
|
struct _MetaStackTracker
|
|
{
|
|
MetaScreen *screen;
|
|
|
|
/* This is the last state of the stack as based on events received
|
|
* from the X server.
|
|
*/
|
|
GArray *xserver_stack;
|
|
|
|
/* This is the serial of the last request we made that was reflected
|
|
* in xserver_stack
|
|
*/
|
|
gulong xserver_serial;
|
|
|
|
/* A combined stack containing X and Wayland windows but without
|
|
* any unverified operations applied. */
|
|
GArray *verified_stack;
|
|
|
|
/* This is a queue of requests we've made to change the stacking order,
|
|
* where we haven't yet gotten a reply back from the server.
|
|
*/
|
|
GQueue *unverified_predictions;
|
|
|
|
/* This is how we think the stack is, based on verified_stack, and
|
|
* on the unverified_predictions we've made subsequent to
|
|
* verified_stack.
|
|
*/
|
|
GArray *predicted_stack;
|
|
|
|
/* Idle function used to sync the compositor's view of the window
|
|
* stack up with our best guess before a frame is drawn.
|
|
*/
|
|
guint sync_stack_later;
|
|
};
|
|
|
|
static gboolean
|
|
meta_stack_window_is_set (const MetaStackWindow *window)
|
|
{
|
|
if (window->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
return window->x11.xwindow == None ? FALSE : TRUE;
|
|
else
|
|
return window->wayland.meta_window ? TRUE : FALSE;
|
|
}
|
|
|
|
gboolean
|
|
meta_stack_window_equal (const MetaStackWindow *a,
|
|
const MetaStackWindow *b)
|
|
{
|
|
if (a->any.type == b->any.type)
|
|
{
|
|
if (a->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
return a->x11.xwindow == b->x11.xwindow;
|
|
else
|
|
return a->wayland.meta_window == b->wayland.meta_window;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static char *
|
|
get_window_id (MetaStackWindow *window)
|
|
{
|
|
if (window->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
return g_strdup_printf ("X11:%lx", window->x11.xwindow);
|
|
else
|
|
return g_strdup_printf ("Wayland:%p", window->wayland.meta_window);
|
|
}
|
|
|
|
static void
|
|
meta_stack_op_dump (MetaStackOp *op,
|
|
const char *prefix,
|
|
const char *suffix)
|
|
{
|
|
char *window_id = get_window_id (&op->any.window);
|
|
|
|
switch (op->any.type)
|
|
{
|
|
case STACK_OP_ADD:
|
|
meta_topic (META_DEBUG_STACK, "%sADD(%s; %ld)%s",
|
|
prefix, window_id, op->any.serial, suffix);
|
|
break;
|
|
case STACK_OP_REMOVE:
|
|
meta_topic (META_DEBUG_STACK, "%sREMOVE(%s; %ld)%s",
|
|
prefix, window_id, op->any.serial, suffix);
|
|
break;
|
|
case STACK_OP_RAISE_ABOVE:
|
|
{
|
|
char *sibling_id = get_window_id (&op->raise_above.sibling);
|
|
meta_topic (META_DEBUG_STACK, "%sRAISE_ABOVE(%s, %s; %ld)%s",
|
|
prefix,
|
|
window_id, sibling_id,
|
|
op->any.serial,
|
|
suffix);
|
|
g_free (sibling_id);
|
|
break;
|
|
}
|
|
case STACK_OP_LOWER_BELOW:
|
|
{
|
|
char *sibling_id = get_window_id (&op->lower_below.sibling);
|
|
meta_topic (META_DEBUG_STACK, "%sLOWER_BELOW(%s, %s; %ld)%s",
|
|
prefix,
|
|
window_id, sibling_id,
|
|
op->any.serial,
|
|
suffix);
|
|
g_free (sibling_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_free (window_id);
|
|
}
|
|
|
|
static void
|
|
meta_stack_tracker_dump (MetaStackTracker *tracker)
|
|
{
|
|
guint i;
|
|
GList *l;
|
|
|
|
meta_topic (META_DEBUG_STACK, "MetaStackTracker state (screen=%d)\n", tracker->screen->number);
|
|
meta_push_no_msg_prefix ();
|
|
meta_topic (META_DEBUG_STACK, " xserver_serial: %ld\n", tracker->xserver_serial);
|
|
meta_topic (META_DEBUG_STACK, " xserver_stack: ");
|
|
for (i = 0; i < tracker->xserver_stack->len; i++)
|
|
{
|
|
MetaStackWindow *window = &g_array_index (tracker->xserver_stack, MetaStackWindow, i);
|
|
char *window_id = get_window_id (window);
|
|
meta_topic (META_DEBUG_STACK, " %s", window_id);
|
|
g_free (window_id);
|
|
}
|
|
meta_topic (META_DEBUG_STACK, "\n verfied_stack: ");
|
|
for (i = 0; i < tracker->verified_stack->len; i++)
|
|
{
|
|
MetaStackWindow *window = &g_array_index (tracker->verified_stack, MetaStackWindow, i);
|
|
char *window_id = get_window_id (window);
|
|
meta_topic (META_DEBUG_STACK, " %s", window_id);
|
|
g_free (window_id);
|
|
}
|
|
meta_topic (META_DEBUG_STACK, "\n unverified_predictions: [");
|
|
for (l = tracker->unverified_predictions->head; l; l = l->next)
|
|
{
|
|
MetaStackOp *op = l->data;
|
|
meta_stack_op_dump (op, "", l->next ? ", " : "");
|
|
}
|
|
meta_topic (META_DEBUG_STACK, "]\n");
|
|
if (tracker->predicted_stack)
|
|
{
|
|
meta_topic (META_DEBUG_STACK, "\n predicted_stack: ");
|
|
for (i = 0; i < tracker->predicted_stack->len; i++)
|
|
{
|
|
MetaStackWindow *window = &g_array_index (tracker->predicted_stack, MetaStackWindow, i);
|
|
char *window_id = get_window_id (window);
|
|
meta_topic (META_DEBUG_STACK, " %s", window_id);
|
|
g_free (window_id);
|
|
}
|
|
}
|
|
meta_topic (META_DEBUG_STACK, "\n");
|
|
meta_pop_no_msg_prefix ();
|
|
}
|
|
|
|
static void
|
|
meta_stack_op_free (MetaStackOp *op)
|
|
{
|
|
g_slice_free (MetaStackOp, op);
|
|
}
|
|
|
|
static int
|
|
find_window (GArray *window_stack,
|
|
MetaStackWindow *window)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < window_stack->len; i++)
|
|
{
|
|
MetaStackWindow *current = &g_array_index (window_stack, MetaStackWindow, i);
|
|
if (current->any.type == window->any.type)
|
|
{
|
|
if (current->any.type == META_WINDOW_CLIENT_TYPE_X11 &&
|
|
current->x11.xwindow == window->x11.xwindow)
|
|
return i;
|
|
else
|
|
if (current->wayland.meta_window == window->wayland.meta_window)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Returns TRUE if stack was changed */
|
|
static gboolean
|
|
move_window_above (GArray *stack,
|
|
MetaStackWindow *window,
|
|
int old_pos,
|
|
int above_pos)
|
|
{
|
|
/* Copy the window by-value before we start shifting things around
|
|
* in the stack in case window points into the stack itself. */
|
|
MetaStackWindow window_val = *window;
|
|
int i;
|
|
|
|
if (old_pos < above_pos)
|
|
{
|
|
for (i = old_pos; i < above_pos; i++)
|
|
g_array_index (stack, MetaStackWindow, i) =
|
|
g_array_index (stack, MetaStackWindow, i + 1);
|
|
|
|
g_array_index (stack, MetaStackWindow, above_pos) = window_val;
|
|
|
|
return TRUE;
|
|
}
|
|
else if (old_pos > above_pos + 1)
|
|
{
|
|
for (i = old_pos; i > above_pos + 1; i--)
|
|
g_array_index (stack, MetaStackWindow, i) =
|
|
g_array_index (stack, MetaStackWindow, i - 1);
|
|
|
|
g_array_index (stack, MetaStackWindow, above_pos + 1) = window_val;
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* Returns TRUE if stack was changed */
|
|
static gboolean
|
|
meta_stack_op_apply (MetaStackOp *op,
|
|
GArray *stack)
|
|
{
|
|
switch (op->any.type)
|
|
{
|
|
case STACK_OP_ADD:
|
|
{
|
|
int old_pos = find_window (stack, &op->add.window);
|
|
if (old_pos >= 0)
|
|
{
|
|
char *window_id = get_window_id (&op->add.window);
|
|
g_warning ("STACK_OP_ADD: window %s already in stack",
|
|
window_id);
|
|
g_free (window_id);
|
|
return FALSE;
|
|
}
|
|
|
|
g_array_append_val (stack, op->add.window);
|
|
return TRUE;
|
|
}
|
|
case STACK_OP_REMOVE:
|
|
{
|
|
int old_pos = find_window (stack, &op->remove.window);
|
|
if (old_pos < 0)
|
|
{
|
|
char *window_id = get_window_id (&op->remove.window);
|
|
g_warning ("STACK_OP_REMOVE: window %s not in stack",
|
|
window_id);
|
|
g_free (window_id);
|
|
return FALSE;
|
|
}
|
|
|
|
g_array_remove_index (stack, old_pos);
|
|
return TRUE;
|
|
}
|
|
case STACK_OP_RAISE_ABOVE:
|
|
{
|
|
int old_pos = find_window (stack, &op->raise_above.window);
|
|
int above_pos;
|
|
if (old_pos < 0)
|
|
{
|
|
char *window_id = get_window_id (&op->raise_above.window);
|
|
g_warning ("STACK_OP_RAISE_ABOVE: window %s not in stack",
|
|
window_id);
|
|
g_free (window_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (meta_stack_window_is_set (&op->raise_above.sibling))
|
|
{
|
|
above_pos = find_window (stack, &op->raise_above.sibling);
|
|
if (above_pos < 0)
|
|
{
|
|
char *sibling_id = get_window_id (&op->raise_above.sibling);
|
|
g_warning ("STACK_OP_RAISE_ABOVE: sibling window %s not in stack",
|
|
sibling_id);
|
|
g_free (sibling_id);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
above_pos = -1;
|
|
}
|
|
|
|
return move_window_above (stack, &op->raise_above.window, old_pos, above_pos);
|
|
}
|
|
case STACK_OP_LOWER_BELOW:
|
|
{
|
|
int old_pos = find_window (stack, &op->lower_below.window);
|
|
int above_pos;
|
|
if (old_pos < 0)
|
|
{
|
|
char *window_id = get_window_id (&op->lower_below.window);
|
|
g_warning ("STACK_OP_LOWER_BELOW: window %s not in stack",
|
|
window_id);
|
|
g_free (window_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (meta_stack_window_is_set (&op->lower_below.sibling))
|
|
{
|
|
int below_pos = find_window (stack, &op->lower_below.sibling);
|
|
if (below_pos < 0)
|
|
{
|
|
char *sibling_id = get_window_id (&op->lower_below.sibling);
|
|
g_warning ("STACK_OP_LOWER_BELOW: sibling window %s not in stack",
|
|
sibling_id);
|
|
g_free (sibling_id);
|
|
return FALSE;
|
|
}
|
|
|
|
above_pos = below_pos - 1;
|
|
}
|
|
else
|
|
{
|
|
above_pos = stack->len - 1;
|
|
}
|
|
|
|
return move_window_above (stack, &op->lower_below.window, old_pos, above_pos);
|
|
}
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
return FALSE;
|
|
}
|
|
|
|
static GArray *
|
|
copy_stack (GArray *stack)
|
|
{
|
|
GArray *copy = g_array_sized_new (FALSE, FALSE, sizeof (MetaStackWindow), stack->len);
|
|
|
|
g_array_set_size (copy, stack->len);
|
|
|
|
memcpy (copy->data, stack->data, sizeof (MetaStackWindow) * stack->len);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
requery_xserver_stack (MetaStackTracker *tracker)
|
|
{
|
|
MetaScreen *screen = tracker->screen;
|
|
Window ignored1, ignored2;
|
|
Window *children;
|
|
guint n_children;
|
|
guint i;
|
|
|
|
if (tracker->xserver_stack)
|
|
g_array_free (tracker->xserver_stack, TRUE);
|
|
|
|
tracker->xserver_serial = XNextRequest (screen->display->xdisplay);
|
|
|
|
XQueryTree (screen->display->xdisplay,
|
|
screen->xroot,
|
|
&ignored1, &ignored2, &children, &n_children);
|
|
|
|
tracker->xserver_stack =
|
|
g_array_sized_new (FALSE, FALSE, sizeof (MetaStackWindow), n_children);
|
|
g_array_set_size (tracker->xserver_stack, n_children);
|
|
|
|
for (i = 0; i < n_children; i++)
|
|
{
|
|
MetaStackWindow *window =
|
|
&g_array_index (tracker->xserver_stack, MetaStackWindow, i);
|
|
window->any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
window->x11.xwindow = children[i];
|
|
}
|
|
|
|
XFree (children);
|
|
}
|
|
|
|
MetaStackTracker *
|
|
meta_stack_tracker_new (MetaScreen *screen)
|
|
{
|
|
MetaStackTracker *tracker;
|
|
|
|
tracker = g_new0 (MetaStackTracker, 1);
|
|
tracker->screen = screen;
|
|
|
|
requery_xserver_stack (tracker);
|
|
|
|
tracker->verified_stack = copy_stack (tracker->xserver_stack);
|
|
|
|
tracker->unverified_predictions = g_queue_new ();
|
|
|
|
meta_stack_tracker_dump (tracker);
|
|
|
|
return tracker;
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_free (MetaStackTracker *tracker)
|
|
{
|
|
if (tracker->sync_stack_later)
|
|
meta_later_remove (tracker->sync_stack_later);
|
|
|
|
g_array_free (tracker->xserver_stack, TRUE);
|
|
g_array_free (tracker->verified_stack, TRUE);
|
|
if (tracker->predicted_stack)
|
|
g_array_free (tracker->predicted_stack, TRUE);
|
|
|
|
g_queue_foreach (tracker->unverified_predictions, (GFunc)meta_stack_op_free, NULL);
|
|
g_queue_free (tracker->unverified_predictions);
|
|
tracker->unverified_predictions = NULL;
|
|
|
|
g_free (tracker);
|
|
}
|
|
|
|
static void
|
|
stack_tracker_apply_prediction (MetaStackTracker *tracker,
|
|
MetaStackOp *op)
|
|
{
|
|
/* If this is a wayland operation then it's implicitly verified so
|
|
* we can apply it immediately so long as it doesn't depend on any
|
|
* unverified X operations...
|
|
*/
|
|
if (op->any.window.any.type == META_WINDOW_CLIENT_TYPE_WAYLAND &&
|
|
tracker->unverified_predictions->length == 0)
|
|
{
|
|
if (meta_stack_op_apply (op, tracker->verified_stack))
|
|
meta_stack_tracker_queue_sync_stack (tracker);
|
|
}
|
|
else
|
|
{
|
|
meta_stack_op_dump (op, "Predicting: ", "\n");
|
|
g_queue_push_tail (tracker->unverified_predictions, op);
|
|
}
|
|
|
|
if (!tracker->predicted_stack ||
|
|
meta_stack_op_apply (op, tracker->predicted_stack))
|
|
meta_stack_tracker_queue_sync_stack (tracker);
|
|
|
|
meta_stack_tracker_dump (tracker);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_add (MetaStackTracker *tracker,
|
|
const MetaStackWindow *window,
|
|
gulong serial)
|
|
{
|
|
MetaStackOp *op = g_slice_new (MetaStackOp);
|
|
|
|
op->any.type = STACK_OP_ADD;
|
|
op->any.serial = serial;
|
|
op->any.window = *window;
|
|
|
|
stack_tracker_apply_prediction (tracker, op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_remove (MetaStackTracker *tracker,
|
|
const MetaStackWindow *window,
|
|
gulong serial)
|
|
{
|
|
MetaStackOp *op = g_slice_new (MetaStackOp);
|
|
|
|
op->any.type = STACK_OP_REMOVE;
|
|
op->any.serial = serial;
|
|
op->any.window = *window;
|
|
|
|
stack_tracker_apply_prediction (tracker, op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_restack_windows (MetaStackTracker *tracker,
|
|
const MetaStackWindow *windows,
|
|
int n_windows,
|
|
gulong serial)
|
|
{
|
|
int i;
|
|
int n_x_windows = 0;
|
|
|
|
/* XRestackWindows() isn't actually a X requests - it's broken down
|
|
* by XLib into a series of XConfigureWindow(StackMode=below); we
|
|
* mirror that here.
|
|
*
|
|
* Since there may be a mixture of X and wayland windows in the
|
|
* stack it's ambiguous which operations we should associate with an
|
|
* X serial number. One thing we do know though is that there will
|
|
* be (n_x_window - 1) X requests made.
|
|
*
|
|
* Aside: Having a separate StackOp for this would be possible to
|
|
* get some extra efficiency in memory allocation and in applying
|
|
* the op, at the expense of a code complexity. Implementation hint
|
|
* for that - keep op->restack_window.n_complete, and when receiving
|
|
* events with intermediate serials, set n_complete rather than
|
|
* removing the op from the queue.
|
|
*/
|
|
if (n_windows && windows[0].any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
n_x_windows++;
|
|
for (i = 0; i < n_windows - 1; i++)
|
|
{
|
|
const MetaStackWindow *lower = &windows[i + 1];
|
|
gboolean involves_x = FALSE;
|
|
|
|
if (lower->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
{
|
|
n_x_windows++;
|
|
|
|
/* Since the first X window is a reference point we only
|
|
* assoicate a serial number with the operations involving
|
|
* later X windows. */
|
|
if (n_x_windows > 1)
|
|
involves_x = TRUE;
|
|
}
|
|
|
|
meta_stack_tracker_record_lower_below (tracker, lower, &windows[i],
|
|
involves_x ? serial++ : 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_raise_above (MetaStackTracker *tracker,
|
|
const MetaStackWindow *window,
|
|
const MetaStackWindow *sibling,
|
|
gulong serial)
|
|
{
|
|
MetaStackOp *op = g_slice_new (MetaStackOp);
|
|
|
|
op->any.type = STACK_OP_RAISE_ABOVE;
|
|
op->any.serial = serial;
|
|
op->any.window = *window;
|
|
if (sibling)
|
|
op->raise_above.sibling = *sibling;
|
|
else
|
|
{
|
|
op->raise_above.sibling.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op->raise_above.sibling.x11.xwindow = None;
|
|
}
|
|
|
|
stack_tracker_apply_prediction (tracker, op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_lower_below (MetaStackTracker *tracker,
|
|
const MetaStackWindow *window,
|
|
const MetaStackWindow *sibling,
|
|
gulong serial)
|
|
{
|
|
MetaStackOp *op = g_slice_new (MetaStackOp);
|
|
|
|
op->any.type = STACK_OP_LOWER_BELOW;
|
|
op->any.serial = serial;
|
|
op->any.window = *window;
|
|
if (sibling)
|
|
op->lower_below.sibling = *sibling;
|
|
else
|
|
{
|
|
op->lower_below.sibling.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op->lower_below.sibling.x11.xwindow = None;
|
|
}
|
|
|
|
stack_tracker_apply_prediction (tracker, op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_record_lower (MetaStackTracker *tracker,
|
|
const MetaStackWindow *window,
|
|
gulong serial)
|
|
{
|
|
meta_stack_tracker_record_raise_above (tracker, window, NULL, serial);
|
|
}
|
|
|
|
/* @op is an operation derived from an X event from the server and we
|
|
* want to verify that our predicted operations are consistent with
|
|
* what's being reported by the X server.
|
|
*
|
|
* NB: Since our stack may actually be a mixture of X and Wayland
|
|
* clients we can't simply apply these operations derived from X
|
|
* events onto our stack and discard old predictions because these
|
|
* operations aren't aware of wayland windows.
|
|
*
|
|
* This function applies all the unverified predicted operations up to
|
|
* the given @serial onto the verified_stack so that we can check the
|
|
* stack for consistency with the given X operation.
|
|
*
|
|
* Return value: %TRUE if the predicted state is consistent with
|
|
* receiving the given @op from X, else %FALSE.
|
|
*
|
|
* @modified will be set to %TRUE if tracker->verified_stack is
|
|
* changed by applying any newly validated operations, else %FALSE.
|
|
*/
|
|
static gboolean
|
|
stack_tracker_verify_predictions (MetaStackTracker *tracker,
|
|
MetaStackOp *op,
|
|
gboolean *modified)
|
|
{
|
|
GArray *tmp_predicted_stack = NULL;
|
|
GArray *predicted_stack;
|
|
gboolean modified_stack = FALSE;
|
|
|
|
/* Wayland operations don't need to be verified and shouldn't end up
|
|
* passed to this api. */
|
|
g_return_val_if_fail (op->any.window.any.type == META_WINDOW_CLIENT_TYPE_X11, FALSE);
|
|
|
|
if (tracker->unverified_predictions->length)
|
|
{
|
|
GList *l;
|
|
|
|
tmp_predicted_stack = predicted_stack = copy_stack (tracker->verified_stack);
|
|
|
|
for (l = tracker->unverified_predictions->head; l; l = l->next)
|
|
{
|
|
MetaStackOp *current_op = l->data;
|
|
|
|
if (current_op->any.serial > op->any.serial)
|
|
break;
|
|
|
|
modified_stack |= meta_stack_op_apply (current_op, predicted_stack);
|
|
}
|
|
}
|
|
else
|
|
predicted_stack = tracker->verified_stack;
|
|
|
|
switch (op->any.type)
|
|
{
|
|
case STACK_OP_ADD:
|
|
if (!find_window (predicted_stack, &op->any.window))
|
|
{
|
|
char *window_id = get_window_id (&op->any.window);
|
|
meta_topic (META_DEBUG_STACK, "Verify STACK_OP_ADD: window %s not found\n",
|
|
window_id);
|
|
g_free (window_id);
|
|
goto not_verified;
|
|
}
|
|
break;
|
|
case STACK_OP_REMOVE:
|
|
if (find_window (predicted_stack, &op->any.window))
|
|
{
|
|
char *window_id = get_window_id (&op->any.window);
|
|
meta_topic (META_DEBUG_STACK, "Verify STACK_OP_REMOVE: window %s was unexpectedly found\n",
|
|
window_id);
|
|
g_free (window_id);
|
|
goto not_verified;
|
|
}
|
|
break;
|
|
case STACK_OP_RAISE_ABOVE:
|
|
{
|
|
Window last_xwindow = None;
|
|
char *window_id;
|
|
unsigned int i;
|
|
|
|
/* This code is only intended for verifying operations based
|
|
* on XEvents where we can assume the sibling refers to
|
|
* another X window... */
|
|
g_return_val_if_fail (op->raise_above.sibling.any.type ==
|
|
META_WINDOW_CLIENT_TYPE_X11, FALSE);
|
|
|
|
for (i = 0; i < predicted_stack->len; i++)
|
|
{
|
|
MetaStackWindow *window = &g_array_index (predicted_stack, MetaStackWindow, i);
|
|
|
|
if (meta_stack_window_equal (window, &op->any.window))
|
|
{
|
|
if (last_xwindow == op->raise_above.sibling.x11.xwindow)
|
|
goto verified;
|
|
else
|
|
goto not_verified;
|
|
}
|
|
|
|
if (window->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
last_xwindow = window->x11.xwindow;
|
|
}
|
|
|
|
window_id = get_window_id (&op->any.window);
|
|
meta_topic (META_DEBUG_STACK, "Verify STACK_OP_RAISE_ABOVE: window %s not found\n",
|
|
window_id);
|
|
g_free (window_id);
|
|
goto not_verified;
|
|
}
|
|
case STACK_OP_LOWER_BELOW:
|
|
g_warn_if_reached (); /* No X events currently lead to this path */
|
|
goto not_verified;
|
|
}
|
|
|
|
verified:
|
|
|
|
/* We can free the operations which we have now verified... */
|
|
while (tracker->unverified_predictions->head)
|
|
{
|
|
MetaStackOp *queued_op = tracker->unverified_predictions->head->data;
|
|
|
|
if (queued_op->any.serial > op->any.serial)
|
|
break;
|
|
|
|
g_queue_pop_head (tracker->unverified_predictions);
|
|
meta_stack_op_free (queued_op);
|
|
}
|
|
|
|
*modified = modified_stack;
|
|
if (modified_stack)
|
|
{
|
|
g_array_free (tracker->verified_stack, TRUE);
|
|
tracker->verified_stack = predicted_stack;
|
|
}
|
|
else if (tmp_predicted_stack)
|
|
g_array_free (tmp_predicted_stack, TRUE);
|
|
|
|
return TRUE;
|
|
|
|
not_verified:
|
|
|
|
if (tmp_predicted_stack)
|
|
g_array_free (tmp_predicted_stack, TRUE);
|
|
|
|
if (tracker->predicted_stack)
|
|
{
|
|
g_array_free (tracker->predicted_stack, TRUE);
|
|
tracker->predicted_stack = NULL;
|
|
}
|
|
|
|
*modified = FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* If we find that our predicted state is not consistent with what the
|
|
* X server is reporting to us then this function can re-query and
|
|
* re-synchronize verified_stack with the X server stack while
|
|
* hopefully not disrupting the relative stacking of Wayland windows.
|
|
*
|
|
* Return value: %TRUE if the verified stack was modified with respect
|
|
* to the predicted stack else %FALSE.
|
|
*
|
|
* Note: ->predicted_stack will be cleared by this function if
|
|
* ->verified_stack had to be modified when re-synchronizing.
|
|
*/
|
|
static gboolean
|
|
resync_verified_stack_with_xserver_stack (MetaStackTracker *tracker)
|
|
{
|
|
GList *l;
|
|
unsigned int i, j;
|
|
MetaStackWindow *expected_xwindow;
|
|
gboolean modified_stack;
|
|
|
|
/* Overview of the algorithm:
|
|
*
|
|
* - Re-query the complete X window stack from the X server via
|
|
* XQueryTree() and update xserver_stack.
|
|
*
|
|
* - Apply all operations in unverified_predictions to
|
|
* verified_stack so we have a predicted stack including Wayland
|
|
* windows and free the queue of unverified_predictions.
|
|
*
|
|
* - Iterate through the x windows listed in verified_stack at the
|
|
* same time as iterating the windows in xserver_list. (Stop
|
|
* when we reach the end of the xserver_list)
|
|
* - If the window found doesn't match the window expected
|
|
* according to the order of xserver_list then:
|
|
* - Look ahead for the window we were expecting and restack
|
|
* that above the previous X window. If we fail to find the
|
|
* expected window then create a new entry for it and stack
|
|
* that.
|
|
*
|
|
* - Continue to iterate through verified_stack for any remaining
|
|
* X windows that we now know aren't in the xserver_list and
|
|
* remove them.
|
|
*
|
|
* - Free ->predicted_stack if any.
|
|
*/
|
|
|
|
meta_topic (META_DEBUG_STACK, "Fully re-synchronizing X stack with verified stack\n");
|
|
|
|
requery_xserver_stack (tracker);
|
|
|
|
for (l = tracker->unverified_predictions->head; l; l = l->next)
|
|
meta_stack_op_apply (l->data, tracker->verified_stack);
|
|
g_queue_clear (tracker->unverified_predictions);
|
|
|
|
j = 0;
|
|
expected_xwindow =
|
|
&g_array_index (tracker->xserver_stack, MetaStackWindow, j);
|
|
|
|
for (i = 0;
|
|
i < tracker->verified_stack->len;
|
|
)
|
|
{
|
|
MetaStackWindow *current =
|
|
&g_array_index (tracker->verified_stack, MetaStackWindow, i);
|
|
|
|
if (current->any.type != META_WINDOW_CLIENT_TYPE_X11)
|
|
{
|
|
/* Progress i but not j */
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (current->x11.xwindow != expected_xwindow->x11.xwindow)
|
|
{
|
|
MetaStackWindow new;
|
|
MetaStackWindow *expected;
|
|
int expected_index;
|
|
|
|
/* If the current window corresponds to a window that's not
|
|
* in xserver_stack any more then the least disruptive thing
|
|
* we can do is to simply remove it and take another look at
|
|
* the same index.
|
|
*
|
|
* Note: we didn't used to do this and instead relied on
|
|
* removed windows getting pushed to the end of the list so
|
|
* they could all be removed together but this also resulted
|
|
* in pushing Wayland windows to the end too, disrupting
|
|
* their positioning relative to X windows too much.
|
|
*
|
|
* Technically we only need to look forward from j if we
|
|
* wanted to optimize this a bit...
|
|
*/
|
|
if (find_window (tracker->xserver_stack, current) < 0)
|
|
{
|
|
g_array_remove_index (tracker->verified_stack, i);
|
|
continue;
|
|
}
|
|
|
|
/* Technically we only need to look forward from i if we
|
|
* wanted to optimize this a bit... */
|
|
expected_index =
|
|
find_window (tracker->verified_stack, expected_xwindow);
|
|
|
|
if (expected_index >= 0)
|
|
{
|
|
expected = &g_array_index (tracker->verified_stack,
|
|
MetaStackWindow, expected_index);
|
|
}
|
|
else
|
|
{
|
|
new.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
new.x11.xwindow = expected_xwindow->x11.xwindow;
|
|
|
|
g_array_append_val (tracker->verified_stack, new);
|
|
|
|
expected = &new;
|
|
expected_index = tracker->verified_stack->len - 1;
|
|
}
|
|
|
|
/* Note: that this move will effectively bump the index of
|
|
* the current window.
|
|
*
|
|
* We want to continue by re-checking this window against
|
|
* the next expected window though so we don't have to
|
|
* update i to compensate here.
|
|
*/
|
|
move_window_above (tracker->verified_stack, expected,
|
|
expected_index, /* current index */
|
|
i - 1); /* above */
|
|
modified_stack = TRUE;
|
|
}
|
|
|
|
/* NB: we want to make sure that if we break the loop because j
|
|
* reaches the end of xserver_stack that i has also been
|
|
* incremented already so that we can run a final loop to remove
|
|
* remaining windows based on the i index. */
|
|
i++;
|
|
|
|
j++;
|
|
expected_xwindow =
|
|
&g_array_index (tracker->xserver_stack, MetaStackWindow, j);
|
|
|
|
if (j >= tracker->xserver_stack->len)
|
|
break;
|
|
}
|
|
|
|
/* We now know that any remaining X windows aren't listed in the
|
|
* xserver_stack and so we can remove them. */
|
|
while (i < tracker->verified_stack->len)
|
|
{
|
|
MetaStackWindow *current =
|
|
&g_array_index (tracker->verified_stack, MetaStackWindow, i);
|
|
|
|
if (current->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
g_array_remove_index (tracker->verified_stack, i);
|
|
else
|
|
i++;
|
|
|
|
modified_stack = TRUE;
|
|
}
|
|
|
|
/* If we get to the end of verified_list and there are any remaining
|
|
* entries in xserver_list then append them all to the end */
|
|
for (; j < tracker->xserver_stack->len; j++)
|
|
{
|
|
MetaStackWindow *current =
|
|
&g_array_index (tracker->xserver_stack, MetaStackWindow, j);
|
|
g_array_append_val (tracker->verified_stack, *current);
|
|
|
|
modified_stack = TRUE;
|
|
}
|
|
|
|
if (modified_stack)
|
|
{
|
|
if (tracker->predicted_stack)
|
|
{
|
|
g_array_free (tracker->predicted_stack, TRUE);
|
|
tracker->predicted_stack = NULL;
|
|
}
|
|
|
|
meta_stack_tracker_queue_sync_stack (tracker);
|
|
}
|
|
|
|
return modified_stack;
|
|
}
|
|
|
|
static void
|
|
stack_tracker_event_received (MetaStackTracker *tracker,
|
|
MetaStackOp *op)
|
|
{
|
|
gboolean need_sync = FALSE;
|
|
gboolean verified;
|
|
|
|
meta_stack_op_dump (op, "Stack op event received: ", "\n");
|
|
|
|
if (op->any.serial < tracker->xserver_serial)
|
|
{
|
|
g_warning ("Spurious X event received affecting stack; doing full re-query");
|
|
resync_verified_stack_with_xserver_stack (tracker);
|
|
meta_stack_tracker_dump (tracker);
|
|
return;
|
|
}
|
|
|
|
tracker->xserver_serial = op->any.serial;
|
|
|
|
/* XXX: With the design we have ended up with it looks like we've
|
|
* ended up making it unnecessary to maintain tracker->xserver_stack
|
|
* since we only need an xserver_stack during the
|
|
* resync_verified_stack_with_xserver_stack() at which point we are
|
|
* going to query the full stack from the X server using
|
|
* XQueryTree() anyway.
|
|
*
|
|
* TODO: remove tracker->xserver_stack.
|
|
*/
|
|
meta_stack_op_apply (op, tracker->xserver_stack);
|
|
|
|
verified = stack_tracker_verify_predictions (tracker, op, &need_sync);
|
|
if (!verified)
|
|
{
|
|
resync_verified_stack_with_xserver_stack (tracker);
|
|
meta_stack_tracker_dump (tracker);
|
|
return;
|
|
}
|
|
|
|
meta_stack_tracker_dump (tracker);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_create_event (MetaStackTracker *tracker,
|
|
XCreateWindowEvent *event)
|
|
{
|
|
MetaStackOp op;
|
|
|
|
op.any.type = STACK_OP_ADD;
|
|
op.any.serial = event->serial;
|
|
op.add.window.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.add.window.x11.xwindow = event->window;
|
|
|
|
stack_tracker_event_received (tracker, &op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_destroy_event (MetaStackTracker *tracker,
|
|
XDestroyWindowEvent *event)
|
|
{
|
|
MetaStackOp op;
|
|
|
|
op.any.type = STACK_OP_REMOVE;
|
|
op.any.serial = event->serial;
|
|
op.remove.window.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.remove.window.x11.xwindow = event->window;
|
|
|
|
stack_tracker_event_received (tracker, &op);
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_reparent_event (MetaStackTracker *tracker,
|
|
XReparentEvent *event)
|
|
{
|
|
if (event->parent == event->event)
|
|
{
|
|
MetaStackOp op;
|
|
|
|
op.any.type = STACK_OP_ADD;
|
|
op.any.serial = event->serial;
|
|
op.add.window.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.add.window.x11.xwindow = event->window;
|
|
|
|
stack_tracker_event_received (tracker, &op);
|
|
}
|
|
else
|
|
{
|
|
MetaStackOp op;
|
|
|
|
op.any.type = STACK_OP_REMOVE;
|
|
op.any.serial = event->serial;
|
|
op.remove.window.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.remove.window.x11.xwindow = event->window;
|
|
|
|
stack_tracker_event_received (tracker, &op);
|
|
}
|
|
}
|
|
|
|
void
|
|
meta_stack_tracker_configure_event (MetaStackTracker *tracker,
|
|
XConfigureEvent *event)
|
|
{
|
|
MetaStackOp op;
|
|
|
|
op.any.type = STACK_OP_RAISE_ABOVE;
|
|
op.any.serial = event->serial;
|
|
op.raise_above.window.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.raise_above.window.x11.xwindow = event->window;
|
|
op.raise_above.sibling.any.type = META_WINDOW_CLIENT_TYPE_X11;
|
|
op.raise_above.sibling.x11.xwindow = event->above;
|
|
|
|
stack_tracker_event_received (tracker, &op);
|
|
}
|
|
|
|
/**
|
|
* meta_stack_tracker_get_stack:
|
|
* @tracker: a #MetaStackTracker
|
|
* @windows: location to store list of windows, or %NULL
|
|
* @n_windows: location to store count of windows, or %NULL
|
|
*
|
|
* @windows will contain the most current view we have of the stacking order
|
|
* of the children of the root window. The returned array contains
|
|
* everything: InputOnly windows, override-redirect windows,
|
|
* hidden windows, etc. Some of these will correspond to MetaWindow
|
|
* objects, others won't.
|
|
*
|
|
* Assuming that no other clients have made requests that change
|
|
* the stacking order since we last received a notification, the
|
|
* returned list of windows is exactly that you'd get as the
|
|
* children when calling XQueryTree() on the root window.
|
|
*/
|
|
void
|
|
meta_stack_tracker_get_stack (MetaStackTracker *tracker,
|
|
MetaStackWindow **windows,
|
|
int *n_windows)
|
|
{
|
|
GArray *stack;
|
|
|
|
if (tracker->unverified_predictions->length == 0)
|
|
{
|
|
stack = tracker->verified_stack;
|
|
}
|
|
else
|
|
{
|
|
if (tracker->predicted_stack == NULL)
|
|
{
|
|
GList *l;
|
|
|
|
tracker->predicted_stack = copy_stack (tracker->verified_stack);
|
|
for (l = tracker->unverified_predictions->head; l; l = l->next)
|
|
{
|
|
MetaStackOp *op = l->data;
|
|
meta_stack_op_apply (op, tracker->predicted_stack);
|
|
}
|
|
}
|
|
|
|
stack = tracker->predicted_stack;
|
|
}
|
|
|
|
meta_topic (META_DEBUG_STACK, "Get Stack\n");
|
|
meta_stack_tracker_dump (tracker);
|
|
|
|
if (windows)
|
|
*windows = (MetaStackWindow *)stack->data;
|
|
if (n_windows)
|
|
*n_windows = stack->len;
|
|
}
|
|
|
|
/**
|
|
* meta_stack_tracker_sync_stack:
|
|
* @tracker: a #MetaStackTracker
|
|
*
|
|
* Informs the compositor of the current stacking order of windows,
|
|
* based on the predicted view maintained by the #MetaStackTracker.
|
|
*/
|
|
void
|
|
meta_stack_tracker_sync_stack (MetaStackTracker *tracker)
|
|
{
|
|
MetaStackWindow *windows;
|
|
GList *meta_windows;
|
|
int n_windows;
|
|
int i;
|
|
|
|
if (tracker->sync_stack_later)
|
|
{
|
|
meta_later_remove (tracker->sync_stack_later);
|
|
tracker->sync_stack_later = 0;
|
|
}
|
|
|
|
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
|
|
|
|
meta_windows = NULL;
|
|
for (i = 0; i < n_windows; i++)
|
|
{
|
|
MetaStackWindow *window = &windows[i];
|
|
|
|
if (window->any.type == META_WINDOW_CLIENT_TYPE_X11)
|
|
{
|
|
MetaWindow *meta_window =
|
|
meta_display_lookup_x_window (tracker->screen->display, windows[i].x11.xwindow);
|
|
|
|
/* When mapping back from xwindow to MetaWindow we have to be a bit careful;
|
|
* children of the root could include unmapped windows created by toolkits
|
|
* for internal purposes, including ones that we have registered in our
|
|
* XID => window table. (Wine uses a toplevel for _NET_WM_USER_TIME_WINDOW;
|
|
* see window-prop.c:reload_net_wm_user_time_window() for registration.)
|
|
*/
|
|
if (meta_window &&
|
|
(windows[i].x11.xwindow == meta_window->xwindow ||
|
|
(meta_window->frame && windows[i].x11.xwindow == meta_window->frame->xwindow)))
|
|
meta_windows = g_list_prepend (meta_windows, meta_window);
|
|
}
|
|
else
|
|
meta_windows = g_list_prepend (meta_windows, window->wayland.meta_window);
|
|
}
|
|
|
|
if (tracker->screen->display->compositor)
|
|
meta_compositor_sync_stack (tracker->screen->display->compositor,
|
|
tracker->screen,
|
|
meta_windows);
|
|
g_list_free (meta_windows);
|
|
|
|
meta_screen_restacked (tracker->screen);
|
|
}
|
|
|
|
static gboolean
|
|
stack_tracker_sync_stack_later (gpointer data)
|
|
{
|
|
meta_stack_tracker_sync_stack (data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* meta_stack_tracker_queue_sync_stack:
|
|
* @tracker: a #MetaStackTracker
|
|
*
|
|
* Queue informing the compositor of the new stacking order before the
|
|
* next redraw. (See meta_stack_tracker_sync_stack()). This is called
|
|
* internally when the stack of X windows changes, but also needs be
|
|
* called directly when we an undecorated window is first shown or
|
|
* withdrawn since the compositor's stacking order (which contains only
|
|
* the windows that have a corresponding MetaWindow) will change without
|
|
* any change to the stacking order of the X windows, if we are creating
|
|
* or destroying MetaWindows.
|
|
*/
|
|
void
|
|
meta_stack_tracker_queue_sync_stack (MetaStackTracker *tracker)
|
|
{
|
|
if (tracker->sync_stack_later == 0)
|
|
{
|
|
tracker->sync_stack_later = meta_later_add (META_LATER_SYNC_STACK,
|
|
stack_tracker_sync_stack_later,
|
|
tracker, NULL);
|
|
}
|
|
}
|
|
|