diff --git a/src/core/meta-session-manager.c b/src/core/meta-session-manager.c
new file mode 100644
index 000000000..3b6715798
--- /dev/null
+++ b/src/core/meta-session-manager.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2024 Red Hat Inc.
+ *
+ * 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 .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#include "config.h"
+
+#include "core/meta-session-manager.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "core/meta-anonymous-file.h"
+
+#define SESSION_FILE_NAME "session.gvdb"
+
+struct _MetaSessionManager
+{
+ GObject parent_instance;
+ GHashTable *sessions; /* Session name -> MetaSessionState */
+ GHashTable *deleted_sessions; /* Set of session names */
+ GvdbTable *gvdb_table;
+ char *name;
+ int fd;
+ GMappedFile *mapped_file;
+};
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_FD,
+ N_PROPS,
+};
+
+static GParamSpec *props[N_PROPS] = { 0, };
+
+#define MAX_SIZE (10 * 1024 * 1024)
+
+static void meta_session_manager_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (MetaSessionManager, meta_session_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+ meta_session_manager_initable_iface_init))
+
+static void
+meta_session_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MetaSessionManager *session_manager = META_SESSION_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ session_manager->name = g_value_dup_string (value);
+ break;
+ case PROP_FD:
+ session_manager->fd = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_session_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MetaSessionManager *session_manager = META_SESSION_MANAGER (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, session_manager->name);
+ break;
+ case PROP_FD:
+ g_value_set_int (value, session_manager->fd);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+meta_session_manager_finalize (GObject *object)
+{
+ MetaSessionManager *session_manager = META_SESSION_MANAGER (object);
+
+ g_clear_pointer (&session_manager->sessions, g_hash_table_unref);
+ g_clear_pointer (&session_manager->deleted_sessions, g_hash_table_unref);
+ g_clear_pointer (&session_manager->mapped_file, g_mapped_file_unref);
+ g_clear_pointer (&session_manager->gvdb_table, gvdb_table_free);
+ g_clear_pointer (&session_manager->name, g_free);
+ g_clear_fd (&session_manager->fd, NULL);
+
+ G_OBJECT_CLASS (meta_session_manager_parent_class)->finalize (object);
+}
+
+static void
+meta_session_manager_class_init (MetaSessionManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = meta_session_manager_set_property;
+ object_class->get_property = meta_session_manager_get_property;
+ object_class->finalize = meta_session_manager_finalize;
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name", NULL, NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+ props[PROP_FD] =
+ g_param_spec_int ("fd", NULL, NULL,
+ G_MININT, G_MAXINT, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+meta_session_manager_init (MetaSessionManager *session_manager)
+{
+ session_manager->fd = -1;
+ session_manager->sessions =
+ g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+ session_manager->deleted_sessions =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+}
+
+static gboolean
+meta_session_manager_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ MetaSessionManager *manager = META_SESSION_MANAGER (initable);
+ g_autoptr (GBytes) bytes = NULL;
+
+ if (manager->name && manager->fd < 0)
+ {
+ g_autofree char *session_dir = NULL, *session_file = NULL;
+
+ session_dir = g_build_filename (g_get_user_data_dir (),
+ manager->name, NULL);
+
+ if (g_mkdir_with_parents (session_dir, 0700) < 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "Could not create directory for session data: %m");
+ return FALSE;
+ }
+
+ session_file = g_build_filename (session_dir,
+ SESSION_FILE_NAME,
+ NULL);
+
+ manager->fd = open (session_file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
+ }
+
+ if (manager->fd < 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Error opening session file: %m");
+ return FALSE;
+ }
+
+ manager->mapped_file = g_mapped_file_new_from_fd (manager->fd, TRUE, error);
+ if (!manager->mapped_file)
+ return FALSE;
+
+ if (g_mapped_file_get_length (manager->mapped_file) > 0)
+ {
+ bytes = g_mapped_file_get_bytes (manager->mapped_file);
+ manager->gvdb_table = gvdb_table_new_from_bytes (bytes, FALSE, error);
+ if (!manager->gvdb_table)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+meta_session_manager_initable_iface_init (GInitableIface *iface)
+{
+ iface->init = meta_session_manager_initable_init;
+}
+
+MetaSessionManager *
+meta_session_manager_new (const gchar *name,
+ GError **error)
+{
+ return g_initable_new (META_TYPE_SESSION_MANAGER, NULL, error,
+ "name", name,
+ NULL);
+}
+
+MetaSessionManager *
+meta_session_manager_new_for_fd (const gchar *name,
+ int fd,
+ GError **error)
+{
+ return g_initable_new (META_TYPE_SESSION_MANAGER,
+ NULL, error,
+ "name", name,
+ "fd", fd,
+ NULL);
+}
+
+int
+meta_session_manager_get_fd (MetaSessionManager *manager)
+{
+ return manager->fd;
+}
+
+gboolean
+meta_session_manager_get_session_exists (MetaSessionManager *manager,
+ const char *name)
+{
+ if (g_hash_table_contains (manager->sessions, name))
+ return TRUE;
+
+ if (g_hash_table_contains (manager->deleted_sessions, name))
+ return FALSE;
+
+ if (manager->gvdb_table)
+ {
+ GvdbTable *table;
+
+ table = gvdb_table_get_table (manager->gvdb_table, name);
+ if (table)
+ {
+ gvdb_table_free (table);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+MetaSessionState *
+meta_session_manager_get_session (MetaSessionManager *manager,
+ GType type,
+ const gchar *name)
+{
+ g_autoptr (MetaSessionState) session_state = NULL;
+ GvdbTable *table = NULL;
+
+ g_assert (g_type_is_a (type, META_TYPE_SESSION_STATE));
+
+ session_state = g_hash_table_lookup (manager->sessions, name);
+ if (session_state)
+ return g_steal_pointer (&session_state);
+
+ session_state = g_object_new (type, "name", name, NULL);
+
+ if (manager->gvdb_table)
+ table = gvdb_table_get_table (manager->gvdb_table, name);
+
+ if (table)
+ {
+ g_autoptr (GError) error = NULL;
+
+ meta_session_state_parse (session_state, table, &error);
+ g_clear_pointer (&table, gvdb_table_free);
+
+ if (error)
+ {
+ g_critical ("Error parsing session data: %s\n", error->message);
+ /* Ensure to return a pristine state */
+ g_clear_object (&session_state);
+ session_state = g_object_new (type, "name", name, NULL);
+ }
+ }
+
+ g_hash_table_insert (manager->sessions,
+ (gpointer) meta_session_state_get_name (session_state),
+ g_object_ref (session_state));
+
+ return g_steal_pointer (&session_state);
+}
+
+void
+meta_session_manager_delete_session (MetaSessionManager *manager,
+ const char *name)
+{
+ g_hash_table_add (manager->deleted_sessions, g_strdup (name));
+ g_hash_table_remove (manager->sessions, name);
+}
+
+static void
+snapshot_gvdb_recursively (GvdbTable *table,
+ GHashTable *dest,
+ const gchar *name)
+{
+ g_autoptr (GVariant) value = NULL;
+
+ value = gvdb_table_get_value (table, name);
+
+ if (value)
+ {
+ GvdbItem *item;
+
+ item = gvdb_hash_table_insert (dest, name);
+ gvdb_item_set_value (item, value);
+ }
+ else
+ {
+ GvdbTable *subtable;
+ GHashTable *dest_subtable;
+ g_auto (GStrv) names;
+ size_t len, i;
+
+ subtable = gvdb_table_get_table (table, name);
+ names = gvdb_table_get_names (subtable, &len);
+ dest_subtable = gvdb_hash_table_new (dest, name);
+
+ for (i = 0; i < len; i++)
+ snapshot_gvdb_recursively (subtable, dest_subtable, names[i]);
+
+ gvdb_table_free (subtable);
+ }
+}
+
+gboolean
+meta_session_manager_save_sync (MetaSessionManager *manager,
+ GError **error)
+{
+ GHashTableIter iter;
+ GHashTable *table;
+ MetaSessionState *session_state;
+ g_autofree char *session_dir = NULL, *session_file = NULL;
+
+ if (!manager->name)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Context does not have a name");
+ return FALSE;
+ }
+
+ session_dir = g_build_filename (g_get_user_data_dir (),
+ manager->name,
+ NULL);
+
+ if (g_mkdir_with_parents (session_dir, 0700) < 0)
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "Could not create directory for session data: %m");
+ return FALSE;
+ }
+
+ session_file = g_build_filename (session_dir, SESSION_FILE_NAME, NULL);
+
+ g_hash_table_iter_init (&iter, manager->sessions);
+ table = gvdb_hash_table_new (NULL, NULL);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &session_state))
+ {
+ GHashTable *session_table;
+
+ session_table =
+ gvdb_hash_table_new (table, meta_session_state_get_name (session_state));
+ meta_session_state_serialize (session_state, session_table);
+ }
+
+ if (manager->gvdb_table)
+ {
+ g_auto (GStrv) names;
+ gsize len, i;
+
+ names = gvdb_table_get_names (manager->gvdb_table, &len);
+
+ for (i = 0; i < len; i++)
+ {
+ if (g_hash_table_contains (manager->sessions, names[i]))
+ continue;
+ if (g_hash_table_contains (manager->deleted_sessions, names[i]))
+ continue;
+
+ snapshot_gvdb_recursively (manager->gvdb_table, table, names[i]);
+ }
+ }
+
+ return gvdb_table_write_contents (table,
+ session_file,
+ FALSE, error);
+}
diff --git a/src/core/meta-session-manager.h b/src/core/meta-session-manager.h
new file mode 100644
index 000000000..8c908de76
--- /dev/null
+++ b/src/core/meta-session-manager.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 Red Hat Inc.
+ *
+ * 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 .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#pragma once
+
+#include
+
+#include "core/meta-session-state.h"
+#include "meta/window.h"
+
+#define META_TYPE_SESSION_MANAGER (meta_session_manager_get_type ())
+G_DECLARE_FINAL_TYPE (MetaSessionManager,
+ meta_session_manager,
+ META, SESSION_MANAGER,
+ GObject)
+
+MetaSessionManager * meta_session_manager_new (const gchar *name,
+ GError **error);
+
+MetaSessionManager * meta_session_manager_new_for_fd (const gchar *name,
+ int fd,
+ GError **error);
+
+int meta_session_manager_get_fd (MetaSessionManager *manager);
+
+gboolean meta_session_manager_get_session_exists (MetaSessionManager *manager,
+ const char *name);
+
+MetaSessionState * meta_session_manager_get_session (MetaSessionManager *manager,
+ GType type,
+ const char *name);
+
+gboolean meta_session_manager_save_sync (MetaSessionManager *manager,
+ GError **error);
+
+void meta_session_manager_delete_session (MetaSessionManager *manager,
+ const char *name);
diff --git a/src/meson.build b/src/meson.build
index aaa160de2..e92e709d3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -372,6 +372,7 @@ mutter_sources = [
'core/meta-selection.c',
'core/meta-selection-source.c',
'core/meta-selection-source-memory.c',
+ 'core/meta-session-manager.c',
'core/meta-session-state.c',
'core/meta-sound-player.c',
'core/meta-tablet-action-mapper.c',