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
187public 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