workspace: Rewrite workspace management code

The existing workspace management code is quite hairy, with plenty of
logic inline in all of window.c, workspace.c, and screen.c, making it
hard to understand or make changes to, since you might forget to change
several of the other places the code was around.

Rewrite the internal workspace management logic so that it's
centralized and all in window.c. Document the invariants we need to
maintain, and ensure that these invariants are properly kept, with
asserts in various places.

Extensive testing on gnome-shell did not bring up any issues, and this
is a considerable cleanup.
This commit is contained in:
Jasper St. Pierre 2014-08-16 13:22:05 -04:00
parent 19795c1681
commit 527c53a2a0
4 changed files with 176 additions and 281 deletions

View File

@ -625,7 +625,7 @@ void meta_window_set_user_time (MetaWindow *window,
guint32 timestamp);
void meta_window_update_for_monitors_changed (MetaWindow *window);
void meta_window_update_on_all_workspaces (MetaWindow *window);
void meta_window_on_all_workspaces_changed (MetaWindow *window);
gboolean meta_window_should_attach_to_parent (MetaWindow *window);
gboolean meta_window_can_tile_side_by_side (MetaWindow *window);

View File

@ -88,7 +88,6 @@ static void meta_window_save_rect (MetaWindow *window);
static void ensure_mru_position_after (MetaWindow *window,
MetaWindow *after_this_one);
static void meta_window_move_resize_now (MetaWindow *window);
static void meta_window_unqueue (MetaWindow *window, guint queuebits);
@ -123,6 +122,10 @@ static void meta_window_propagate_focus_appearance (MetaWindow *window,
static void meta_window_update_icon_now (MetaWindow *window,
gboolean force);
static void set_workspace_state (MetaWindow *window,
gboolean on_all_workspaces,
MetaWorkspace *workspace);
/* Idle handlers for the three queues (run with meta_later_add()). The
* "data" parameter in each case will be a GINT_TO_POINTER of the
* index into the queue arrays to use.
@ -192,8 +195,7 @@ prefs_changed_callback (MetaPreference pref,
if (pref == META_PREF_WORKSPACES_ONLY_ON_PRIMARY)
{
meta_window_update_on_all_workspaces (window);
meta_window_queue (window, META_QUEUE_CALC_SHOWING);
meta_window_on_all_workspaces_changed (window);
}
else if (pref == META_PREF_ATTACH_MODAL_DIALOGS &&
window->type == META_WINDOW_MODAL_DIALOG)
@ -576,8 +578,7 @@ meta_window_class_init (MetaWindowClass *klass)
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_INT);
G_TYPE_NONE, 0);
window_signals[FOCUS] =
g_signal_new ("focus",
@ -766,7 +767,6 @@ _meta_window_shared_new (MetaDisplay *display,
XWindowAttributes *attrs)
{
MetaWindow *window;
MetaWorkspace *space;
g_assert (attrs != NULL);
@ -1053,6 +1053,9 @@ _meta_window_shared_new (MetaDisplay *display,
if (window->initial_workspace_set)
{
gboolean on_all_workspaces;
MetaWorkspace *workspace;
if (window->initial_workspace == (int) 0xFFFFFFFF)
{
meta_topic (META_DEBUG_PLACEMENT,
@ -1063,8 +1066,9 @@ _meta_window_shared_new (MetaDisplay *display,
* added to all the MRU lists
*/
window->on_all_workspaces_requested = TRUE;
window->on_all_workspaces = TRUE;
meta_workspace_add_window (window->screen->active_workspace, window);
on_all_workspaces = TRUE;
workspace = NULL;
}
else
{
@ -1072,13 +1076,12 @@ _meta_window_shared_new (MetaDisplay *display,
"Window %s is initially on space %d\n",
window->desc, window->initial_workspace);
space =
meta_screen_get_workspace_by_index (window->screen,
on_all_workspaces = FALSE;
workspace = meta_screen_get_workspace_by_index (window->screen,
window->initial_workspace);
if (space)
meta_workspace_add_window (space, window);
}
set_workspace_state (window, on_all_workspaces, workspace);
}
/* override-redirect windows are subtly different from other windows
@ -1089,26 +1092,15 @@ _meta_window_shared_new (MetaDisplay *display,
*/
if (!window->override_redirect)
{
if (window->workspace == NULL &&
window->transient_for != NULL)
{
/* Try putting dialog on parent's workspace */
if (window->transient_for->workspace)
if (window->workspace == NULL && window->transient_for != NULL)
{
meta_topic (META_DEBUG_PLACEMENT,
"Putting window %s on same workspace as parent %s\n",
window->desc, window->transient_for->desc);
if (window->transient_for->on_all_workspaces_requested)
{
window->on_all_workspaces_requested = TRUE;
window->on_all_workspaces = TRUE;
}
/* this will implicitly add to the appropriate MRU lists
*/
meta_workspace_add_window (window->transient_for->workspace, window);
}
set_workspace_state (window,
window->transient_for->on_all_workspaces_requested,
window->transient_for->workspace);
}
if (window->workspace == NULL)
@ -1117,14 +1109,9 @@ _meta_window_shared_new (MetaDisplay *display,
"Putting window %s on active workspace\n",
window->desc);
space = window->screen->active_workspace;
meta_workspace_add_window (space, window);
set_workspace_state (window, FALSE, window->screen->active_workspace);
}
/* for the various on_all_workspaces = TRUE possible above */
meta_window_current_workspace_changed (window);
meta_window_update_struts (window);
}
@ -1273,7 +1260,7 @@ meta_window_unmanage (MetaWindow *window,
* not confuse other window managers that may take over
*/
if (window->screen->closing && meta_prefs_get_workspaces_only_on_primary ())
meta_window_update_on_all_workspaces (window);
meta_window_on_all_workspaces_changed (window);
if (window->fullscreen)
{
@ -1352,8 +1339,7 @@ meta_window_unmanage (MetaWindow *window,
META_QUEUE_UPDATE_ICON);
meta_window_free_delete_dialog (window);
if (window->workspace)
meta_workspace_remove_window (window->workspace, window);
set_workspace_state (window, FALSE, NULL);
g_assert (window->workspace == NULL);
@ -1399,62 +1385,6 @@ meta_window_unmanage (MetaWindow *window,
g_object_unref (window);
}
static gboolean
should_be_on_all_workspaces (MetaWindow *window)
{
return
window->on_all_workspaces_requested ||
window->override_redirect ||
(meta_prefs_get_workspaces_only_on_primary () &&
!window->unmanaging &&
!meta_window_is_on_primary_monitor (window));
}
void
meta_window_update_on_all_workspaces (MetaWindow *window)
{
gboolean old_value;
old_value = window->on_all_workspaces;
window->on_all_workspaces = should_be_on_all_workspaces (window);
if (window->on_all_workspaces != old_value &&
!window->override_redirect)
{
if (window->on_all_workspaces)
{
GList* tmp = window->screen->workspaces;
/* Add to all MRU lists */
while (tmp)
{
MetaWorkspace* work = (MetaWorkspace*) tmp->data;
if (!g_list_find (work->mru_list, window))
work->mru_list = g_list_prepend (work->mru_list, window);
tmp = tmp->next;
}
}
else
{
GList* tmp = window->screen->workspaces;
/* Remove from MRU lists except the window's workspace */
while (tmp)
{
MetaWorkspace* work = (MetaWorkspace*) tmp->data;
if (work != window->workspace)
work->mru_list = g_list_remove (work->mru_list, window);
tmp = tmp->next;
}
}
meta_window_current_workspace_changed (window);
g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_ON_ALL_WORKSPACES]);
}
}
static void
set_wm_state (MetaWindow *window)
{
@ -1576,27 +1506,10 @@ meta_window_showing_on_its_workspace (MetaWindow *window)
gboolean
meta_window_should_be_showing (MetaWindow *window)
{
gboolean on_workspace;
meta_verbose ("Should be showing for window %s\n", window->desc);
/* See if we're on the workspace */
on_workspace = meta_window_located_on_workspace (window,
window->screen->active_workspace);
if (!on_workspace)
meta_verbose ("Window %s is not on workspace %d\n",
window->desc,
meta_workspace_index (window->screen->active_workspace));
else
meta_verbose ("Window %s is on the active workspace %d\n",
window->desc,
meta_workspace_index (window->screen->active_workspace));
if (window->on_all_workspaces)
meta_verbose ("Window %s is on all workspaces\n", window->desc);
return on_workspace && meta_window_showing_on_its_workspace (window);
/* Windows should be showing if they're located on the
* active workspace and they're showing on their own workspace. */
return (meta_window_located_on_workspace (window, window->screen->active_workspace) &&
meta_window_showing_on_its_workspace (window));
}
static void
@ -3461,6 +3374,7 @@ meta_window_activate_full (MetaWindow *window,
meta_topic (META_DEBUG_FOCUS,
"Focusing window %s due to activation\n",
window->desc);
if (meta_window_located_on_workspace (window, workspace))
meta_window_focus (window, timestamp);
else
@ -3617,7 +3531,7 @@ meta_window_update_monitor (MetaWindow *window,
window->monitor = meta_screen_get_monitor_for_window (window->screen, window);
if (old != window->monitor)
{
meta_window_update_on_all_workspaces (window);
meta_window_on_all_workspaces_changed (window);
/* If workspaces only on primary and we moved back to primary due to a user action,
* ensure that the window is now in that workspace. We do this because while
@ -4339,10 +4253,8 @@ meta_window_focus (MetaWindow *window,
meta_topic (META_DEBUG_FOCUS,
"%s has %s as a modal transient, so focusing it instead.\n",
window->desc, modal_transient->desc);
if (!modal_transient->on_all_workspaces &&
modal_transient->workspace != window->screen->active_workspace)
meta_window_change_workspace (modal_transient,
window->screen->active_workspace);
if (!meta_window_located_on_workspace (modal_transient, window->screen->active_workspace))
meta_window_change_workspace (modal_transient, window->screen->active_workspace);
window = modal_transient;
}
@ -4364,35 +4276,130 @@ meta_window_focus (MetaWindow *window,
/* meta_effect_run_focus(window, NULL, NULL); */
}
/* Workspace management. Invariants:
*
* - window->workspace describes the workspace the window is on.
*
* - workspace->windows is a list of windows that is located on
* that workspace.
*
* - If the window is on_all_workspaces, then then
* window->workspace == NULL, but workspace->windows contains
* the window.
*/
static void
set_workspace_state (MetaWindow *window,
gboolean on_all_workspaces,
MetaWorkspace *workspace)
{
/* If we're on all workspaces, then our new workspace must be NULL. */
if (on_all_workspaces)
g_assert (workspace == NULL);
/* If this is an override-redirect window, ensure that the only
* times we're setting the workspace state is either during construction
* to mark as on_all_workspaces, or when unmanaging to remove all the
* workspaces. */
if (window->override_redirect)
g_return_if_fail ((window->constructing && on_all_workspaces) || window->unmanaging);
if (on_all_workspaces == window->on_all_workspaces &&
workspace == window->workspace)
return;
if (window->workspace)
meta_workspace_remove_window (window->workspace, window);
else if (window->on_all_workspaces)
{
GList *l;
for (l = window->screen->workspaces; l != NULL; l = l->next)
{
MetaWorkspace *workspace = l->data;
meta_workspace_remove_window (workspace, window);
}
}
window->on_all_workspaces = on_all_workspaces;
window->workspace = workspace;
if (window->workspace)
meta_workspace_add_window (window->workspace, window);
else if (window->on_all_workspaces)
{
GList *l;
for (l = window->screen->workspaces; l != NULL; l = l->next)
{
MetaWorkspace *workspace = l->data;
meta_workspace_add_window (workspace, window);
}
}
/* queue a move_resize since changing workspaces may change
* the relevant struts
*/
if (!window->override_redirect)
meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
meta_window_queue (window, META_QUEUE_CALC_SHOWING);
meta_window_current_workspace_changed (window);
g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_ON_ALL_WORKSPACES]);
g_signal_emit (window, window_signals[WORKSPACE_CHANGED], 0);
}
static gboolean
should_be_on_all_workspaces (MetaWindow *window)
{
return
window->always_sticky ||
window->on_all_workspaces_requested ||
window->override_redirect ||
(meta_prefs_get_workspaces_only_on_primary () &&
!window->unmanaging &&
!meta_window_is_on_primary_monitor (window));
}
void
meta_window_on_all_workspaces_changed (MetaWindow *window)
{
gboolean on_all_workspaces = should_be_on_all_workspaces (window);
if (window->on_all_workspaces == on_all_workspaces)
return;
MetaWorkspace *workspace;
if (on_all_workspaces)
{
workspace = NULL;
}
else
{
/* We're coming out of the sticky state. Put the window on
* the currently active workspace. */
workspace = window->screen->active_workspace;
}
set_workspace_state (window, on_all_workspaces, workspace);
}
static void
meta_window_change_workspace_without_transients (MetaWindow *window,
MetaWorkspace *workspace)
{
int old_workspace = -1;
meta_verbose ("Changing window %s to workspace %d\n",
window->desc, meta_workspace_index (workspace));
if (!window->on_all_workspaces_requested)
{
old_workspace = meta_workspace_index (window->workspace);
}
/* unstick if stuck. meta_window_unstick would call
* meta_window_change_workspace recursively if the window
* is not in the active workspace.
*/
/* Try to unstick the window if it's stuck. This doesn't
* have any guarantee that we'll actually unstick the
* window, since it could be stuck for other reasons. */
if (window->on_all_workspaces_requested)
meta_window_unstick (window);
/* See if we're already on this space. If not, make sure we are */
if (window->workspace != workspace)
{
meta_workspace_remove_window (window->workspace, window);
meta_workspace_add_window (workspace, window);
g_signal_emit (window, window_signals[WORKSPACE_CHANGED], 0,
old_workspace);
}
/* We failed to unstick the window. */
if (window->on_all_workspaces)
return;
if (window->workspace == workspace)
return;
set_workspace_state (window, FALSE, workspace);
}
static gboolean
@ -4409,9 +4416,6 @@ meta_window_change_workspace (MetaWindow *window,
{
g_return_if_fail (!window->override_redirect);
if (window->always_sticky)
return;
meta_window_change_workspace_without_transients (window, workspace);
meta_window_foreach_transient (window, change_workspace_foreach,
@ -4434,10 +4438,7 @@ window_stick_impl (MetaWindow *window)
* toggled back off.
*/
window->on_all_workspaces_requested = TRUE;
meta_window_frame_size_changed (window);
meta_window_update_on_all_workspaces (window);
meta_window_queue(window, META_QUEUE_CALC_SHOWING);
meta_window_on_all_workspaces_changed (window);
}
static void
@ -4449,18 +4450,7 @@ window_unstick_impl (MetaWindow *window)
/* Revert to window->workspaces */
window->on_all_workspaces_requested = FALSE;
meta_window_frame_size_changed (window);
meta_window_update_on_all_workspaces (window);
/* We change ourselves to the active workspace, since otherwise you'd get
* a weird window-vaporization effect. Once we have UI for being
* on more than one workspace this should probably be add_workspace
* not change_workspace.
*/
if (window->screen->active_workspace != window->workspace)
meta_window_change_workspace (window, window->screen->active_workspace);
meta_window_queue(window, META_QUEUE_CALC_SHOWING);
meta_window_on_all_workspaces_changed (window);
}
static gboolean
@ -5002,8 +4992,10 @@ meta_window_get_workspaces (MetaWindow *window)
return window->screen->workspaces;
else if (window->workspace != NULL)
return window->workspace->list_containing_self;
else
else if (window->constructing)
return NULL;
else
g_assert_not_reached ();
}
static void
@ -6697,7 +6689,7 @@ meta_window_set_demands_attention (MetaWindow *window)
if (window->wm_state_demands_attention)
return;
if (workspace!=window->workspace)
if (!meta_window_located_on_workspace (window, workspace))
{
/* windows on other workspaces are necessarily obscured */
obscured = TRUE;
@ -6894,7 +6886,7 @@ meta_window_get_workspace (MetaWindow *window)
{
if (window->on_all_workspaces)
return window->screen->active_workspace;
else
return window->workspace;
}

View File

@ -166,16 +166,6 @@ meta_workspace_init (MetaWorkspace *workspace)
{
}
static void
maybe_add_to_list (MetaWindow *window,
gpointer data)
{
GList **mru_list = data;
if (window->on_all_workspaces)
*mru_list = g_list_prepend (*mru_list, window);
}
MetaWorkspace*
meta_workspace_new (MetaScreen *screen)
{
@ -188,7 +178,6 @@ meta_workspace_new (MetaScreen *screen)
g_list_append (workspace->screen->workspaces, workspace);
workspace->windows = NULL;
workspace->mru_list = NULL;
meta_screen_foreach_window (screen, META_LIST_DEFAULT, maybe_add_to_list, &workspace->mru_list);
workspace->work_areas_invalid = TRUE;
workspace->work_area_monitor = NULL;
@ -260,19 +249,6 @@ meta_workspace_remove (MetaWorkspace *workspace)
g_return_if_fail (workspace != workspace->screen->active_workspace);
/* Here we assume all the windows are already on another workspace
* as well, so they won't be "orphaned"
*/
while (workspace->windows != NULL)
{
MetaWindow *window = workspace->windows->data;
/* pop front of list we're iterating over */
meta_workspace_remove_window (workspace, window);
g_assert (window->workspace != NULL);
}
g_assert (workspace->windows == NULL);
screen = workspace->screen;
@ -316,36 +292,11 @@ void
meta_workspace_add_window (MetaWorkspace *workspace,
MetaWindow *window)
{
g_return_if_fail (window->workspace == NULL);
/* If the window is on all workspaces, we want to add it to all mru
* lists, otherwise just add it to this workspaces mru list
*/
if (window->on_all_workspaces)
{
if (window->workspace == NULL)
{
GList *l;
for (l = window->screen->workspaces; l != NULL; l = l->next)
{
MetaWorkspace* work = (MetaWorkspace*) l->data;
if (!g_list_find (work->mru_list, window))
work->mru_list = g_list_prepend (work->mru_list, window);
}
}
}
else
{
g_assert (g_list_find (workspace->mru_list, window) == NULL);
workspace->mru_list = g_list_prepend (workspace->mru_list, window);
}
workspace->windows = g_list_prepend (workspace->windows, window);
window->workspace = workspace;
meta_window_current_workspace_changed (window);
if (window->struts)
{
meta_topic (META_DEBUG_WORKAREA,
@ -354,11 +305,6 @@ meta_workspace_add_window (MetaWorkspace *workspace,
meta_workspace_invalidate_work_area (workspace);
}
/* queue a move_resize since changing workspaces may change
* the relevant struts
*/
meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
g_signal_emit (workspace, signals[WINDOW_ADDED], 0, window);
g_object_notify_by_pspec (G_OBJECT (workspace), obj_props[PROP_N_WINDOWS]);
}
@ -367,31 +313,10 @@ void
meta_workspace_remove_window (MetaWorkspace *workspace,
MetaWindow *window)
{
g_return_if_fail (window->workspace == workspace);
workspace->windows = g_list_remove (workspace->windows, window);
window->workspace = NULL;
/* If the window is on all workspaces, we don't want to remove it
* from the MRU list unless this causes it to be removed from all
* workspaces
*/
if (window->on_all_workspaces)
{
GList *l;
for (l = window->screen->workspaces; l != NULL; l = l->next)
{
MetaWorkspace* work = (MetaWorkspace*) l->data;
work->mru_list = g_list_remove (work->mru_list, window);
}
}
else
{
workspace->mru_list = g_list_remove (workspace->mru_list, window);
g_assert (g_list_find (workspace->mru_list, window) == NULL);
}
meta_window_current_workspace_changed (window);
if (window->struts)
{
@ -401,11 +326,6 @@ meta_workspace_remove_window (MetaWorkspace *workspace,
meta_workspace_invalidate_work_area (workspace);
}
/* queue a move_resize since changing workspaces may change
* the relevant struts
*/
meta_window_queue (window, META_QUEUE_CALC_SHOWING|META_QUEUE_MOVE_RESIZE);
g_signal_emit (workspace, signals[WINDOW_REMOVED], 0, window);
g_object_notify (G_OBJECT (workspace), "n-windows");
}
@ -424,8 +344,7 @@ meta_workspace_relocate_windows (MetaWorkspace *workspace,
for (l = copy; l != NULL; l = l->next)
{
MetaWindow *window = l->data;
meta_workspace_remove_window (workspace, window);
meta_workspace_add_window (new_home, window);
meta_window_change_workspace (window, new_home);
}
g_list_free (copy);
@ -574,9 +493,6 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace,
if (move_window != NULL)
{
if (move_window->on_all_workspaces)
move_window = NULL; /* don't move it after all */
/* We put the window on the new workspace, flip spaces,
* then remove from old workspace, so the window
* never gets unmapped and we maintain the button grab
@ -584,21 +500,13 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace,
*
* \bug This comment appears to be the reverse of what happens
*/
if (move_window && (move_window->workspace != workspace))
{
meta_workspace_remove_window (old, move_window);
meta_workspace_add_window (workspace, move_window);
}
if (!meta_window_located_on_workspace (move_window, workspace))
meta_window_change_workspace (move_window, workspace);
}
meta_workspace_queue_calc_showing (old);
meta_workspace_queue_calc_showing (workspace);
/* FIXME: Why do we need this?!? Isn't it handled in the lines above? */
if (move_window)
/* Removes window from other spaces */
meta_window_change_workspace (move_window, workspace);
/*
* Notify the compositor that the active workspace is changing.
*/

View File

@ -422,7 +422,7 @@ meta_window_apply_session_info (MetaWindow *window,
if (info->on_all_workspaces_set)
{
window->on_all_workspaces_requested = info->on_all_workspaces;
meta_window_update_on_all_workspaces (window);
meta_window_on_all_workspaces_changed (window);
meta_topic (META_DEBUG_SM,
"Restoring sticky state %d for window %s\n",
window->on_all_workspaces_requested, window->desc);
@ -452,24 +452,19 @@ meta_window_apply_session_info (MetaWindow *window,
if (spaces)
{
/* This briefly breaks the invariant that we are supposed
* to always be on some workspace. But we paranoically
* ensured that one of the workspaces from the session was
* indeed valid, so we know we'll go right back to one.
/* XXX: What should we do if there's more than one workspace
* listed? We only support one workspace for each window.
*
* For now, just choose the first one.
*/
if (window->workspace)
meta_workspace_remove_window (window->workspace, window);
MetaWorkspace *workspace = spaces->data;
/* Only restore to the first workspace if the window
* happened to be on more than one, since we have replaces
* window->workspaces with window->workspace
*/
meta_workspace_add_window (spaces->data, window);
meta_window_change_workspace (window, workspace);
meta_topic (META_DEBUG_SM,
"Restoring saved window %s to workspace %d\n",
window->desc,
meta_workspace_index (spaces->data));
meta_workspace_index (workspace));
g_slist_free (spaces);
}