diff options
Diffstat (limited to 'src/core/screen.c')
-rw-r--r-- | src/core/screen.c | 294 |
1 files changed, 272 insertions, 22 deletions
diff --git a/src/core/screen.c b/src/core/screen.c index 6c61585e..f6f1c14a 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -55,11 +55,17 @@ #include <string.h> #include <stdio.h> +#define MAX_REASONABLE_WORKSPACES 36 + static char* get_screen_name (MetaDisplay *display, int number); static void update_num_workspaces (MetaScreen *screen, guint32 timestamp); +static void safely_remove_workspaces (MetaScreen *screen, + GList *extras, + MetaWorkspace *last_remaining, + guint32 timestamp); static void update_focus_mode (MetaScreen *screen); static void set_workspace_names (MetaScreen *screen); static void prefs_changed_callback (MetaPreference pref, @@ -509,6 +515,7 @@ meta_screen_new (MetaDisplay *display, screen->vertical_workspaces = FALSE; screen->starting_corner = META_SCREEN_TOPLEFT; screen->compositor_data = NULL; + screen->dynamic_workspace_idle_id = 0; { XFontStruct *font_info; @@ -645,6 +652,12 @@ meta_screen_free (MetaScreen *screen, screen->closing += 1; + if (screen->dynamic_workspace_idle_id != 0) + { + g_source_remove (screen->dynamic_workspace_idle_id); + screen->dynamic_workspace_idle_id = 0; + } + meta_display_grab (display); if (screen->display->compositor) @@ -882,6 +895,10 @@ prefs_changed_callback (MetaPreference pref, meta_display_get_current_time_roundtrip (screen->display); update_num_workspaces (screen, timestamp); } + else if (pref == META_PREF_DYNAMIC_WORKSPACES) + { + meta_screen_update_dynamic_workspaces (screen); + } else if (pref == META_PREF_FOCUS_MODE) { update_focus_mode (screen); @@ -1113,6 +1130,40 @@ set_number_of_spaces_hint (MetaScreen *screen, meta_error_trap_pop (screen->display, FALSE); } +/* + * Asks the window manager to change the number of workspaces on screen. + * This function is copied from libwnck. + */ +static void +request_workspace_count_change (MetaScreen *screen, + int count) +{ + XEvent xev; + + if (screen->closing > 0) + return; + + meta_verbose ("Requesting workspace count change to %d via client message\n", count); + + /* Send _NET_NUMBER_OF_DESKTOPS client message to trigger proper GSettings update */ + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.window = screen->xroot; + xev.xclient.send_event = True; + xev.xclient.display = screen->display->xdisplay; + xev.xclient.message_type = screen->display->atom__NET_NUMBER_OF_DESKTOPS; + xev.xclient.format = 32; + xev.xclient.data.l[0] = count; + + meta_error_trap_push (screen->display); + XSendEvent (screen->display->xdisplay, + screen->xroot, + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev); + meta_error_trap_pop (screen->display, FALSE); +} + static void set_desktop_geometry_hint (MetaScreen *screen) { @@ -1158,39 +1209,200 @@ set_desktop_viewport_hint (MetaScreen *screen) meta_error_trap_pop (screen->display, FALSE); } + +static gboolean +workspace_has_non_sticky_windows (MetaWorkspace *workspace) +{ + GList *window_list; + + for (window_list = workspace->windows; window_list != NULL; window_list = window_list->next) + { + MetaWindow *window = window_list->data; + if (!window->on_all_workspaces && !window->always_sticky) + return TRUE; + } + return FALSE; +} + +static gboolean +dynamic_workspaces_idle_callback (gpointer user_data) +{ + MetaScreen *screen = user_data; + int workspace_count = meta_screen_get_n_workspaces (screen); + GList *workspace_list; + int empty_workspaces = 0; + + screen->dynamic_workspace_idle_id = 0; + + if (!meta_prefs_get_dynamic_workspaces ()) + return G_SOURCE_REMOVE; + + /* Count empty workspaces */ + for (workspace_list = screen->workspaces; workspace_list != NULL; workspace_list = workspace_list->next) + { + MetaWorkspace *workspace = workspace_list->data; + if (!workspace_has_non_sticky_windows (workspace)) + empty_workspaces++; + } + + /* Make sure there's at least one empty workspace */ + if (empty_workspaces == 0 && workspace_count < MAX_REASONABLE_WORKSPACES) + { + meta_workspace_new (screen); + workspace_count++; + empty_workspaces++; + request_workspace_count_change (screen, workspace_count); + } + + /* If there are empty workspaces in the middle, consolidate windows from the + * next workspace into it. Do this sequentially to make sure all empty + * workspaces are at the end. */ + for (int i = 0; i < workspace_count - 1; i++) + { + MetaWorkspace *current_ws = meta_screen_get_workspace_by_index (screen, i); + if (current_ws && !workspace_has_non_sticky_windows (current_ws)) + { + /* Find next workspace with windows to move into this empty slot */ + for (int j = i + 1; j < workspace_count; j++) + { + MetaWorkspace *source_ws = meta_screen_get_workspace_by_index (screen, j); + if (source_ws && workspace_has_non_sticky_windows (source_ws)) + { + meta_workspace_relocate_windows (source_ws, current_ws); + break; + } + } + } + } + + /* Count how many trailing empty workspaces there are */ + int trailing_empty_count = 0; + for (int i = workspace_count - 1; i >= 0; i--) + { + MetaWorkspace *ws = meta_screen_get_workspace_by_index (screen, i); + if (ws && !workspace_has_non_sticky_windows (ws)) + { + trailing_empty_count++; + } + else + { + break; + } + } + + /* Remove all but one trailing empty workspace */ + int workspaces_to_remove_count = MAX (0, trailing_empty_count - 1); + if (workspaces_to_remove_count > 0) + { + GList *workspaces_to_remove = NULL; + MetaWorkspace *safe_target = NULL; + + /* Build list of workspaces to remove */ + int current_count = meta_screen_get_n_workspaces (screen); + for (int i = 0; i < workspaces_to_remove_count; i++) + { + MetaWorkspace *last_ws = meta_screen_get_workspace_by_index (screen, current_count - 1 - i); + if (last_ws && !workspace_has_non_sticky_windows (last_ws)) + { + workspaces_to_remove = g_list_prepend (workspaces_to_remove, last_ws); + } + } + + /* Find a safe target workspace - prefer non-empty workspaces, but accept + * any workspace that will remain if all are empty */ + for (int i = 0; i < meta_screen_get_n_workspaces (screen); i++) + { + MetaWorkspace *ws = meta_screen_get_workspace_by_index (screen, i); + if (ws && !g_list_find (workspaces_to_remove, ws)) + { + /* Found a workspace that will remain */ + if (!safe_target) + safe_target = ws; /* Use this as fallback */ + + /* Prefer non-empty workspaces */ + if (workspace_has_non_sticky_windows (ws)) + { + safe_target = ws; + break; + } + } + } + + /* If we found workspaces to remove and a safe target, do the removal */ + if (workspaces_to_remove && safe_target) + { + guint32 timestamp = meta_display_get_current_time_roundtrip (screen->display); + int expected_count = current_count - g_list_length (workspaces_to_remove); + safely_remove_workspaces (screen, workspaces_to_remove, safe_target, timestamp); + + /* Verify workspace count is as expected after removal */ + int actual_count = meta_screen_get_n_workspaces (screen); + if (actual_count != expected_count) + { + meta_warning ("Dynamic workspaces: expected %d workspaces after removal, got %d\n", + expected_count, actual_count); + } + + request_workspace_count_change (screen, actual_count); + } + else if (workspaces_to_remove && !safe_target) + { + meta_warning ("Dynamic workspaces: could not find safe target for workspace removal\n"); + } + + g_list_free (workspaces_to_remove); + } + + return G_SOURCE_REMOVE; +} + +void +meta_screen_update_dynamic_workspaces (MetaScreen *screen) +{ + if (!meta_prefs_get_dynamic_workspaces ()) + return; + + /* Don't queue multiple idle callbacks */ + if (screen->dynamic_workspace_idle_id != 0) + return; + + /* Queue idle callback to run after current operations complete */ + screen->dynamic_workspace_idle_id = g_idle_add (dynamic_workspaces_idle_callback, screen); +} + static void -update_num_workspaces (MetaScreen *screen, - guint32 timestamp) +safely_remove_workspaces (MetaScreen *screen, + GList *extras, + MetaWorkspace *last_remaining, + guint32 timestamp) { - int new_num; GList *tmp; - int i; - GList *extras; - MetaWorkspace *last_remaining; gboolean need_change_space; - new_num = meta_prefs_get_num_workspaces (); + g_return_if_fail (screen != NULL); + g_return_if_fail (last_remaining != NULL); - g_assert (new_num > 0); + if (extras == NULL) + return; - last_remaining = NULL; - extras = NULL; - i = 0; - tmp = screen->workspaces; - while (tmp != NULL) + /* Validate that we're not trying to remove all workspaces */ + int total_workspaces = meta_screen_get_n_workspaces (screen); + int removal_count = g_list_length (extras); + if (removal_count >= total_workspaces) { - MetaWorkspace *w = tmp->data; - - if (i >= new_num) - extras = g_list_prepend (extras, w); - else - last_remaining = w; + meta_warning ("Attempted to remove all workspaces (%d >= %d), aborting\n", + removal_count, total_workspaces); + return; + } - ++i; - tmp = tmp->next; + /* Validate that last_remaining is not in the removal list */ + if (g_list_find (extras, last_remaining)) + { + meta_warning ("Last remaining workspace is in removal list, aborting workspace removal\n"); + return; } - g_assert (last_remaining); + meta_verbose ("Safely removing %d workspaces\n", g_list_length (extras)); /* Get rid of the extra workspaces by moving all their windows * to last_remaining, then activating last_remaining if @@ -1227,6 +1439,44 @@ update_num_workspaces (MetaScreen *screen, tmp = tmp->next; } + meta_verbose ("Workspace removal completed successfully\n"); +} + +static void +update_num_workspaces (MetaScreen *screen, + guint32 timestamp) +{ + int new_num; + GList *tmp; + int i; + GList *extras; + MetaWorkspace *last_remaining; + + new_num = meta_prefs_get_num_workspaces (); + + g_assert (new_num > 0); + + last_remaining = NULL; + extras = NULL; + i = 0; + tmp = screen->workspaces; + while (tmp != NULL) + { + MetaWorkspace *w = tmp->data; + + if (i >= new_num) + extras = g_list_prepend (extras, w); + else + last_remaining = w; + + ++i; + tmp = tmp->next; + } + + g_assert (last_remaining); + + safely_remove_workspaces (screen, extras, last_remaining, timestamp); + g_list_free (extras); while (i < new_num) |