/* * Copyright (C) 2024 Red Hat * * 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 "wayland/meta-wayland-xdg-session-state.h" #include #include "backends/meta-monitor.h" #include "core/meta-context-private.h" #include "core/window-private.h" #include "wayland/meta-wayland.h" #define STATE_FORMAT_VERSION 1 typedef enum _WindowState { WINDOW_STATE_NONE = 0, /* floating */ WINDOW_STATE_FLOATING = 1, /* tiling */ WINDOW_STATE_MAXIMIZED = 2, WINDOW_STATE_TILED_LEFT = 3, WINDOW_STATE_TILED_RIGHT = 4, } WindowState; struct _MetaWaylandXdgToplevelState { MetaWaylandXdgSessionState *session_state; WindowState window_state; struct { MtkRectangle rect; } floating; struct { MtkRectangle rect; } tiled; gboolean is_minimized; int workspace_idx; }; struct _MetaWaylandXdgSessionState { MetaSessionState parent; GHashTable *toplevels; }; G_DEFINE_TYPE (MetaWaylandXdgSessionState, meta_wayland_xdg_session_state, META_TYPE_SESSION_STATE) static GVariant * new_rect_variant (const MtkRectangle *rect) { return g_variant_new ("(iiii)", rect->x, rect->y, rect->width, rect->height); } static void variant_to_rect (GVariant *variant, MtkRectangle *rect) { g_variant_get (variant, "(iiii)", &rect->x, &rect->y, &rect->width, &rect->height); } static void meta_wayland_xdg_toplevel_state_free (MetaWaylandXdgToplevelState *toplevel_state) { g_free (toplevel_state); } static const char * window_state_to_string (WindowState state) { switch (state) { case WINDOW_STATE_NONE: return "none"; case WINDOW_STATE_FLOATING: return "floating"; case WINDOW_STATE_MAXIMIZED: return "maximized"; case WINDOW_STATE_TILED_LEFT: return "tiled-left"; case WINDOW_STATE_TILED_RIGHT: return "tiled-right"; } g_assert_not_reached (); } static char * meta_wayland_xdg_toplevel_state_to_string (MetaWaylandXdgToplevelState *state) { GString *str = NULL; str = g_string_new (NULL); g_string_append (str, window_state_to_string (state->window_state)); switch (state->window_state) { case WINDOW_STATE_NONE: break; case WINDOW_STATE_FLOATING: g_string_append_printf (str, " Rect [%d,%d +%d,%d]", state->floating.rect.x, state->floating.rect.y, state->floating.rect.width, state->floating.rect.height); break; case WINDOW_STATE_MAXIMIZED: case WINDOW_STATE_TILED_LEFT: case WINDOW_STATE_TILED_RIGHT: g_string_append_printf (str, " Rect [%d,%d +%d,%d]", state->tiled.rect.x, state->tiled.rect.y, state->tiled.rect.width, state->tiled.rect.height); break; } return g_string_free_and_steal (str); } static MetaWaylandXdgToplevelState * meta_wayland_xdg_session_state_ensure_toplevel (MetaWaylandXdgSessionState *session_state, const char *name) { MetaWaylandXdgToplevelState *toplevel_state; toplevel_state = g_hash_table_lookup (session_state->toplevels, name); if (!toplevel_state) { toplevel_state = g_new0 (MetaWaylandXdgToplevelState, 1); toplevel_state->session_state = session_state; g_hash_table_insert (session_state->toplevels, g_strdup (name), toplevel_state); } return toplevel_state; } static void meta_wayland_xdg_session_state_dispose (GObject *object) { MetaWaylandXdgSessionState *session_state = META_WAYLAND_XDG_SESSION_STATE (object); g_clear_pointer (&session_state->toplevels, g_hash_table_unref); G_OBJECT_CLASS (meta_wayland_xdg_session_state_parent_class)->dispose (object); } static gboolean meta_wayland_xdg_session_state_serialize (MetaSessionState *session_state, GHashTable *gvdb_data) { MetaWaylandXdgSessionState *xdg_session_state = META_WAYLAND_XDG_SESSION_STATE (session_state); GHashTable *toplevels; GHashTableIter iter; gpointer key, value; GvdbItem *item; item = gvdb_hash_table_insert (gvdb_data, "version"); gvdb_item_set_value (item, g_variant_new ("i", STATE_FORMAT_VERSION)); item = gvdb_hash_table_insert (gvdb_data, "last-used"); gvdb_item_set_value (item, g_variant_new ("x", g_get_real_time ())); toplevels = gvdb_hash_table_new (gvdb_data, "toplevels"); g_hash_table_iter_init (&iter, xdg_session_state->toplevels); while (g_hash_table_iter_next (&iter, &key, &value)) { char *name = key; MetaWaylandXdgToplevelState *toplevel_state = value; GHashTable *toplevel; meta_topic (META_DEBUG_SESSION_MANAGEMENT, "Serializing toplevel state %s", name); toplevel = gvdb_hash_table_new (toplevels, name); item = gvdb_hash_table_insert (toplevel, "state"); gvdb_item_set_value (item, g_variant_new ("u", toplevel_state->window_state)); switch (toplevel_state->window_state) { case WINDOW_STATE_NONE: break; case WINDOW_STATE_FLOATING: item = gvdb_hash_table_insert (toplevel, "floating-rect"); gvdb_item_set_value (item, new_rect_variant (&toplevel_state->floating.rect)); break; case WINDOW_STATE_MAXIMIZED: case WINDOW_STATE_TILED_LEFT: case WINDOW_STATE_TILED_RIGHT: item = gvdb_hash_table_insert (toplevel, "tiled-rect"); gvdb_item_set_value (item, new_rect_variant (&toplevel_state->tiled.rect)); break; } item = gvdb_hash_table_insert (toplevel, "is-minimized"); gvdb_item_set_value (item, g_variant_new_boolean (toplevel_state->is_minimized)); item = gvdb_hash_table_insert (toplevel, "workspace"); gvdb_item_set_value (item, g_variant_new_int32 (toplevel_state->workspace_idx)); } return TRUE; } static gboolean meta_wayland_xdg_session_state_parse (MetaSessionState *session_state, GvdbTable *data, GError **error) { MetaWaylandXdgSessionState *xdg_session_state = META_WAYLAND_XDG_SESSION_STATE (session_state); g_autoptr (GVariant) version = NULL; GvdbTable *toplevels, *toplevel; GStrv toplevel_names = NULL; int i; version = gvdb_table_get_value (data, "version"); if (!g_variant_is_of_type (version, G_VARIANT_TYPE ("i")) || g_variant_get_int32 (version) > STATE_FORMAT_VERSION) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Too new session-data version"); return FALSE; } toplevels = gvdb_table_get_table (data, "toplevels"); toplevel_names = gvdb_table_get_names (toplevels, NULL); for (i = 0; toplevel_names[i]; i++) { MetaWaylandXdgToplevelState * toplevel_state; g_autoptr (GVariant) state = NULL, floating_rect = NULL, tiled_rect = NULL; g_autoptr (GVariant) is_minimized = NULL, monitor = NULL, workspace = NULL; meta_topic (META_DEBUG_SESSION_MANAGEMENT, "Parsing toplevel state %s", toplevel_names[i]); toplevel_state = meta_wayland_xdg_session_state_ensure_toplevel (xdg_session_state, toplevel_names[i]); toplevel = gvdb_table_get_table (toplevels, toplevel_names[i]); state = gvdb_table_get_value (toplevel, "state"); if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("u"))) toplevel_state->window_state = g_variant_get_uint32 (state); floating_rect = gvdb_table_get_value (toplevel, "floating-rect"); if (floating_rect && g_variant_is_of_type (floating_rect, G_VARIANT_TYPE ("(iiii)"))) { variant_to_rect (floating_rect, &toplevel_state->floating.rect); } tiled_rect = gvdb_table_get_value (toplevel, "tiled-rect"); if (tiled_rect && g_variant_is_of_type (tiled_rect, G_VARIANT_TYPE ("(iiii)"))) { variant_to_rect (floating_rect, &toplevel_state->tiled.rect); } is_minimized = gvdb_table_get_value (toplevel, "is-minimized"); if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("b"))) toplevel_state->is_minimized = g_variant_get_boolean (is_minimized); workspace = gvdb_table_get_value (toplevel, "workspace"); if (workspace && g_variant_is_of_type (workspace, G_VARIANT_TYPE ("i"))) toplevel_state->workspace_idx = g_variant_get_int32 (workspace); gvdb_table_free (toplevel); } g_clear_pointer (&toplevel_names, g_strfreev); gvdb_table_free (toplevels); return TRUE; } static void meta_wayland_xdg_session_state_save_window (MetaSessionState *state, const char *name, MetaWindow *window) { MetaWaylandXdgSessionState *xdg_session_state = META_WAYLAND_XDG_SESSION_STATE (state); MetaWaylandXdgToplevelState *toplevel_state; MtkRectangle rect; toplevel_state = meta_wayland_xdg_session_state_ensure_toplevel (xdg_session_state, name); rect = meta_window_config_get_rect (window->config); g_object_get (window, "minimized", &toplevel_state->is_minimized, NULL); if (meta_window_get_maximized (window) == (META_MAXIMIZE_VERTICAL | META_MAXIMIZE_HORIZONTAL)) { toplevel_state->window_state = WINDOW_STATE_MAXIMIZED; toplevel_state->tiled.rect = rect; } else if (window->tile_mode == META_TILE_LEFT || window->tile_mode == META_TILE_RIGHT) { if (window->tile_mode == META_TILE_LEFT) toplevel_state->window_state = WINDOW_STATE_TILED_LEFT; else if (window->tile_mode == META_TILE_RIGHT) toplevel_state->window_state = WINDOW_STATE_TILED_RIGHT; toplevel_state->tiled.rect = rect; } else { toplevel_state->window_state = WINDOW_STATE_FLOATING; toplevel_state->floating.rect = rect; } toplevel_state->workspace_idx = meta_workspace_index (window->workspace); if (meta_is_topic_enabled (META_DEBUG_SESSION_MANAGEMENT)) { g_autofree char *state_str = NULL; state_str = meta_wayland_xdg_toplevel_state_to_string (toplevel_state); meta_topic (META_DEBUG_SESSION_MANAGEMENT, "Saved window state %s: %s", name, state_str); } } static gboolean meta_wayland_xdg_session_state_restore_window (MetaSessionState *state, const char *name, MetaWindow *window) { MetaWaylandXdgSessionState *xdg_session_state = META_WAYLAND_XDG_SESSION_STATE (state); MetaWaylandXdgToplevelState *toplevel_state; MtkRectangle *rect = NULL; toplevel_state = g_hash_table_lookup (xdg_session_state->toplevels, name); if (!toplevel_state) return FALSE; switch (toplevel_state->window_state) { case WINDOW_STATE_NONE: break; case WINDOW_STATE_FLOATING: rect = &toplevel_state->floating.rect; break; case WINDOW_STATE_TILED_LEFT: case WINDOW_STATE_TILED_RIGHT: case WINDOW_STATE_MAXIMIZED: break; } switch (toplevel_state->window_state) { case WINDOW_STATE_NONE: case WINDOW_STATE_FLOATING: break; case WINDOW_STATE_TILED_LEFT: meta_window_tile (window, META_TILE_LEFT); break; case WINDOW_STATE_TILED_RIGHT: meta_window_tile (window, META_TILE_RIGHT); break; case WINDOW_STATE_MAXIMIZED: meta_window_maximize (window, META_MAXIMIZE_VERTICAL | META_MAXIMIZE_HORIZONTAL); break; } if (toplevel_state->is_minimized) meta_window_minimize (window); if (toplevel_state->workspace_idx >= 0) { meta_window_change_workspace_by_index (window, toplevel_state->workspace_idx, TRUE); } if (rect) { meta_window_move_resize (window, (META_MOVE_RESIZE_WAYLAND_CLIENT_RESIZE | META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE | META_MOVE_RESIZE_MOVE_ACTION | META_MOVE_RESIZE_RESIZE_ACTION), *rect); } window->placed = TRUE; if (meta_is_topic_enabled (META_DEBUG_SESSION_MANAGEMENT)) { g_autofree char *state_str = NULL; state_str = meta_wayland_xdg_toplevel_state_to_string (toplevel_state); meta_topic (META_DEBUG_SESSION_MANAGEMENT, "Restored window state %s: %s", name, state_str); } return TRUE; } static void meta_wayland_xdg_session_state_remove_window (MetaSessionState *state, const char *name) { MetaWaylandXdgSessionState *xdg_session_state = META_WAYLAND_XDG_SESSION_STATE (state); g_hash_table_remove (xdg_session_state->toplevels, name); } static void meta_wayland_xdg_session_state_class_init (MetaWaylandXdgSessionStateClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); MetaSessionStateClass *session_state_class = META_SESSION_STATE_CLASS (klass); object_class->dispose = meta_wayland_xdg_session_state_dispose; session_state_class->serialize = meta_wayland_xdg_session_state_serialize; session_state_class->parse = meta_wayland_xdg_session_state_parse; session_state_class->save_window = meta_wayland_xdg_session_state_save_window; session_state_class->restore_window = meta_wayland_xdg_session_state_restore_window; session_state_class->remove_window = meta_wayland_xdg_session_state_remove_window; } static void meta_wayland_xdg_session_state_init (MetaWaylandXdgSessionState *session_state) { session_state->toplevels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) meta_wayland_xdg_toplevel_state_free); }