mutter/src/wayland/meta-wayland-xdg-session.c
Jonas Ådahl 74ce36f323 wayland: Implement the XDG session management protocol
The xdg_session_manager_v1 global interface is the generator
of xdg_session_v1 objects for clients. These will notify of an
unique ID that can be used for future instantiations.

Once a xdg_session_v1 object is obtained, toplevels can be added
to be managed by it, and clients may get a hint about whether the
toplevel was restored to a saved state.

Changes by Carlos Garnacho: Integrate with MetaSessionManager core
object. Flesh out event emission of xdg_session_v1 and
xdg_toplevel_session_v1 objects, handle sessions being
replaced/deleted.

Changes by Sebastian Wick:
* make lifetimes of xdg_sessions entirely determined by the wayland and
  handle its destruction via the signal
* fix session destruction vs deletion
* do not drop refcount of replaced session state temporarily to make
  sure the replacing session keeps the state
* disconnect signals of destroyed and replaced sessions
* disconnect window-unmanaging signal handler for
  MetaWaylandXdgToplevelSession
* call wl_resource_destroy in xdg_toplevel_session_remove to make it a
  destructor
* handle session being destroyed before topevel-sessions
* handle the toplevel going away before the topevel-sessions

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3825>
2024-09-14 13:47:21 +00:00

