Skip to content

Commit 540ef8c

Browse files
authored
Rewrite WorkspaceManager (#2326)
1 parent 750cb32 commit 540ef8c

File tree

1 file changed

+91
-197
lines changed

1 file changed

+91
-197
lines changed

src/WorkspaceManager.vala

Lines changed: 91 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
//
2-
// Copyright (C) 2014 Tom Beckmann, Rico Tzschichholz
3-
//
4-
// This program is free software: you can redistribute it and/or modify
5-
// it under the terms of the GNU General Public License as published by
6-
// the Free Software Foundation, either version 3 of the License, or
7-
// (at your option) any later version.
8-
//
9-
// This program is distributed in the hope that it will be useful,
10-
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11-
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12-
// GNU General Public License for more details.
13-
//
14-
// You should have received a copy of the GNU General Public License
15-
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16-
//
1+
/*
2+
* Copyright 2014 Tom Beckmann, Rico Tzschichholz
3+
* Copyright 2025 elementary, Inc. (https://elementary.io)
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
176

187
public class Gala.WorkspaceManager : Object {
198
public static void init (WindowManager wm) requires (instance == null) {
@@ -28,47 +17,27 @@ public class Gala.WorkspaceManager : Object {
2817

2918
public WindowManager wm { get; construct; }
3019

31-
private Gee.LinkedList<Meta.Workspace> workspaces_marked_removed;
3220
private int remove_freeze_count = 0;
21+
private uint check_workspaces_id = 0;
3322

3423
private WorkspaceManager (WindowManager wm) {
3524
Object (wm: wm);
3625
}
3726

3827
construct {
39-
workspaces_marked_removed = new Gee.LinkedList<Meta.Workspace> ();
40-
unowned Meta.Display display = wm.get_display ();
41-
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
42-
43-
// There are some empty workspace at startup
44-
cleanup ();
28+
unowned var display = wm.get_display ();
29+
unowned var manager = display.get_workspace_manager ();
4530

4631
manager.override_workspace_layout (Meta.DisplayCorner.TOPLEFT, false, 1, -1);
4732

4833
for (var i = 0; i < manager.get_n_workspaces (); i++) {
4934
workspace_added (manager, i);
5035
}
5136

52-
manager.workspace_switched.connect_after (workspace_switched);
37+
manager.workspace_switched.connect_after (queue_check_workspaces);
5338
manager.workspace_added.connect (workspace_added);
54-
manager.workspace_removed.connect_after (workspace_removed);
5539
display.window_entered_monitor.connect (window_entered_monitor);
5640
display.window_left_monitor.connect (window_left_monitor);
57-
58-
// make sure the last workspace has no windows on it
59-
if (Utils.get_n_windows (manager.get_workspace_by_index (manager.get_n_workspaces () - 1)) > 0) {
60-
append_workspace ();
61-
}
62-
}
63-
64-
~WorkspaceManager () {
65-
unowned Meta.Display display = wm.get_display ();
66-
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
67-
manager.workspace_added.disconnect (workspace_added);
68-
manager.workspace_switched.disconnect (workspace_switched);
69-
manager.workspace_removed.disconnect (workspace_removed);
70-
display.window_entered_monitor.disconnect (window_entered_monitor);
71-
display.window_left_monitor.disconnect (window_left_monitor);
7241
}
7342

7443
private void workspace_added (Meta.WorkspaceManager manager, int index) {
@@ -77,203 +46,128 @@ public class Gala.WorkspaceManager : Object {
7746
return;
7847
}
7948

80-
workspace.window_added.connect (queue_window_added);
81-
workspace.window_removed.connect (window_removed);
49+
workspace.window_added.connect (queue_check_workspaces);
50+
workspace.window_removed.connect (queue_check_workspaces);
8251
}
8352

84-
private void workspace_removed (Meta.WorkspaceManager manager, int index) {
85-
List<Meta.Workspace> existing_workspaces = null;
86-
for (int i = 0; i < manager.get_n_workspaces (); i++) {
87-
existing_workspaces.append (manager.get_workspace_by_index (i));
88-
}
89-
90-
var it = workspaces_marked_removed.iterator ();
91-
while (it.next ()) {
92-
var workspace = it.@get ();
93-
94-
if (existing_workspaces.index (workspace) < 0) {
95-
it.remove ();
96-
}
53+
private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) {
54+
if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
55+
queue_check_workspaces ();
9756
}
9857
}
9958

100-
private void workspace_switched (Meta.WorkspaceManager manager, int from, int to, Meta.MotionDirection direction) {
101-
// remove empty workspaces after we switched away from them
102-
maybe_remove_workspace (manager.get_workspace_by_index (from), null);
59+
private void window_left_monitor (Meta.Display display, int monitor, Meta.Window window) {
60+
if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
61+
queue_check_workspaces ();
62+
}
10363
}
10464

105-
private void queue_window_added (Meta.Workspace? workspace, Meta.Window window) {
106-
// We get this call very early so we have to queue an idle for ShellClients
107-
// that might not have checked the window/got a protocol call yet
108-
Idle.add (() => window_added (workspace, window));
65+
/**
66+
* Temporarily disables removing workspaces when they are empty
67+
*/
68+
public void freeze_remove () {
69+
GLib.AtomicInt.inc (ref remove_freeze_count);
10970
}
11071

111-
private bool window_added (Meta.Workspace? workspace, Meta.Window window) {
112-
if (workspace == null || window.on_all_workspaces) {
113-
return Source.REMOVE;
114-
}
115-
116-
unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager ();
117-
int last_workspace = manager.get_n_workspaces () - 1;
118-
119-
if ((window.window_type == Meta.WindowType.NORMAL
120-
|| window.window_type == Meta.WindowType.DIALOG
121-
|| window.window_type == Meta.WindowType.MODAL_DIALOG)
122-
&& workspace.index () == last_workspace
123-
) {
124-
append_workspace ();
72+
/**
73+
* Undo the effect of freeze_remove()
74+
*/
75+
public void thaw_remove () {
76+
if (GLib.AtomicInt.dec_and_test (ref remove_freeze_count)) {
77+
queue_check_workspaces ();
12578
}
12679

127-
return Source.REMOVE;
80+
assert (remove_freeze_count >= 0);
12881
}
12982

130-
private void window_removed (Meta.Workspace? workspace, Meta.Window window) {
131-
if (workspace == null || window.on_all_workspaces) {
132-
return;
83+
private void queue_check_workspaces () {
84+
if (check_workspaces_id == 0) {
85+
var laters = wm.get_display ().get_compositor ().get_laters ();
86+
check_workspaces_id = laters.add (BEFORE_REDRAW, check_workspaces);
13387
}
88+
}
13489

135-
if (window.window_type != Meta.WindowType.NORMAL
136-
&& window.window_type != Meta.WindowType.DIALOG
137-
&& window.window_type != Meta.WindowType.MODAL_DIALOG
138-
) {
139-
return;
140-
}
90+
private bool check_workspaces () {
91+
unowned var display = wm.get_display ();
92+
unowned var manager = display.get_workspace_manager ();
14193

142-
// has already been removed
143-
if (workspace.index () < 0) {
144-
return;
94+
if (remove_freeze_count > 0) {
95+
return Source.CONTINUE;
14596
}
14697

147-
maybe_remove_workspace (workspace, window);
148-
}
98+
bool[] empty_workspaces = new bool[manager.n_workspaces];
14999

150-
private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) {
151-
if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
152-
queue_window_added (window.get_workspace (), window);
100+
for (int i = 0; i < empty_workspaces.length; i++) {
101+
empty_workspaces[i] = true;
153102
}
154-
}
155103

156-
private void window_left_monitor (Meta.Display display, int monitor, Meta.Window window) {
157-
if (Meta.Prefs.get_workspaces_only_on_primary () && monitor == display.get_primary_monitor ()) {
158-
window_removed (window.get_workspace (), window);
104+
unowned var active_startup_sequences = display.get_startup_notification ().get_sequences ();
105+
foreach (var startup_sequence in active_startup_sequences) {
106+
var index = startup_sequence.get_workspace ();
107+
if (index >= 0 && index < empty_workspaces.length) {
108+
empty_workspaces[index] = false;
109+
}
159110
}
160-
}
161111

162-
private void append_workspace () {
163-
unowned Meta.Display display = wm.get_display ();
164-
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
112+
unowned var window_actors = display.get_window_actors ();
113+
foreach (var actor in window_actors) {
114+
var win = actor.meta_window;
165115

166-
manager.append_new_workspace (false, display.get_current_time ());
167-
}
116+
if (win == null || win.on_all_workspaces || win.get_workspace () == null) {
117+
continue;
118+
}
168119

169-
private void maybe_remove_workspace (Meta.Workspace workspace, Meta.Window? window) {
170-
unowned var manager = workspace.get_display ().get_workspace_manager ();
171-
var is_active_workspace = workspace == manager.get_active_workspace ();
172-
var last_workspace_index = manager.get_n_workspaces () - 1 - workspaces_marked_removed.size;
173-
174-
// remove it right away if it was the active workspace and it's not the very last
175-
// or we are in modal-mode
176-
if ((!is_active_workspace || wm.is_modal ())
177-
&& remove_freeze_count < 1
178-
&& Utils.get_n_windows (workspace, true, window) == 0
179-
&& workspace.index () != last_workspace_index
180-
) {
181-
queue_remove_workspace (workspace);
182-
} else if (is_active_workspace // if window is the second last and empty, make it the last workspace
183-
&& remove_freeze_count < 1
184-
&& Utils.get_n_windows (workspace, true, window) == 0
185-
&& workspace.index () == last_workspace_index - 1
186-
) {
187-
queue_remove_workspace (manager.get_workspace_by_index (last_workspace_index));
120+
empty_workspaces[win.get_workspace ().index ()] = false;
188121
}
189-
}
190122

191-
private void queue_remove_workspace (Meta.Workspace workspace) {
192-
// workspace has already been removed
193-
if (workspace in workspaces_marked_removed) {
194-
return;
123+
// If we don't have an empty workspace at the end, add one
124+
if (!empty_workspaces[empty_workspaces.length - 1]) {
125+
manager.append_new_workspace (false, display.get_current_time ());
126+
empty_workspaces += true;
195127
}
196128

197-
workspaces_marked_removed.add (workspace);
198-
199-
// We might be here because of a signal emition from the ws machinery (e.g. workspace.window_removed).
200-
// Often the function emitting the signal doesn't take a ref on the ws so if we remove it right
201-
// away it will be freed. But because the function often accesses it after the singal emition this leads
202-
// to warnings and in some cases a crash.
203-
Idle.add (() => remove_workspace (workspace));
204-
}
205-
206-
/**
207-
* Make sure we switch to a different workspace and remove the given one
208-
*
209-
* @param workspace The workspace to remove
210-
*/
211-
private bool remove_workspace (Meta.Workspace workspace) {
212-
unowned Meta.Display display = workspace.get_display ();
213-
unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
214-
var time = display.get_current_time ();
215-
unowned Meta.Workspace active_workspace = manager.get_active_workspace ();
216-
217-
if (workspace == active_workspace) {
218-
Meta.Workspace? next = null;
219-
220-
next = workspace.get_neighbor (Meta.MotionDirection.LEFT);
221-
// if it's the first one we may have another one to the right
222-
if (next == workspace || next == null) {
223-
next = workspace.get_neighbor (Meta.MotionDirection.RIGHT);
224-
}
129+
var last_index = empty_workspaces.length - 1;
225130

226-
if (next != null) {
227-
next.activate (time);
131+
int last_empty_index = 0;
132+
for (int i = last_index; i >= 0; i--) {
133+
if (!empty_workspaces[i]) {
134+
last_empty_index = i + 1;
135+
break;
228136
}
229137
}
230138

231-
workspace.window_added.disconnect (queue_window_added);
232-
workspace.window_removed.disconnect (window_removed);
233-
234-
manager.remove_workspace (workspace, time);
235-
236-
return Source.REMOVE;
237-
}
139+
if (!wm.is_modal ()) {
140+
var active_index = manager.get_active_workspace_index ();
141+
empty_workspaces[active_index] = false;
142+
}
238143

239-
/**
240-
* Temporarily disables removing workspaces when they are empty
241-
*/
242-
public void freeze_remove () {
243-
GLib.AtomicInt.inc (ref remove_freeze_count);
244-
}
144+
// Delete empty workspaces except for the last one; do it from the end
145+
// to avoid index changes
146+
for (int i = last_index; i >= 0; i--) {
147+
if (!empty_workspaces[i] || i == last_empty_index) {
148+
continue;
149+
}
245150

246-
/**
247-
* Undo the effect of freeze_remove()
248-
*/
249-
public void thaw_remove () {
250-
if (GLib.AtomicInt.dec_and_test (ref remove_freeze_count)) {
251-
cleanup ();
252-
}
151+
var workspace = manager.get_workspace_by_index (i);
253152

254-
assert (remove_freeze_count >= 0);
255-
}
153+
if (workspace == manager.get_active_workspace ()) {
154+
Meta.Workspace? next = null;
256155

257-
/**
258-
* If workspaces are dynamic, checks if there are empty workspaces that should
259-
* be removed. Particularly useful in conjunction with freeze/thaw_remove to
260-
* cleanup after an operation that required stable workspace/window indices
261-
*/
262-
private void cleanup () {
263-
unowned Meta.WorkspaceManager manager = wm.get_display ().get_workspace_manager ();
156+
next = workspace.get_neighbor (LEFT);
157+
// If it's the first one we may have another one to the right
158+
if (next == workspace || next == null) {
159+
next = workspace.get_neighbor (RIGHT);
160+
}
264161

265-
bool remove_last = false;
266-
foreach (var workspace in manager.get_workspaces ().copy ()) {
267-
if (Utils.get_n_windows (workspace, true) != 0) {
268-
remove_last = false;
269-
continue;
162+
if (next != null) {
163+
next.activate (display.get_current_time ());
164+
}
270165
}
271166

272-
if (workspace.active) {
273-
remove_last = true;
274-
} else if (workspace.index () != manager.n_workspaces - 1 || remove_last) {
275-
remove_workspace (workspace);
276-
}
167+
manager.remove_workspace (workspace, display.get_current_time ());
277168
}
169+
170+
check_workspaces_id = 0;
171+
return Source.REMOVE;
278172
}
279173
}

0 commit comments

Comments
 (0)