diff --git a/src/WorkspaceManager.vala b/src/WorkspaceManager.vala
index 87d115aa9..b3ee12bda 100644
--- a/src/WorkspaceManager.vala
+++ b/src/WorkspaceManager.vala
@@ -1,19 +1,8 @@
-//
-// Copyright (C) 2014 Tom Beckmann, Rico Tzschichholz
-//
-// 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 3 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 .
-//
+/*
+ * Copyright 2014 Tom Beckmann, Rico Tzschichholz
+ * Copyright 2025 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
public class Gala.WorkspaceManager : Object {
public static void init (WindowManager wm) requires (instance == null) {
@@ -28,20 +17,16 @@ public class Gala.WorkspaceManager : Object {
public WindowManager wm { get; construct; }
- private Gee.LinkedList workspaces_marked_removed;
private int remove_freeze_count = 0;
+ private uint check_workspaces_id = 0;
private WorkspaceManager (WindowManager wm) {
Object (wm: wm);
}
construct {
- workspaces_marked_removed = new Gee.LinkedList ();
- unowned Meta.Display display = wm.get_display ();
- unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
-
- // There are some empty workspace at startup
- cleanup ();
+ unowned var display = wm.get_display ();
+ unowned var manager = display.get_workspace_manager ();
manager.override_workspace_layout (Meta.DisplayCorner.TOPLEFT, false, 1, -1);
@@ -49,26 +34,10 @@ public class Gala.WorkspaceManager : Object {
workspace_added (manager, i);
}
- manager.workspace_switched.connect_after (workspace_switched);
+ manager.workspace_switched.connect_after (queue_check_workspaces);
manager.workspace_added.connect (workspace_added);
- manager.workspace_removed.connect_after (workspace_removed);
display.window_entered_monitor.connect (window_entered_monitor);
display.window_left_monitor.connect (window_left_monitor);
-
- // make sure the last workspace has no windows on it
- if (Utils.get_n_windows (manager.get_workspace_by_index (manager.get_n_workspaces () - 1)) > 0) {
- append_workspace ();
- }
- }
-
- ~WorkspaceManager () {
- unowned Meta.Display display = wm.get_display ();
- unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
- manager.workspace_added.disconnect (workspace_added);
- manager.workspace_switched.disconnect (workspace_switched);
- manager.workspace_removed.disconnect (workspace_removed);
- display.window_entered_monitor.disconnect (window_entered_monitor);
- display.window_left_monitor.disconnect (window_left_monitor);
}
private void workspace_added (Meta.WorkspaceManager manager, int index) {
@@ -77,203 +46,128 @@ public class Gala.WorkspaceManager : Object {
return;
}
- workspace.window_added.connect (queue_window_added);
- workspace.window_removed.connect (window_removed);
+ workspace.window_added.connect (queue_check_workspaces);
+ workspace.window_removed.connect (queue_check_workspaces);
}
- private void workspace_removed (Meta.WorkspaceManager manager, int index) {
- List existing_workspaces = null;
- for (int i = 0; i < manager.get_n_workspaces (); i++) {
- existing_workspaces.append (manager.get_workspace_by_index (i));
- }
-
- var it = workspaces_marked_removed.iterator ();
- while (it.next ()) {
- var workspace = it.@get ();
-
- if (existing_workspaces.index (workspace) < 0) {
- it.remove ();
- }
+ private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) {
+ if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
+ queue_check_workspaces ();
}
}
- private void workspace_switched (Meta.WorkspaceManager manager, int from, int to, Meta.MotionDirection direction) {
- // remove empty workspaces after we switched away from them
- maybe_remove_workspace (manager.get_workspace_by_index (from), null);
+ private void window_left_monitor (Meta.Display display, int monitor, Meta.Window window) {
+ if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
+ queue_check_workspaces ();
+ }
}
- private void queue_window_added (Meta.Workspace? workspace, Meta.Window window) {
- // We get this call very early so we have to queue an idle for ShellClients
- // that might not have checked the window/got a protocol call yet
- Idle.add (() => window_added (workspace, window));
+ /**
+ * Temporarily disables removing workspaces when they are empty
+ */
+ public void freeze_remove () {
+ GLib.AtomicInt.inc (ref remove_freeze_count);
}
- private bool window_added (Meta.Workspace? workspace, Meta.Window window) {
- if (workspace == null || window.on_all_workspaces) {
- return Source.REMOVE;
- }
-
- unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager ();
- int last_workspace = manager.get_n_workspaces () - 1;
-
- if ((window.window_type == Meta.WindowType.NORMAL
- || window.window_type == Meta.WindowType.DIALOG
- || window.window_type == Meta.WindowType.MODAL_DIALOG)
- && workspace.index () == last_workspace
- ) {
- append_workspace ();
+ /**
+ * Undo the effect of freeze_remove()
+ */
+ public void thaw_remove () {
+ if (GLib.AtomicInt.dec_and_test (ref remove_freeze_count)) {
+ queue_check_workspaces ();
}
- return Source.REMOVE;
+ assert (remove_freeze_count >= 0);
}
- private void window_removed (Meta.Workspace? workspace, Meta.Window window) {
- if (workspace == null || window.on_all_workspaces) {
- return;
+ private void queue_check_workspaces () {
+ if (check_workspaces_id == 0) {
+ var laters = wm.get_display ().get_compositor ().get_laters ();
+ check_workspaces_id = laters.add (BEFORE_REDRAW, check_workspaces);
}
+ }
- if (window.window_type != Meta.WindowType.NORMAL
- && window.window_type != Meta.WindowType.DIALOG
- && window.window_type != Meta.WindowType.MODAL_DIALOG
- ) {
- return;
- }
+ private bool check_workspaces () {
+ unowned var display = wm.get_display ();
+ unowned var manager = display.get_workspace_manager ();
- // has already been removed
- if (workspace.index () < 0) {
- return;
+ if (remove_freeze_count > 0) {
+ return Source.CONTINUE;
}
- maybe_remove_workspace (workspace, window);
- }
+ bool[] empty_workspaces = new bool[manager.n_workspaces];
- private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) {
- if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
- queue_window_added (window.get_workspace (), window);
+ for (int i = 0; i < empty_workspaces.length; i++) {
+ empty_workspaces[i] = true;
}
- }
- private void window_left_monitor (Meta.Display display, int monitor, Meta.Window window) {
- if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
- window_removed (window.get_workspace (), window);
+ unowned var active_startup_sequences = display.get_startup_notification ().get_sequences ();
+ foreach (var startup_sequence in active_startup_sequences) {
+ var index = startup_sequence.get_workspace ();
+ if (index >= 0 && index < empty_workspaces.length) {
+ empty_workspaces[index] = false;
+ }
}
- }
- private void append_workspace () {
- unowned Meta.Display display = wm.get_display ();
- unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
+ unowned var window_actors = display.get_window_actors ();
+ foreach (var actor in window_actors) {
+ var win = actor.meta_window;
- manager.append_new_workspace (false, display.get_current_time ());
- }
+ if (win == null || win.on_all_workspaces || win.get_workspace () == null) {
+ continue;
+ }
- private void maybe_remove_workspace (Meta.Workspace workspace, Meta.Window? window) {
- unowned var manager = workspace.get_display ().get_workspace_manager ();
- var is_active_workspace = workspace == manager.get_active_workspace ();
- var last_workspace_index = manager.get_n_workspaces () - 1 - workspaces_marked_removed.size;
-
- // remove it right away if it was the active workspace and it's not the very last
- // or we are in modal-mode
- if ((!is_active_workspace || wm.is_modal ())
- && remove_freeze_count < 1
- && Utils.get_n_windows (workspace, true, window) == 0
- && workspace.index () != last_workspace_index
- ) {
- queue_remove_workspace (workspace);
- } else if (is_active_workspace // if window is the second last and empty, make it the last workspace
- && remove_freeze_count < 1
- && Utils.get_n_windows (workspace, true, window) == 0
- && workspace.index () == last_workspace_index - 1
- ) {
- queue_remove_workspace (manager.get_workspace_by_index (last_workspace_index));
+ empty_workspaces[win.get_workspace ().index ()] = false;
}
- }
- private void queue_remove_workspace (Meta.Workspace workspace) {
- // workspace has already been removed
- if (workspace in workspaces_marked_removed) {
- return;
+ // If we don't have an empty workspace at the end, add one
+ if (!empty_workspaces[empty_workspaces.length - 1]) {
+ manager.append_new_workspace (false, display.get_current_time ());
+ empty_workspaces += true;
}
- workspaces_marked_removed.add (workspace);
-
- // We might be here because of a signal emition from the ws machinery (e.g. workspace.window_removed).
- // Often the function emitting the signal doesn't take a ref on the ws so if we remove it right
- // away it will be freed. But because the function often accesses it after the singal emition this leads
- // to warnings and in some cases a crash.
- Idle.add (() => remove_workspace (workspace));
- }
-
- /**
- * Make sure we switch to a different workspace and remove the given one
- *
- * @param workspace The workspace to remove
- */
- private bool remove_workspace (Meta.Workspace workspace) {
- unowned Meta.Display display = workspace.get_display ();
- unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
- var time = display.get_current_time ();
- unowned Meta.Workspace active_workspace = manager.get_active_workspace ();
-
- if (workspace == active_workspace) {
- Meta.Workspace? next = null;
-
- next = workspace.get_neighbor (Meta.MotionDirection.LEFT);
- // if it's the first one we may have another one to the right
- if (next == workspace || next == null) {
- next = workspace.get_neighbor (Meta.MotionDirection.RIGHT);
- }
+ var last_index = empty_workspaces.length - 1;
- if (next != null) {
- next.activate (time);
+ int last_empty_index = 0;
+ for (int i = last_index; i >= 0; i--) {
+ if (!empty_workspaces[i]) {
+ last_empty_index = i + 1;
+ break;
}
}
- workspace.window_added.disconnect (queue_window_added);
- workspace.window_removed.disconnect (window_removed);
-
- manager.remove_workspace (workspace, time);
-
- return Source.REMOVE;
- }
+ if (!wm.is_modal ()) {
+ var active_index = manager.get_active_workspace_index ();
+ empty_workspaces[active_index] = false;
+ }
- /**
- * Temporarily disables removing workspaces when they are empty
- */
- public void freeze_remove () {
- GLib.AtomicInt.inc (ref remove_freeze_count);
- }
+ // Delete empty workspaces except for the last one; do it from the end
+ // to avoid index changes
+ for (int i = last_index; i >= 0; i--) {
+ if (!empty_workspaces[i] || i == last_empty_index) {
+ continue;
+ }
- /**
- * Undo the effect of freeze_remove()
- */
- public void thaw_remove () {
- if (GLib.AtomicInt.dec_and_test (ref remove_freeze_count)) {
- cleanup ();
- }
+ var workspace = manager.get_workspace_by_index (i);
- assert (remove_freeze_count >= 0);
- }
+ if (workspace == manager.get_active_workspace ()) {
+ Meta.Workspace? next = null;
- /**
- * If workspaces are dynamic, checks if there are empty workspaces that should
- * be removed. Particularly useful in conjunction with freeze/thaw_remove to
- * cleanup after an operation that required stable workspace/window indices
- */
- private void cleanup () {
- unowned Meta.WorkspaceManager manager = wm.get_display ().get_workspace_manager ();
+ next = workspace.get_neighbor (LEFT);
+ // If it's the first one we may have another one to the right
+ if (next == workspace || next == null) {
+ next = workspace.get_neighbor (RIGHT);
+ }
- bool remove_last = false;
- foreach (var workspace in manager.get_workspaces ().copy ()) {
- if (Utils.get_n_windows (workspace, true) != 0) {
- remove_last = false;
- continue;
+ if (next != null) {
+ next.activate (display.get_current_time ());
+ }
}
- if (workspace.active) {
- remove_last = true;
- } else if (workspace.index () != manager.n_workspaces - 1 || remove_last) {
- remove_workspace (workspace);
- }
+ manager.remove_workspace (workspace, display.get_current_time ());
}
+
+ check_workspaces_id = 0;
+ return Source.REMOVE;
}
}