458 lines
14 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "wayland/meta-wayland-xdg-session.h"
#include <glib-object.h>
#include "wayland/meta-wayland-xdg-session-state.h"
#include "wayland/meta-wayland-xdg-shell.h"
#include "session-management-v1-server-protocol.h"
typedef struct _MetaWaylandXdgToplevelSession
{
grefcount ref_count;
MetaWaylandSurface *surface;
struct wl_resource *resource;
MetaWaylandXdgSession *session;
char *name;
} MetaWaylandXdgToplevelSession;
enum
{
DESTROYED,
RESTORE_TOPLEVEL,
SAVE_TOPLEVEL,
REMOVE_TOPLEVEL,
DELETE,
N_SIGNALS
};
static guint signals[N_SIGNALS];
struct _MetaWaylandXdgSession
{
GObject parent;
char *id;
struct wl_resource *resource;
GHashTable *toplevels; /* name -> MetaWaylandXdgToplevelSession */
};
G_DEFINE_FINAL_TYPE (MetaWaylandXdgSession,
meta_wayland_xdg_session,
G_TYPE_OBJECT)
static void on_window_unmanaging (MetaWindow *window,
MetaWaylandXdgToplevelSession *toplevel_session);
static MetaWaylandXdgToplevelSession *
meta_wayland_xdg_toplevel_session_ref (MetaWaylandXdgToplevelSession *toplevel_session)
{
g_ref_count_inc (&toplevel_session->ref_count);
return toplevel_session;
}
static void
meta_wayland_xdg_toplevel_session_unref (MetaWaylandXdgToplevelSession *toplevel_session)
{
if (g_ref_count_dec (&toplevel_session->ref_count))
{
MetaWindow *window = NULL;
MetaWaylandSurface *surface = toplevel_session->surface;
if (surface)
window = meta_wayland_surface_get_toplevel_window (surface);
if (window)
{
g_signal_handlers_disconnect_by_func (window,
on_window_unmanaging,
toplevel_session);
}
g_free (toplevel_session->name);
g_free (toplevel_session);
}
}
static void
xdg_toplevel_session_destroy (struct wl_client *wl_client,
struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
xdg_toplevel_session_remove (struct wl_client *wl_client,
struct wl_resource *resource)
{
MetaWaylandXdgToplevelSession *toplevel_session =
wl_resource_get_user_data (resource);
MetaWaylandXdgSession *session = toplevel_session->session;
if (session)
{
g_signal_emit (session, signals[REMOVE_TOPLEVEL], 0, toplevel_session->name);
g_hash_table_remove (session->toplevels, toplevel_session->name);
}
wl_resource_destroy (resource);
}
static const struct xx_toplevel_session_v1_interface meta_xdg_toplevel_session_interface = {
xdg_toplevel_session_destroy,
xdg_toplevel_session_remove,
};
static void
xdg_toplevel_session_destructor (struct wl_resource *resource)
{
MetaWaylandXdgToplevelSession *toplevel_session =
wl_resource_get_user_data (resource);
meta_wayland_xdg_toplevel_session_unref (toplevel_session);
}
static MetaWaylandXdgToplevelSession *
meta_wayland_xdg_toplevel_session_new (MetaWaylandXdgSession *xdg_session,
MetaWaylandSurface *surface,
const char *name,
struct wl_client *wl_client,
uint32_t version,
uint32_t id)
{
MetaWaylandXdgToplevelSession *toplevel_session;
toplevel_session = g_new0 (MetaWaylandXdgToplevelSession, 1);
g_ref_count_init (&toplevel_session->ref_count);
toplevel_session->surface = surface;
toplevel_session->session = xdg_session;
toplevel_session->name = g_strdup (name);
toplevel_session->resource =
wl_resource_create (wl_client,
&xx_toplevel_session_v1_interface,
version, id);
wl_resource_set_implementation (toplevel_session->resource,
&meta_xdg_toplevel_session_interface,
meta_wayland_xdg_toplevel_session_ref (toplevel_session),
xdg_toplevel_session_destructor);
return toplevel_session;
}
static void
meta_wayland_xdg_toplevel_session_emit_restored (MetaWaylandXdgToplevelSession *toplevel_session)
{
MetaWaylandXdgToplevel *xdg_toplevel =
META_WAYLAND_XDG_TOPLEVEL (toplevel_session->surface->role);
struct wl_resource *xdg_toplevel_resource =
meta_wayland_xdg_toplevel_get_resource (xdg_toplevel);
xx_toplevel_session_v1_send_restored (toplevel_session->resource,
xdg_toplevel_resource);
}
static void
meta_wayland_xdg_session_dispose (GObject *object)
{
MetaWaylandXdgSession *session = META_WAYLAND_XDG_SESSION (object);
g_clear_pointer (&session->id, g_free);
g_clear_pointer (&session->toplevels, g_hash_table_unref);
G_OBJECT_CLASS (meta_wayland_xdg_session_parent_class)->dispose (object);
}
static void
meta_wayland_xdg_session_class_init (MetaWaylandXdgSessionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = meta_wayland_xdg_session_dispose;
signals[DESTROYED] =
g_signal_new ("destroyed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
NULL,
G_TYPE_NONE, 0);
signals[RESTORE_TOPLEVEL] =
g_signal_new ("restore-toplevel",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
g_signal_accumulator_true_handled,
NULL,
NULL,
G_TYPE_BOOLEAN, 2,
META_TYPE_WAYLAND_XDG_TOPLEVEL,
G_TYPE_STRING);
signals[SAVE_TOPLEVEL] =
g_signal_new ("save-toplevel",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 3,
META_TYPE_WAYLAND_XDG_TOPLEVEL,
G_TYPE_STRING,
META_TYPE_WINDOW);
signals[REMOVE_TOPLEVEL] =
g_signal_new ("remove-toplevel",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_STRING);
signals[DELETE] =
g_signal_new ("delete",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
meta_wayland_xdg_session_init (MetaWaylandXdgSession *session)
{
}
static void
xdg_session_destroy (struct wl_client *wl_client,
struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
xdg_session_remove (struct wl_client *wl_client,
struct wl_resource *resource)
{
MetaWaylandXdgSession *session =
META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
g_signal_emit (session, signals[DELETE], 0);
wl_resource_destroy (resource);
}
static void
on_window_unmanaging (MetaWindow *window,
MetaWaylandXdgToplevelSession *toplevel_session)
{
MetaWaylandXdgSession *session = toplevel_session->session;
if (session)
{
MetaWaylandXdgToplevel *xdg_toplevel =
META_WAYLAND_XDG_TOPLEVEL (toplevel_session->surface->role);
g_signal_emit (session, signals[SAVE_TOPLEVEL], 0,
xdg_toplevel, toplevel_session->name, window);
}
toplevel_session->surface = NULL;
}
static void
xdg_session_add_toplevel (struct wl_client *wl_client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *toplevel_resource,
const char *name)
{
MetaWaylandXdgSession *session =
META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
MetaWaylandXdgToplevel *xdg_toplevel =
wl_resource_get_user_data (toplevel_resource);
MetaWaylandSurfaceRole *surface_role;
MetaWaylandSurface *surface;
MetaWaylandXdgToplevelSession *toplevel_session;
MetaWindow *window;
if (g_hash_table_lookup (session->toplevels, name))
{
wl_resource_post_error (resource, XX_SESSION_V1_ERROR_NAME_IN_USE,
"Name of toplevel was already in use");
return;
}
surface_role = META_WAYLAND_SURFACE_ROLE (xdg_toplevel);
surface = meta_wayland_surface_role_get_surface (surface_role);
toplevel_session =
meta_wayland_xdg_toplevel_session_new (session, surface, name,
wl_client,
wl_resource_get_version (resource),
id);
g_hash_table_insert (session->toplevels, g_strdup (name), toplevel_session);
window = meta_wayland_surface_get_toplevel_window (surface);
if (window)
{
g_signal_connect (window, "unmanaging",
G_CALLBACK (on_window_unmanaging), toplevel_session);
}
}
static void
xdg_session_restore_toplevel (struct wl_client *wl_client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *toplevel_resource,
const char *name)
{
MetaWaylandXdgSession *session =
META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
MetaWaylandXdgToplevel *xdg_toplevel =
wl_resource_get_user_data (toplevel_resource);
MetaWaylandSurfaceRole *surface_role;
MetaWaylandSurface *surface;
MetaWaylandXdgToplevelSession *toplevel_session;
MetaWindow *window;
gboolean restored = FALSE;
if (g_hash_table_lookup (session->toplevels, name))
{
wl_resource_post_error (resource, XX_SESSION_V1_ERROR_NAME_IN_USE,
"Name of toplevel was already in use");
return;
}
surface_role = META_WAYLAND_SURFACE_ROLE (xdg_toplevel);
surface = meta_wayland_surface_role_get_surface (surface_role);
if (meta_wayland_surface_has_initial_commit (surface))
{
wl_resource_post_error (resource, XX_SESSION_V1_ERROR_ALREADY_MAPPED,
"Tried to restore an already mapped toplevel");
return;
}
toplevel_session =
meta_wayland_xdg_toplevel_session_new (session, surface, name,
wl_client,
wl_resource_get_version (resource),
id);
g_hash_table_insert (session->toplevels, g_strdup (name), toplevel_session);
window = meta_wayland_surface_get_toplevel_window (surface);
if (window)
{
g_signal_connect (window, "unmanaging",
G_CALLBACK (on_window_unmanaging), toplevel_session);
}
g_signal_emit (session,
signals[RESTORE_TOPLEVEL], 0,
xdg_toplevel, name, &restored);
if (restored)
meta_wayland_xdg_toplevel_session_emit_restored (toplevel_session);
}
static const struct xx_session_v1_interface meta_xdg_session_interface = {
xdg_session_destroy,
xdg_session_remove,
xdg_session_add_toplevel,
xdg_session_restore_toplevel,
};
static void
xdg_session_destructor (struct wl_resource *resource)
{
MetaWaylandXdgSession *session =
META_WAYLAND_XDG_SESSION (wl_resource_get_user_data (resource));
GHashTableIter iter;
gpointer value;
g_signal_emit (session, signals[DESTROYED], 0);
g_hash_table_iter_init (&iter, session->toplevels);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
MetaWaylandXdgToplevelSession *toplevel_session = value;
toplevel_session->session = NULL;
}
g_object_unref (session);
}
MetaWaylandXdgSession *
meta_wayland_xdg_session_new (MetaWaylandXdgSessionState *session_state,
struct wl_client *wl_client,
uint32_t version,
uint32_t id)
{
g_autoptr (MetaWaylandXdgSession) session = NULL;
session = g_object_new (META_TYPE_WAYLAND_XDG_SESSION, NULL);
session->id =
g_strdup (meta_session_state_get_name (META_SESSION_STATE (session_state)));
session->toplevels =
g_hash_table_new_full (g_str_hash, g_str_equal,
g_free,
(GDestroyNotify) meta_wayland_xdg_toplevel_session_unref);
session->resource = wl_resource_create (wl_client,
&xx_session_v1_interface,
version, id);
wl_resource_set_implementation (session->resource,
&meta_xdg_session_interface,
g_object_ref (session),
xdg_session_destructor);
return g_steal_pointer (&session);
}
const char *
meta_wayland_xdg_session_get_id (MetaWaylandXdgSession *session)
{
return session->id;
}
void
meta_wayland_xdg_session_emit_created (MetaWaylandXdgSession *session)
{
xx_session_v1_send_created (session->resource, session->id);
}
void
meta_wayland_xdg_session_emit_replaced (MetaWaylandXdgSession *session)
{
xx_session_v1_send_replaced (session->resource);
}
void
meta_wayland_xdg_session_emit_restored (MetaWaylandXdgSession *session)
{
xx_session_v1_send_restored (session->resource);
}
gboolean
meta_wayland_xdg_session_is_same_client (MetaWaylandXdgSession *session,
struct wl_client *client)
{
return wl_resource_get_client (session->resource) == client;
}