/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* stack-tracker:
*
* 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 .
*/
#include "config.h"
#include "core/stack-tracker.h"
#include
#include "compositor/compositor-private.h"
#include "core/display-private.h"
#include "core/frame.h"
#include "meta/util.h"
#include "mtk/mtk-x11.h"
#include "x11/meta-x11-display-private.h"
#include "x11/window-x11.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;
typedef enum
{
APPLY_DEFAULT = 0,
/* Only do restacking that we can do locally without changing
* the order of X windows. After we've received any stack
* events from the X server, we apply the locally cached
* ops in this mode to handle the non-X parts */
NO_RESTACK_X_WINDOWS = 1 << 0,
/* If the stacking operation wouldn't change the order of X
* windows, ignore it. We use this when applying events received
* from X so that a spontaneous ConfigureNotify (for a move, say)
* doesn't change the stacking of X windows with respect to
* Wayland windows. */
IGNORE_NOOP_X_RESTACK = 1 << 1
} ApplyFlags;
/* 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;
guint64 window;
} any;
struct {
MetaStackOpType type;
gulong serial;
guint64 window;
} add;
struct {
MetaStackOpType type;
gulong serial;
guint64 window;
} remove;
struct {
MetaStackOpType type;
gulong serial;
guint64 window;
guint64 sibling;
} raise_above;
struct {
MetaStackOpType type;
gulong serial;
guint64 window;
guint64 sibling;
} lower_below;
};
struct _MetaStackTracker
{
MetaDisplay *display;
MetaStack *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 void
meta_stack_tracker_keep_override_redirect_on_top (MetaStackTracker *tracker);
static inline const char *
get_window_desc (MetaStackTracker *tracker,
guint64 window)
{
return meta_display_describe_stack_id (tracker->display, window);
}
static void
meta_stack_op_dump (MetaStackTracker *tracker,
MetaStackOp *op,
const char *prefix,
const char *suffix)
{
#ifdef WITH_VERBOSE_MODE
const char *window_desc = get_window_desc (tracker, op->any.window);
#endif
switch (op->any.type)
{
case STACK_OP_ADD:
meta_topic (META_DEBUG_STACK, "%sADD(%s; %ld)%s",
prefix, window_desc, op->any.serial, suffix);
break;
case STACK_OP_REMOVE:
meta_topic (META_DEBUG_STACK, "%sREMOVE(%s; %ld)%s",
prefix, window_desc, op->any.serial, suffix);
break;
case STACK_OP_RAISE_ABOVE:
{
meta_topic (META_DEBUG_STACK, "%sRAISE_ABOVE(%s, %s; %ld)%s",
prefix,
window_desc,
get_window_desc (tracker, op->raise_above.sibling),
op->any.serial,
suffix);
break;
}
case STACK_OP_LOWER_BELOW:
{
meta_topic (META_DEBUG_STACK, "%sLOWER_BELOW(%s, %s; %ld)%s",
prefix,
window_desc,
get_window_desc (tracker, op->lower_below.sibling),
op->any.serial,
suffix);
break;
}
}
}
#ifdef WITH_VERBOSE_MODE
static void
stack_dump (MetaStackTracker *tracker,
GArray *stack)
{
guint i;
for (i = 0; i < stack->len; i++)
{
guint64 window = g_array_index (stack, guint64, i);
meta_topic (META_DEBUG_STACK, " %s", get_window_desc (tracker, window));
}
}
#endif /* WITH_VERBOSE_MODE */
static void
meta_stack_tracker_dump (MetaStackTracker *tracker)
{
#ifdef WITH_VERBOSE_MODE
GList *l;
meta_topic (META_DEBUG_STACK, "MetaStackTracker state");
meta_topic (META_DEBUG_STACK, " xserver_serial: %ld", tracker->xserver_serial);
meta_topic (META_DEBUG_STACK, " verified_stack: ");
stack_dump (tracker, tracker->verified_stack);
meta_topic (META_DEBUG_STACK, " unverified_predictions: [");
for (l = tracker->unverified_predictions->head; l; l = l->next)
{
MetaStackOp *op = l->data;
meta_stack_op_dump (tracker, op, "", l->next ? ", " : "");
}
meta_topic (META_DEBUG_STACK, "]");
if (tracker->predicted_stack)
{
meta_topic (META_DEBUG_STACK, " predicted_stack: ");
stack_dump (tracker, tracker->predicted_stack);
}
#endif /* WITH_VERBOSE_MODE */
}
static void
meta_stack_op_free (MetaStackOp *op)
{
g_free (op);
}
static int
find_window (GArray *window_stack,
guint64 window)
{
guint i;
for (i = 0; i < window_stack->len; i++)
{
guint64 current = g_array_index (window_stack, guint64, i);
if (current == window)
return i;
}
return -1;
}
/* Returns TRUE if stack was changed */
static gboolean
move_window_above (GArray *stack,
guint64 window,
int old_pos,
int above_pos,
ApplyFlags apply_flags)
{
int i;
gboolean can_restack_this_window =
(apply_flags & NO_RESTACK_X_WINDOWS) == 0 || !META_STACK_ID_IS_X11 (window);
if (old_pos < above_pos)
{
if ((apply_flags & IGNORE_NOOP_X_RESTACK) != 0)
{
gboolean found_x_window = FALSE;
for (i = old_pos + 1; i <= above_pos; i++)
if (META_STACK_ID_IS_X11 (g_array_index (stack, guint64, i)))
found_x_window = TRUE;
if (!found_x_window)
return FALSE;
}
for (i = old_pos; i < above_pos; i++)
{
if (!can_restack_this_window &&
META_STACK_ID_IS_X11 (g_array_index (stack, guint64, i + 1)))
break;
g_array_index (stack, guint64, i) =
g_array_index (stack, guint64, i + 1);
}
g_array_index (stack, guint64, i) = window;
return i != old_pos;
}
else if (old_pos > above_pos + 1)
{
if ((apply_flags & IGNORE_NOOP_X_RESTACK) != 0)
{
gboolean found_x_window = FALSE;
for (i = above_pos + 1; i < old_pos; i++)
if (META_STACK_ID_IS_X11 (g_array_index (stack, guint64, i)))
found_x_window = TRUE;
if (!found_x_window)
return FALSE;
}
for (i = old_pos; i > above_pos + 1; i--)
{
if (!can_restack_this_window &&
META_STACK_ID_IS_X11 (g_array_index (stack, guint64, i - 1)))
break;
g_array_index (stack, guint64, i) =
g_array_index (stack, guint64, i - 1);
}
g_array_index (stack, guint64, i) = window;
return i != old_pos;
}
else
return FALSE;
}
/* Returns TRUE if stack was changed */
static gboolean
meta_stack_op_apply (MetaStackTracker *tracker,
MetaStackOp *op,
GArray *stack,
ApplyFlags apply_flags)
{
switch (op->any.type)
{
case STACK_OP_ADD:
{
int old_pos;
if (META_STACK_ID_IS_X11 (op->add.window) &&
(apply_flags & NO_RESTACK_X_WINDOWS) != 0)
return FALSE;
old_pos = find_window (stack, op->add.window);
if (old_pos >= 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_ADD: window %s already in stack",
get_window_desc (tracker, op->add.window));
return FALSE;
}
g_array_append_val (stack, op->add.window);
return TRUE;
}
case STACK_OP_REMOVE:
{
int old_pos;
if (META_STACK_ID_IS_X11 (op->remove.window) &&
(apply_flags & NO_RESTACK_X_WINDOWS) != 0)
return FALSE;
old_pos = find_window (stack, op->remove.window);
if (old_pos < 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_REMOVE: window %s not in stack",
get_window_desc (tracker, op->remove.window));
return FALSE;
}
g_array_remove_index (stack, old_pos);
return TRUE;
}
case STACK_OP_RAISE_ABOVE:
{
int old_pos;
int above_pos;
old_pos = find_window (stack, op->raise_above.window);
if (old_pos < 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_RAISE_ABOVE: window %s not in stack",
get_window_desc (tracker, op->raise_above.window));
return FALSE;
}
if (op->raise_above.sibling)
{
above_pos = find_window (stack, op->raise_above.sibling);
if (above_pos < 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_RAISE_ABOVE: sibling window %s not in stack",
get_window_desc (tracker, op->raise_above.sibling));
return FALSE;
}
}
else
{
above_pos = -1;
}
return move_window_above (stack, op->raise_above.window, old_pos, above_pos,
apply_flags);
}
case STACK_OP_LOWER_BELOW:
{
int old_pos;
int above_pos;
old_pos = find_window (stack, op->raise_above.window);
if (old_pos < 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_LOWER_BELOW: window %s not in stack",
get_window_desc (tracker, op->lower_below.window));
return FALSE;
}
if (op->lower_below.sibling)
{
int below_pos;
below_pos = find_window (stack, op->lower_below.sibling);
if (below_pos < 0)
{
meta_topic (META_DEBUG_STACK,
"STACK_OP_LOWER_BELOW: sibling window %s not in stack",
get_window_desc (tracker, op->lower_below.sibling));
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,
apply_flags);
}
}
g_assert_not_reached ();
return FALSE;
}
static GArray *
copy_stack (GArray *stack)
{
GArray *copy = g_array_sized_new (FALSE, FALSE, sizeof (guint64), stack->len);
g_array_set_size (copy, stack->len);
memcpy (copy->data, stack->data, sizeof (guint64) * stack->len);
return copy;
}
static void
query_xserver_stack (MetaDisplay *display,
MetaStackTracker *tracker)
{
MetaX11Display *x11_display = display->x11_display;
Window ignored1, ignored2;
Window *children;
guint n_children;
guint i, old_len;
tracker->xserver_serial = XNextRequest (x11_display->xdisplay);
XQueryTree (x11_display->xdisplay,
x11_display->xroot,
&ignored1, &ignored2, &children, &n_children);
old_len = tracker->verified_stack->len;
g_array_set_size (tracker->verified_stack, old_len + n_children);
for (i = 0; i < n_children; i++)
g_array_index (tracker->verified_stack, guint64, old_len + i) = children[i];
XFree (children);
}
static void
drop_x11_windows (MetaDisplay *display,
MetaStackTracker *tracker)
{
GArray *new_stack;
GList *l;
int i;
tracker->xserver_serial = 0;
new_stack = g_array_new (FALSE, FALSE, sizeof (guint64));
for (i = 0; i < tracker->verified_stack->len; i++)
{
guint64 window = g_array_index (tracker->verified_stack, guint64, i);
if (!META_STACK_ID_IS_X11 (window))
g_array_append_val (new_stack, window);
}
g_array_unref (tracker->verified_stack);
tracker->verified_stack = new_stack;
l = tracker->unverified_predictions->head;
while (l)
{
MetaStackOp *op = l->data;
GList *next = l->next;
if (META_STACK_ID_IS_X11 (op->any.window))
g_queue_remove (tracker->unverified_predictions, op);
l = next;
}
}
static void
on_stack_changed (MetaStack *stack,
MetaStackTracker *tracker)
{
MetaDisplay *display = tracker->display;
GArray *all_root_children_stacked;
GList *l;
GArray *hidden_stack_ids;
GList *sorted;
COGL_TRACE_BEGIN_SCOPED (StackChanged, "Meta::StackTracker::on_stack_changed()");
meta_topic (META_DEBUG_STACK, "Syncing window stack to server");
all_root_children_stacked = g_array_new (FALSE, FALSE, sizeof (uint64_t));
hidden_stack_ids = g_array_new (FALSE, FALSE, sizeof (uint64_t));
meta_topic (META_DEBUG_STACK, "Bottom to top: ");
sorted = meta_stack_list_windows (stack, NULL);
for (l = sorted; l; l = l->next)
{
MetaWindow *w = l->data;
uint64_t 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)
{
if (w->frame)
stack_id = w->frame->xwindow;
else
stack_id = meta_window_x11_get_xwindow (w);
}
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);
}
if (display->x11_display)
{
uint64_t guard_window_id;
/* The screen guard window sits above all hidden windows and acts as
* a barrier to input reaching these windows. */
guard_window_id = display->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",
all_root_children_stacked->len);
meta_stack_tracker_restack_managed (tracker,
(uint64_t *)all_root_children_stacked->data,
all_root_children_stacked->len);
meta_stack_tracker_restack_at_bottom (tracker,
(uint64_t *)hidden_stack_ids->data,
hidden_stack_ids->len);
g_array_free (hidden_stack_ids, TRUE);
g_array_free (all_root_children_stacked, TRUE);
g_list_free (sorted);
}
MetaStackTracker *
meta_stack_tracker_new (MetaStack *stack)
{
MetaStackTracker *tracker;
tracker = g_new0 (MetaStackTracker, 1);
tracker->display = stack->display;
tracker->stack = stack;
tracker->verified_stack = g_array_new (FALSE, FALSE, sizeof (guint64));
tracker->unverified_predictions = g_queue_new ();
g_signal_connect (tracker->display,
"x11-display-setup",
G_CALLBACK (query_xserver_stack),
tracker);
g_signal_connect (tracker->display,
"x11-display-closing",
G_CALLBACK (drop_x11_windows),
tracker);
g_signal_connect (tracker->stack, "changed",
G_CALLBACK (on_stack_changed),
tracker);
meta_stack_tracker_dump (tracker);
return tracker;
}
void
meta_stack_tracker_free (MetaStackTracker *tracker)
{
if (tracker->sync_stack_later)
{
MetaCompositor *compositor =
meta_display_get_compositor (tracker->display);
MetaLaters *laters = meta_compositor_get_laters (compositor);
meta_laters_remove (laters, tracker->sync_stack_later);
}
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_signal_handlers_disconnect_by_func (tracker->display,
(gpointer)query_xserver_stack,
tracker);
g_signal_handlers_disconnect_by_func (tracker->display,
drop_x11_windows,
tracker);
g_signal_handlers_disconnect_by_func (tracker->stack,
on_stack_changed,
tracker);
g_free (tracker);
}
static void
stack_tracker_apply_prediction (MetaStackTracker *tracker,
MetaStackOp *op)
{
gboolean free_at_end = FALSE;
/* If this operation doesn't involve restacking X windows then it's
* implicitly verified. We can apply it immediately unless there
* are outstanding X restacks that haven't yet been confirmed.
*/
if (op->any.serial == 0 &&
tracker->unverified_predictions->length == 0)
{
if (meta_stack_op_apply (tracker, op, tracker->verified_stack, APPLY_DEFAULT))
meta_stack_tracker_queue_sync_stack (tracker);
free_at_end = TRUE;
}
else
{
meta_stack_op_dump (tracker, op, "Predicting: ", "");
g_queue_push_tail (tracker->unverified_predictions, op);
}
if (!tracker->predicted_stack ||
meta_stack_op_apply (tracker, op, tracker->predicted_stack, APPLY_DEFAULT))
meta_stack_tracker_queue_sync_stack (tracker);
if (free_at_end)
meta_stack_op_free (op);
meta_stack_tracker_dump (tracker);
}
void
meta_stack_tracker_record_add (MetaStackTracker *tracker,
guint64 window,
gulong serial)
{
MetaStackOp *op = g_new0 (MetaStackOp, 1);
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,
guint64 window,
gulong serial)
{
MetaStackOp *op = g_new0 (MetaStackOp, 1);
op->any.type = STACK_OP_REMOVE;
op->any.serial = serial;
op->any.window = window;
stack_tracker_apply_prediction (tracker, op);
}
static void
meta_stack_tracker_record_raise_above (MetaStackTracker *tracker,
guint64 window,
guint64 sibling,
gulong serial)
{
MetaStackOp *op = g_new0 (MetaStackOp, 1);
op->any.type = STACK_OP_RAISE_ABOVE;
op->any.serial = serial;
op->any.window = window;
op->raise_above.sibling = sibling;
stack_tracker_apply_prediction (tracker, op);
}
static void
meta_stack_tracker_record_lower_below (MetaStackTracker *tracker,
guint64 window,
guint64 sibling,
gulong serial)
{
MetaStackOp *op = g_new0 (MetaStackOp, 1);
op->any.type = STACK_OP_LOWER_BELOW;
op->any.serial = serial;
op->any.window = window;
op->lower_below.sibling = sibling;
stack_tracker_apply_prediction (tracker, op);
}
static void
stack_tracker_event_received (MetaStackTracker *tracker,
MetaStackOp *op)
{
gboolean need_sync = FALSE;
/* If the event is older than our initial query, then it's
* already included in our tree. Just ignore it. */
if (op->any.serial < tracker->xserver_serial)
return;
meta_stack_op_dump (tracker, op, "Stack op event received: ", "");
/* First we apply any operations that we have queued up that depended
* on X operations *older* than what we received .. those operations
* must have been ignored by the X server, so we just apply the
* operations we have as best as possible while not moving windows.
*/
while (tracker->unverified_predictions->head)
{
MetaStackOp *queued_op = tracker->unverified_predictions->head->data;
if (queued_op->any.serial >= op->any.serial)
break;
meta_stack_op_apply (tracker, queued_op, tracker->verified_stack,
NO_RESTACK_X_WINDOWS);
g_queue_pop_head (tracker->unverified_predictions);
meta_stack_op_free (queued_op);
need_sync = TRUE;
}
/* Then we apply the received event. If it's a spontaneous event
* based on stacking we didn't trigger, this is the only handling. If we
* triggered it, we do the X restacking here, and then any residual
* local-only Wayland stacking below.
*/
if (meta_stack_op_apply (tracker, op, tracker->verified_stack,
IGNORE_NOOP_X_RESTACK))
need_sync = TRUE;
/* What is left to process is the prediction corresponding to the event
* (if any), and then any subsequent Wayland-only events we can just
* go ahead and do now.
*/
while (tracker->unverified_predictions->head)
{
MetaStackOp *queued_op = tracker->unverified_predictions->head->data;
if (queued_op->any.serial > op->any.serial)
break;
meta_stack_op_apply (tracker, queued_op, tracker->verified_stack,
NO_RESTACK_X_WINDOWS);
g_queue_pop_head (tracker->unverified_predictions);
meta_stack_op_free (queued_op);
need_sync = TRUE;
}
if (need_sync)
{
if (tracker->predicted_stack)
{
g_array_free (tracker->predicted_stack, TRUE);
tracker->predicted_stack = NULL;
}
meta_stack_tracker_queue_sync_stack (tracker);
}
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 = 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 = 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 = 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 = 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 = event->window;
op.raise_above.sibling = event->above;
stack_tracker_event_received (tracker, &op);
}
static gboolean
meta_stack_tracker_is_guard_window (MetaStackTracker *tracker,
uint64_t stack_id)
{
MetaX11Display *x11_display = tracker->display->x11_display;
if (!x11_display)
return FALSE;
return stack_id == x11_display->guard_window;
}
/**
* 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,
guint64 **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 (tracker, op, tracker->predicted_stack, APPLY_DEFAULT);
}
}
stack = tracker->predicted_stack;
}
if (windows)
*windows = (guint64 *)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)
{
guint64 *windows;
GList *meta_windows;
int n_windows;
int i;
if (tracker->sync_stack_later)
{
MetaLaters *laters;
laters = meta_compositor_get_laters (tracker->display->compositor);
meta_laters_remove (laters, tracker->sync_stack_later);
tracker->sync_stack_later = 0;
}
meta_stack_tracker_keep_override_redirect_on_top (tracker);
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
meta_windows = NULL;
for (i = 0; i < n_windows; i++)
{
guint64 window = windows[i];
if (META_STACK_ID_IS_X11 (window))
{
MetaX11Display *x11_display = tracker->display->x11_display;
MetaWindow *meta_window = NULL;
if (x11_display)
meta_window = meta_x11_display_lookup_x_window (x11_display, (Window) window);
/* 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 &&
((Window)window == meta_window_x11_get_xwindow (meta_window) ||
(meta_window->frame && (Window)window == meta_window->frame->xwindow)))
meta_windows = g_list_prepend (meta_windows, meta_window);
}
else
meta_windows = g_list_prepend (meta_windows,
meta_display_lookup_stamp (tracker->display, window));
}
meta_compositor_sync_stack (tracker->display->compositor,
meta_windows);
g_list_free (meta_windows);
meta_display_restacked (tracker->display);
}
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)
{
MetaLaters *laters;
if (tracker->sync_stack_later != 0)
return;
laters = meta_compositor_get_laters (tracker->display->compositor);
tracker->sync_stack_later = meta_laters_add (laters, META_LATER_SYNC_STACK,
stack_tracker_sync_stack_later,
tracker, NULL);
}
/* When moving an X window we sometimes need an X based sibling.
*
* If the given sibling is X based this function returns it back
* otherwise it searches downwards looking for the nearest X window.
*
* If no X based sibling could be found return NULL. */
static Window
find_x11_sibling_downwards (MetaStackTracker *tracker,
guint64 sibling)
{
guint64 *windows;
int n_windows;
int i;
if (META_STACK_ID_IS_X11 (sibling))
return (Window)sibling;
meta_stack_tracker_get_stack (tracker,
&windows, &n_windows);
/* NB: Children are in order from bottom to top and we
* want to search downwards for the nearest X window.
*/
for (i = n_windows - 1; i >= 0; i--)
if (windows[i] == sibling)
break;
for (; i >= 0; i--)
{
if (META_STACK_ID_IS_X11 (windows[i]))
return (Window)windows[i];
}
return None;
}
static Window
find_x11_sibling_upwards (MetaStackTracker *tracker,
guint64 sibling)
{
guint64 *windows;
int n_windows;
int i;
if (META_STACK_ID_IS_X11 (sibling))
return (Window)sibling;
meta_stack_tracker_get_stack (tracker,
&windows, &n_windows);
for (i = 0; i < n_windows; i++)
if (windows[i] == sibling)
break;
for (; i < n_windows; i++)
{
if (META_STACK_ID_IS_X11 (windows[i]))
return (Window)windows[i];
}
return None;
}
static void
meta_stack_tracker_lower_below (MetaStackTracker *tracker,
guint64 window,
guint64 sibling)
{
gulong serial = 0;
MetaX11Display *x11_display = tracker->display->x11_display;
if (META_STACK_ID_IS_X11 (window))
{
XWindowChanges changes;
changes.sibling = sibling ? find_x11_sibling_upwards (tracker, sibling) : None;
if (changes.sibling != find_x11_sibling_upwards (tracker, window))
{
serial = XNextRequest (x11_display->xdisplay);
mtk_x11_error_trap_push (x11_display->xdisplay);
changes.stack_mode = changes.sibling ? Below : Above;
XConfigureWindow (x11_display->xdisplay,
window,
(changes.sibling ? CWSibling : 0) | CWStackMode,
&changes);
mtk_x11_error_trap_pop (x11_display->xdisplay);
}
}
meta_stack_tracker_record_lower_below (tracker,
window, sibling,
serial);
}
static void
meta_stack_tracker_raise_above (MetaStackTracker *tracker,
guint64 window,
guint64 sibling)
{
gulong serial = 0;
MetaX11Display *x11_display = tracker->display->x11_display;
if (META_STACK_ID_IS_X11 (window))
{
XWindowChanges changes;
changes.sibling = sibling ? find_x11_sibling_downwards (tracker, sibling) : None;
if (changes.sibling != find_x11_sibling_downwards (tracker, window))
{
serial = XNextRequest (x11_display->xdisplay);
mtk_x11_error_trap_push (x11_display->xdisplay);
changes.stack_mode = changes.sibling ? Above : Below;
XConfigureWindow (x11_display->xdisplay,
(Window)window,
(changes.sibling ? CWSibling : 0) | CWStackMode,
&changes);
mtk_x11_error_trap_pop (x11_display->xdisplay);
}
}
meta_stack_tracker_record_raise_above (tracker, window,
sibling, serial);
}
void
meta_stack_tracker_lower (MetaStackTracker *tracker,
guint64 window)
{
meta_stack_tracker_raise_above (tracker, window, None);
}
static void
meta_stack_tracker_keep_override_redirect_on_top (MetaStackTracker *tracker)
{
guint64 *stack;
int n_windows, i;
int topmost_non_or;
meta_stack_tracker_get_stack (tracker, &stack, &n_windows);
for (i = n_windows - 1; i >= 0; i--)
{
MetaWindow *window;
window = meta_display_lookup_stack_id (tracker->display, stack[i]);
if (window && window->layer != META_LAYER_OVERRIDE_REDIRECT)
break;
}
topmost_non_or = i;
for (i -= 1; i >= 0; i--)
{
MetaWindow *window;
if (meta_stack_tracker_is_guard_window (tracker, stack[i]))
break;
window = meta_display_lookup_stack_id (tracker->display, stack[i]);
if (window && window->layer == META_LAYER_OVERRIDE_REDIRECT)
{
meta_stack_tracker_raise_above (tracker, stack[i], stack[topmost_non_or]);
meta_stack_tracker_get_stack (tracker, &stack, &n_windows);
topmost_non_or -= 1;
}
}
}
void
meta_stack_tracker_restack_managed (MetaStackTracker *tracker,
const guint64 *managed,
int n_managed)
{
guint64 *windows;
int n_windows;
int old_pos, new_pos;
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackManaged,
"Meta::StackTracker::restack_managed()");
if (n_managed == 0)
return;
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackManagedGet,
"Meta::StackTracker::restack_managed#get()");
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
/* If the top window has to be restacked, we don't want to move it to the very
* top of the stack, since apps expect override-redirect windows to stay near
* the top of the X stack; we instead move it above all managed windows (or
* above the guard window if there are no non-hidden managed windows.)
*/
old_pos = n_windows - 1;
for (old_pos = n_windows - 1; old_pos >= 0; old_pos--)
{
MetaWindow *old_window = meta_display_lookup_stack_id (tracker->display, windows[old_pos]);
if ((old_window && !old_window->override_redirect && !old_window->unmanaging) ||
meta_stack_tracker_is_guard_window (tracker, windows[old_pos]))
break;
}
g_assert (old_pos >= 0);
COGL_TRACE_END (StackTrackerRestackManagedGet);
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackManagedRaise,
"Meta::StackTracker::restack_managed#raise()");
new_pos = n_managed - 1;
if (managed[new_pos] != windows[old_pos])
{
/* Move the first managed window in the new stack above all managed windows */
meta_stack_tracker_raise_above (tracker, managed[new_pos], windows[old_pos]);
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
/* Moving managed[new_pos] above windows[old_pos], moves the window at old_pos down by one */
}
COGL_TRACE_END (StackTrackerRestackManagedRaise);
old_pos--;
new_pos--;
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackManagedRestack,
"Meta::StackTracker::restack_managed#restack()");
while (old_pos >= 0 && new_pos >= 0)
{
if (meta_stack_tracker_is_guard_window (tracker, windows[old_pos]))
break;
if (windows[old_pos] == managed[new_pos])
{
old_pos--;
new_pos--;
continue;
}
MetaWindow *old_window = meta_display_lookup_stack_id (tracker->display, windows[old_pos]);
if (!old_window || old_window->override_redirect || old_window->unmanaging)
{
old_pos--;
continue;
}
meta_stack_tracker_lower_below (tracker, managed[new_pos], managed[new_pos + 1]);
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
/* Moving managed[new_pos] above windows[old_pos] moves the window at old_pos down by one,
* we'll examine it again to see if it matches the next new window */
old_pos--;
new_pos--;
}
COGL_TRACE_END (StackTrackerRestackManagedRestack);
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackManagedLower,
"Meta::StackTracker::restack_managed#lower()");
while (new_pos > 0)
{
meta_stack_tracker_lower_below (tracker, managed[new_pos], managed[new_pos - 1]);
new_pos--;
}
COGL_TRACE_END (StackTrackerRestackManagedLower);
}
void
meta_stack_tracker_restack_at_bottom (MetaStackTracker *tracker,
const guint64 *new_order,
int n_new_order)
{
guint64 *windows;
int n_windows;
int pos;
COGL_TRACE_BEGIN_SCOPED (StackTrackerRestackAtBottom,
"Meta::StackTracker::restack_at_bottom()");
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
for (pos = 0; pos < n_new_order; pos++)
{
if (pos >= n_windows || windows[pos] != new_order[pos])
{
if (pos == 0)
meta_stack_tracker_lower (tracker, new_order[pos]);
else
meta_stack_tracker_raise_above (tracker, new_order[pos], new_order[pos - 1]);
meta_stack_tracker_get_stack (tracker, &windows, &n_windows);
}
}
}