diff --git a/src/meson.build b/src/meson.build index e92e709d3..142e6a58e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -709,6 +709,8 @@ if have_wayland 'wayland/meta-wayland-xdg-foreign.c', 'wayland/meta-wayland-xdg-foreign.h', 'wayland/meta-wayland-xdg-foreign-private.h', + 'wayland/meta-wayland-xdg-session-state.c', + 'wayland/meta-wayland-xdg-session-state.h', 'wayland/meta-wayland-xdg-shell.c', 'wayland/meta-wayland-xdg-shell.h', 'wayland/meta-wayland-xdg-dialog.c', diff --git a/src/wayland/meta-wayland-types.h b/src/wayland/meta-wayland-types.h index 99b2dc5ad..8c86309b1 100644 --- a/src/wayland/meta-wayland-types.h +++ b/src/wayland/meta-wayland-types.h @@ -73,3 +73,5 @@ typedef struct _MetaWaylandFilterManager MetaWaylandFilterManager; typedef struct _MetaWaylandClient MetaWaylandClient; typedef struct _MetaWaylandDrmLeaseManager MetaWaylandDrmLeaseManager; + +typedef struct _MetaWaylandXdgSessionManager MetaWaylandXdgSessionManager; diff --git a/src/wayland/meta-wayland-xdg-session-state.c b/src/wayland/meta-wayland-xdg-session-state.c new file mode 100644 index 000000000..33f39dd49 --- /dev/null +++ b/src/wayland/meta-wayland-xdg-session-state.c @@ -0,0 +1,486 @@ +/* + * 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; + + toplevel_state = + meta_wayland_xdg_session_state_ensure_toplevel (xdg_session_state, + name); + + 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 = window->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 = window->rect; + } + else + { + toplevel_state->window_state = WINDOW_STATE_FLOATING; + + toplevel_state->floating.rect = window->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_internal (window, + (META_MOVE_RESIZE_WAYLAND_CLIENT_RESIZE | + META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE | + META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION), + META_PLACE_FLAG_NONE, + META_GRAVITY_NORTH_WEST, + *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); +} diff --git a/src/wayland/meta-wayland-xdg-session-state.h b/src/wayland/meta-wayland-xdg-session-state.h new file mode 100644 index 000000000..780b8decf --- /dev/null +++ b/src/wayland/meta-wayland-xdg-session-state.h @@ -0,0 +1,32 @@ +/* + * 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 . + * + */ + +#pragma once + +#include + +#include "core/meta-session-state.h" +#include "wayland/meta-wayland-types.h" + +typedef struct _MetaWaylandXdgToplevelState MetaWaylandXdgToplevelState; + +#define META_TYPE_WAYLAND_XDG_SESSION_STATE (meta_wayland_xdg_session_state_get_type ()) +G_DECLARE_FINAL_TYPE (MetaWaylandXdgSessionState, + meta_wayland_xdg_session_state, + META, WAYLAND_XDG_SESSION_STATE, + MetaSessionState)