/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. * Copyright (C) 2003, 2004 Rob Adams * Copyright (C) 2004-2006 Elijah Newren * * 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 2 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 . */ #include "config.h" #include "core/meta-workspace-manager-private.h" #include #include #include #include "core/window-private.h" #include "core/workspace-private.h" #include "meta/meta-enum-types.h" #include "meta/prefs.h" #include "meta/util.h" G_DEFINE_TYPE (MetaWorkspaceManager, meta_workspace_manager, G_TYPE_OBJECT) enum { WORKSPACE_ADDED, WORKSPACE_REMOVED, WORKSPACE_SWITCHED, ACTIVE_WORKSPACE_CHANGED, SHOWING_DESKTOP_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_N_WORKSPACES }; static guint workspace_manager_signals [LAST_SIGNAL] = { 0 }; static void prefs_changed_callback (MetaPreference pref, gpointer data); static void meta_workspace_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaWorkspaceManager *workspace_manager = META_WORKSPACE_MANAGER (object); switch (prop_id) { case PROP_N_WORKSPACES: g_value_set_int (value, meta_workspace_manager_get_n_workspaces (workspace_manager)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_workspace_manager_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_workspace_manager_finalize (GObject *object) { MetaWorkspaceManager *workspace_manager = META_WORKSPACE_MANAGER (object); meta_prefs_remove_listener (prefs_changed_callback, workspace_manager); G_OBJECT_CLASS (meta_workspace_manager_parent_class)->finalize (object); } static void meta_workspace_manager_class_init (MetaWorkspaceManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = meta_workspace_manager_get_property; object_class->set_property = meta_workspace_manager_set_property; object_class->finalize = meta_workspace_manager_finalize; workspace_manager_signals[WORKSPACE_ADDED] = g_signal_new ("workspace-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); workspace_manager_signals[WORKSPACE_REMOVED] = g_signal_new ("workspace-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT); workspace_manager_signals[WORKSPACE_SWITCHED] = g_signal_new ("workspace-switched", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, META_TYPE_MOTION_DIRECTION); workspace_manager_signals[ACTIVE_WORKSPACE_CHANGED] = g_signal_new ("active-workspace-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); workspace_manager_signals[SHOWING_DESKTOP_CHANGED] = g_signal_new ("showing-desktop-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_N_WORKSPACES, g_param_spec_int ("n-workspaces", "N Workspaces", "Number of workspaces", 1, G_MAXINT, 1, G_PARAM_READABLE)); } static void meta_workspace_manager_init (MetaWorkspaceManager *workspace_manager) { } void meta_workspace_manager_reload_work_areas (MetaWorkspaceManager *workspace_manager) { GList *l; for (l = workspace_manager->workspaces; l; l = l->next) { MetaWorkspace *workspace = l->data; meta_workspace_invalidate_work_area (workspace); } } MetaWorkspaceManager * meta_workspace_manager_new (MetaDisplay *display) { MetaWorkspaceManager *workspace_manager; workspace_manager = g_object_new (META_TYPE_WORKSPACE_MANAGER, NULL); workspace_manager->display = display; workspace_manager->active_workspace = NULL; workspace_manager->workspaces = NULL; workspace_manager->rows_of_workspaces = 1; workspace_manager->columns_of_workspaces = -1; workspace_manager->vertical_workspaces = FALSE; workspace_manager->starting_corner = META_DISPLAY_TOPLEFT; /* This is the default layout extracted from default * variable values in update_num_workspaces () * This can be overriden using _NET_DESKTOP_LAYOUT in * meta_x11_display_new (), if it's specified */ meta_workspace_manager_update_workspace_layout (workspace_manager, META_DISPLAY_TOPLEFT, FALSE, -1, 1); /* There must be at least one workspace at all times, * so create that required workspace. */ meta_workspace_new (workspace_manager); meta_workspace_manager_init_workspaces (workspace_manager); meta_prefs_add_listener (prefs_changed_callback, workspace_manager); return workspace_manager; } void meta_workspace_manager_init_workspaces (MetaWorkspaceManager *workspace_manager) { int num; g_return_if_fail (META_IS_WORKSPACE_MANAGER (workspace_manager)); if (meta_prefs_get_dynamic_workspaces ()) /* This will be properly updated using _NET_NUMBER_OF_DESKTOPS * (if set) in meta_x11_display_new () */ num = 1; else num = meta_prefs_get_num_workspaces (); meta_workspace_manager_update_num_workspaces (workspace_manager, META_CURRENT_TIME, num); meta_workspace_activate (workspace_manager->workspaces->data, META_CURRENT_TIME); meta_workspace_manager_reload_work_areas (workspace_manager); } int meta_workspace_manager_get_n_workspaces (MetaWorkspaceManager *workspace_manager) { return g_list_length (workspace_manager->workspaces); } /** * meta_workspace_manager_get_workspace_by_index: * @workspace_manager: a #MetaWorkspaceManager * @index: index of one of the display's workspaces * * Gets the workspace object for one of a workspace manager's workspaces given the workspace * index. It's valid to call this function with an out-of-range index and it * will robustly return %NULL. * * Return value: (transfer none): the workspace object with specified index, or %NULL * if the index is out of range. */ MetaWorkspace * meta_workspace_manager_get_workspace_by_index (MetaWorkspaceManager *workspace_manager, int idx) { return g_list_nth_data (workspace_manager->workspaces, idx); } void meta_workspace_manager_remove_workspace (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace, guint32 timestamp) { GList *l; GList *next; MetaWorkspace *neighbour = NULL; int index; int active_index; gboolean active_index_changed; int new_num; l = g_list_find (workspace_manager->workspaces, workspace); if (!l) return; next = l->next; if (l->prev) neighbour = l->prev->data; else if (l->next) neighbour = l->next->data; else { /* Cannot remove the only workspace! */ return; } meta_workspace_relocate_windows (workspace, neighbour); if (workspace == workspace_manager->active_workspace) meta_workspace_activate (neighbour, timestamp); /* To emit the signal after removing the workspace */ index = meta_workspace_index (workspace); active_index = meta_workspace_manager_get_active_workspace_index (workspace_manager); active_index_changed = index < active_index; /* This also removes the workspace from the displays list */ meta_workspace_remove (workspace); new_num = g_list_length (workspace_manager->workspaces); if (!meta_prefs_get_dynamic_workspaces ()) meta_prefs_set_num_workspaces (new_num); /* If deleting a workspace before the current workspace, the active * workspace index changes, so we need to update that hint */ if (active_index_changed) g_signal_emit (workspace_manager, workspace_manager_signals[ACTIVE_WORKSPACE_CHANGED], 0, NULL); for (l = next; l; l = l->next) { MetaWorkspace *w = l->data; meta_workspace_index_changed (w); } meta_display_queue_workarea_recalc (workspace_manager->display); g_signal_emit (workspace_manager, workspace_manager_signals[WORKSPACE_REMOVED], 0, index); g_object_notify (G_OBJECT (workspace_manager), "n-workspaces"); } /** * meta_workspace_manager_append_new_workspace: * @workspace_manager: a #MetaWorkspaceManager * @activate: %TRUE if the workspace should be switched to after creation * @timestamp: if switching to a new workspace, timestamp to be used when * focusing a window on the new workspace. (Doesn't hurt to pass a valid * timestamp when available even if not switching workspaces.) * * Append a new workspace to the workspace manager and (optionally) switch to that * display. * * Return value: (transfer none): the newly appended workspace. */ MetaWorkspace * meta_workspace_manager_append_new_workspace (MetaWorkspaceManager *workspace_manager, gboolean activate, guint32 timestamp) { MetaWorkspace *w; int new_num; /* This also adds the workspace to the workspace manager list */ w = meta_workspace_new (workspace_manager); if (!w) return NULL; if (activate) meta_workspace_activate (w, timestamp); new_num = g_list_length (workspace_manager->workspaces); if (!meta_prefs_get_dynamic_workspaces ()) meta_prefs_set_num_workspaces (new_num); meta_display_queue_workarea_recalc (workspace_manager->display); g_signal_emit (workspace_manager, workspace_manager_signals[WORKSPACE_ADDED], 0, meta_workspace_index (w)); g_object_notify (G_OBJECT (workspace_manager), "n-workspaces"); return w; } void meta_workspace_manager_update_num_workspaces (MetaWorkspaceManager *workspace_manager, guint32 timestamp, int new_num) { int old_num; GList *l; int i = 0; GList *extras = NULL; MetaWorkspace *last_remaining = NULL; gboolean need_change_space = FALSE; g_assert (new_num > 0); if (g_list_length (workspace_manager->workspaces) == (guint) new_num) return; for (l = workspace_manager->workspaces; l; l = l->next) { MetaWorkspace *w = l->data; if (i >= new_num) extras = g_list_prepend (extras, w); else last_remaining = w; ++i; } old_num = i; g_assert (last_remaining); /* Get rid of the extra workspaces by moving all their windows * to last_remaining, then activating last_remaining if * one of the removed workspaces was active. This will be a bit * wacky if the config tool for changing number of workspaces * is on a removed workspace ;-) */ for (l = extras; l; l = l->next) { MetaWorkspace *w = l->data; meta_workspace_relocate_windows (w, last_remaining); if (w == workspace_manager->active_workspace) need_change_space = TRUE; } if (need_change_space) meta_workspace_activate (last_remaining, timestamp); /* Should now be safe to free the workspaces */ for (l = extras; l; l = l->next) { MetaWorkspace *w = l->data; meta_workspace_remove (w); } g_list_free (extras); for (i = old_num; i < new_num; i++) meta_workspace_new (workspace_manager); meta_display_queue_workarea_recalc (workspace_manager->display); for (i = old_num; i < new_num; i++) g_signal_emit (workspace_manager, workspace_manager_signals[WORKSPACE_ADDED], 0, i); g_object_notify (G_OBJECT (workspace_manager), "n-workspaces"); } void meta_workspace_manager_update_workspace_layout (MetaWorkspaceManager *workspace_manager, MetaDisplayCorner starting_corner, gboolean vertical_layout, int n_rows, int n_columns) { g_return_if_fail (META_IS_WORKSPACE_MANAGER (workspace_manager)); g_return_if_fail (n_rows > 0 || n_columns > 0); g_return_if_fail (n_rows != 0 && n_columns != 0); if (workspace_manager->workspace_layout_overridden) return; workspace_manager->vertical_workspaces = vertical_layout != FALSE; workspace_manager->starting_corner = starting_corner; workspace_manager->rows_of_workspaces = n_rows; workspace_manager->columns_of_workspaces = n_columns; meta_verbose ("Workspace layout rows = %d cols = %d orientation = %d starting corner = %u\n", workspace_manager->rows_of_workspaces, workspace_manager->columns_of_workspaces, workspace_manager->vertical_workspaces, workspace_manager->starting_corner); } /** * meta_workspace_manager_override_workspace_layout: * @workspace_manager: a #MetaWorkspaceManager * @starting_corner: the corner at which the first workspace is found * @vertical_layout: if %TRUE the workspaces are laid out in columns rather than rows * @n_rows: number of rows of workspaces, or -1 to determine the number of rows from * @n_columns and the total number of workspaces * @n_columns: number of columns of workspaces, or -1 to determine the number of columns from * @n_rows and the total number of workspaces * * Explicitly set the layout of workspaces. Once this has been called, the contents of the * _NET_DESKTOP_LAYOUT property on the root window are completely ignored. */ void meta_workspace_manager_override_workspace_layout (MetaWorkspaceManager *workspace_manager, MetaDisplayCorner starting_corner, gboolean vertical_layout, int n_rows, int n_columns) { workspace_manager->workspace_layout_overridden = FALSE; meta_workspace_manager_update_workspace_layout (workspace_manager, starting_corner, vertical_layout, n_rows, n_columns); workspace_manager->workspace_layout_overridden = TRUE; } #ifdef WITH_VERBOSE_MODE static const char * meta_workspace_manager_corner_to_string (MetaDisplayCorner corner) { switch (corner) { case META_DISPLAY_TOPLEFT: return "TopLeft"; case META_DISPLAY_TOPRIGHT: return "TopRight"; case META_DISPLAY_BOTTOMLEFT: return "BottomLeft"; case META_DISPLAY_BOTTOMRIGHT: return "BottomRight"; } return "Unknown"; } #endif /* WITH_VERBOSE_MODE */ void meta_workspace_manager_calc_workspace_layout (MetaWorkspaceManager *workspace_manager, int num_workspaces, int current_space, MetaWorkspaceLayout *layout) { int rows, cols; int grid_area; int *grid; int i, r, c; int current_row, current_col; rows = workspace_manager->rows_of_workspaces; cols = workspace_manager->columns_of_workspaces; if (rows <= 0 && cols <= 0) cols = num_workspaces; if (rows <= 0) rows = num_workspaces / cols + ((num_workspaces % cols) > 0 ? 1 : 0); if (cols <= 0) cols = num_workspaces / rows + ((num_workspaces % rows) > 0 ? 1 : 0); /* paranoia */ if (rows < 1) rows = 1; if (cols < 1) cols = 1; g_assert (rows != 0 && cols != 0); grid_area = rows * cols; meta_verbose ("Getting layout rows = %d cols = %d current = %d " "num_spaces = %d vertical = %s corner = %s\n", rows, cols, current_space, num_workspaces, workspace_manager->vertical_workspaces ? "(true)" : "(false)", meta_workspace_manager_corner_to_string (workspace_manager->starting_corner)); /* ok, we want to setup the distances in the workspace array to go * in each direction. Remember, there are many ways that a workspace * array can be setup. * see http://www.freedesktop.org/standards/wm-spec/1.2/html/x109.html * and look at the _NET_DESKTOP_LAYOUT section for details. * For instance: */ /* starting_corner = META_DISPLAY_TOPLEFT * vertical_workspaces = 0 vertical_workspaces=1 * 1234 1357 * 5678 2468 * * starting_corner = META_DISPLAY_TOPRIGHT * vertical_workspaces = 0 vertical_workspaces=1 * 4321 7531 * 8765 8642 * * starting_corner = META_DISPLAY_BOTTOMLEFT * vertical_workspaces = 0 vertical_workspaces=1 * 5678 2468 * 1234 1357 * * starting_corner = META_DISPLAY_BOTTOMRIGHT * vertical_workspaces = 0 vertical_workspaces=1 * 8765 8642 * 4321 7531 * */ /* keep in mind that we could have a ragged layout, e.g. the "8" * in the above grids could be missing */ grid = g_new (int, grid_area); current_row = -1; current_col = -1; i = 0; switch (workspace_manager->starting_corner) { case META_DISPLAY_TOPLEFT: if (workspace_manager->vertical_workspaces) { c = 0; while (c < cols) { r = 0; while (r < rows) { grid[r*cols+c] = i; ++i; ++r; } ++c; } } else { r = 0; while (r < rows) { c = 0; while (c < cols) { grid[r*cols+c] = i; ++i; ++c; } ++r; } } break; case META_DISPLAY_TOPRIGHT: if (workspace_manager->vertical_workspaces) { c = cols - 1; while (c >= 0) { r = 0; while (r < rows) { grid[r*cols+c] = i; ++i; ++r; } --c; } } else { r = 0; while (r < rows) { c = cols - 1; while (c >= 0) { grid[r*cols+c] = i; ++i; --c; } ++r; } } break; case META_DISPLAY_BOTTOMLEFT: if (workspace_manager->vertical_workspaces) { c = 0; while (c < cols) { r = rows - 1; while (r >= 0) { grid[r*cols+c] = i; ++i; --r; } ++c; } } else { r = rows - 1; while (r >= 0) { c = 0; while (c < cols) { grid[r*cols+c] = i; ++i; ++c; } --r; } } break; case META_DISPLAY_BOTTOMRIGHT: if (workspace_manager->vertical_workspaces) { c = cols - 1; while (c >= 0) { r = rows - 1; while (r >= 0) { grid[r*cols+c] = i; ++i; --r; } --c; } } else { r = rows - 1; while (r >= 0) { c = cols - 1; while (c >= 0) { grid[r*cols+c] = i; ++i; --c; } --r; } } break; } if (i != grid_area) meta_bug ("did not fill in the whole workspace grid in %s (%d filled)\n", G_STRFUNC, i); current_row = 0; current_col = 0; r = 0; while (r < rows) { c = 0; while (c < cols) { if (grid[r*cols+c] == current_space) { current_row = r; current_col = c; } else if (grid[r*cols+c] >= num_workspaces) { /* flag nonexistent spaces with -1 */ grid[r*cols+c] = -1; } ++c; } ++r; } layout->rows = rows; layout->cols = cols; layout->grid = grid; layout->grid_area = grid_area; layout->current_row = current_row; layout->current_col = current_col; #ifdef WITH_VERBOSE_MODE if (meta_is_verbose ()) { r = 0; while (r < layout->rows) { meta_verbose (" "); meta_push_no_msg_prefix (); c = 0; while (c < layout->cols) { if (r == layout->current_row && c == layout->current_col) meta_verbose ("*%2d ", layout->grid[r*layout->cols+c]); else meta_verbose ("%3d ", layout->grid[r*layout->cols+c]); ++c; } meta_verbose ("\n"); meta_pop_no_msg_prefix (); ++r; } } #endif /* WITH_VERBOSE_MODE */ } void meta_workspace_manager_free_workspace_layout (MetaWorkspaceLayout *layout) { g_free (layout->grid); } static void queue_windows_showing (MetaWorkspaceManager *workspace_manager) { GSList *windows, *l; /* Must operate on all windows on display instead of just on the * active_workspace's window list, because the active_workspace's * window list may not contain the on_all_workspace windows. */ windows = meta_display_list_windows (workspace_manager->display, META_LIST_DEFAULT); for (l = windows; l; l = l->next) { MetaWindow *w = l->data; meta_window_queue (w, META_QUEUE_CALC_SHOWING); } g_slist_free (windows); } void meta_workspace_manager_minimize_all_on_active_workspace_except (MetaWorkspaceManager *workspace_manager, MetaWindow *keep) { GList *l; for (l = workspace_manager->active_workspace->windows; l; l = l->next) { MetaWindow *w = l->data; if (w->has_minimize_func && w != keep) meta_window_minimize (w); } } void meta_workspace_manager_show_desktop (MetaWorkspaceManager *workspace_manager, guint32 timestamp) { GList *l; if (workspace_manager->active_workspace->showing_desktop) return; workspace_manager->active_workspace->showing_desktop = TRUE; queue_windows_showing (workspace_manager); /* Focus the most recently used META_WINDOW_DESKTOP window, if there is one; * see bug 159257. */ for (l = workspace_manager->active_workspace->mru_list; l; l = l->next) { MetaWindow *w = l->data; if (w->type == META_WINDOW_DESKTOP) { meta_window_focus (w, timestamp); break; } } g_signal_emit (workspace_manager, workspace_manager_signals[SHOWING_DESKTOP_CHANGED], 0, NULL); } void meta_workspace_manager_unshow_desktop (MetaWorkspaceManager *workspace_manager) { if (!workspace_manager->active_workspace->showing_desktop) return; workspace_manager->active_workspace->showing_desktop = FALSE; queue_windows_showing (workspace_manager); g_signal_emit (workspace_manager, workspace_manager_signals[SHOWING_DESKTOP_CHANGED], 0, NULL); } /** * meta_workspace_manager_get_workspaces: (skip) * @workspace_manager: a #MetaWorkspaceManager * * Returns: (transfer none) (element-type Meta.Workspace): The workspaces for @display */ GList * meta_workspace_manager_get_workspaces (MetaWorkspaceManager *workspace_manager) { return workspace_manager->workspaces; } int meta_workspace_manager_get_active_workspace_index (MetaWorkspaceManager *workspace_manager) { MetaWorkspace *active = workspace_manager->active_workspace; if (!active) return -1; return meta_workspace_index (active); } /** * meta_workspace_manager_get_active_workspace: * @workspace_manager: A #MetaWorkspaceManager * * Returns: (transfer none): The current workspace */ MetaWorkspace * meta_workspace_manager_get_active_workspace (MetaWorkspaceManager *workspace_manager) { return workspace_manager->active_workspace; } void meta_workspace_manager_workspace_switched (MetaWorkspaceManager *workspace_manager, int from, int to, MetaMotionDirection direction) { g_signal_emit (workspace_manager, workspace_manager_signals[WORKSPACE_SWITCHED], 0, from, to, direction); } static void prefs_changed_callback (MetaPreference pref, gpointer data) { MetaWorkspaceManager *workspace_manager = data; if ((pref == META_PREF_NUM_WORKSPACES || pref == META_PREF_DYNAMIC_WORKSPACES) && !meta_prefs_get_dynamic_workspaces ()) { guint32 timestamp; int new_num; timestamp = meta_display_get_current_time_roundtrip (workspace_manager->display); new_num = meta_prefs_get_num_workspaces (); meta_workspace_manager_update_num_workspaces (workspace_manager, timestamp, new_num); } }