From 3fef77ad212b7e51bfd9ea96dd13ad50f911e1f2 Mon Sep 17 00:00:00 2001 From: Joan Torres Lopez Date: Wed, 5 Nov 2025 16:04:51 +0100 Subject: [PATCH] Add starting headless sessions with C tool Resolves: RHEL-126603 --- 0001-Add-headless-session-files.patch | 219 -- 0001-Introduce-gdm-new-session-tool.patch | 1937 +++++++++++++++++ ...ess-session-Fix-autostarting-on-boot.patch | 28 - gdm.spec | 15 +- 4 files changed, 1946 insertions(+), 253 deletions(-) delete mode 100644 0001-Add-headless-session-files.patch create mode 100644 0001-Introduce-gdm-new-session-tool.patch delete mode 100644 0001-headless-session-Fix-autostarting-on-boot.patch diff --git a/0001-Add-headless-session-files.patch b/0001-Add-headless-session-files.patch deleted file mode 100644 index 9ea57eb..0000000 --- a/0001-Add-headless-session-files.patch +++ /dev/null @@ -1,219 +0,0 @@ -From a8a0d952293337544da4681f0c896052eafd9d0f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20=C3=85dahl?= -Date: Fri, 5 Apr 2024 16:44:07 +0200 -Subject: [PATCH] Add headless session files - -It consists of a python script for running the session, and a systemd -system service template. ---- - data/gnome-headless-session@.service | 6 + - data/meson.build | 4 + - utils/gdm-headless-login-session | 157 +++++++++++++++++++++++++++ - utils/meson.build | 5 + - 4 files changed, 172 insertions(+) - create mode 100644 data/gnome-headless-session@.service - create mode 100644 utils/gdm-headless-login-session - -diff --git a/data/gnome-headless-session@.service b/data/gnome-headless-session@.service -new file mode 100644 -index 000000000..269d16288 ---- /dev/null -+++ b/data/gnome-headless-session@.service -@@ -0,0 +1,6 @@ -+[Unit] -+Description=Headless desktop session -+ -+[Service] -+ExecStart=/usr/libexec/gdm-headless-login-session --user=%i -+Restart=on-failure -diff --git a/data/meson.build b/data/meson.build -index 2211e98b5..2df07cd32 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -221,3 +221,7 @@ if get_option('gdm-xsession') - install_dir: gdmconfdir, - ) - endif -+ -+headless_session_service = install_data('gnome-headless-session@.service', -+ install_dir: systemd_systemunitdir, -+ ) -diff --git a/utils/gdm-headless-login-session b/utils/gdm-headless-login-session -new file mode 100644 -index 000000000..e108be523 ---- /dev/null -+++ b/utils/gdm-headless-login-session -@@ -0,0 +1,157 @@ -+#!/usr/bin/env python3 -+ -+import argparse -+import pam -+import pwd -+import os -+import signal -+import sys -+ -+import gi -+gi.require_version('AccountsService', '1.0') -+from gi.repository import AccountsService, GLib -+ -+def run_desktop_in_new_session(pam_environment, user, session_desktop, tty_input, tty_output): -+ keyfile = GLib.KeyFile() -+ keyfile.load_from_data_dirs(f'wayland-sessions/{session_desktop}.desktop', -+ GLib.KeyFileFlags.NONE) -+ -+ try: -+ can_run_headless = keyfile.get_boolean(GLib.KEY_FILE_DESKTOP_GROUP, -+ 'X-GDM-CanRunHeadless') -+ except GLib.GError: -+ raise Exception(f"Session {session_desktop} can't run headlessly") -+ -+ if not can_run_headless: -+ raise Exception(f"Session {session_desktop} can't run headlessly") -+ -+ executable = keyfile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, -+ GLib.KEY_FILE_DESKTOP_KEY_TRY_EXEC) -+ if GLib.find_program_in_path(executable) is None: -+ raise Exception(f"Invalid session {session_desktop}") -+ -+ command = keyfile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, -+ GLib.KEY_FILE_DESKTOP_KEY_EXEC) -+ [success, args] = GLib.shell_parse_argv(command) -+ -+ pam_handle = pam.pam() -+ -+ for key, value in pam_environment.items(): -+ pam_handle.putenv(f'{key}={value}') -+ -+ if not pam_handle.authenticate(user, '', service='gdm-autologin', call_end=False): -+ raise Exception("Authentication failed") -+ -+ for key, value in pam_environment.items(): -+ pam_handle.putenv(f'{key}={value}') -+ -+ if pam_handle.open_session() != pam.PAM_SUCCESS: -+ raise Exception("Failed to open PAM session") -+ -+ session_environment = os.environ.copy() -+ session_environment.update(pam_handle.getenvlist()) -+ -+ user_info = pwd.getpwnam(user) -+ uid = user_info.pw_uid -+ gid = user_info.pw_gid -+ -+ old_tty_output = os.fdopen(os.dup(2), 'w') -+ -+ pid = os.fork() -+ if pid == 0: -+ try: -+ os.setsid() -+ except OSError as e: -+ print(f"Could not create new pid session: {e}", file=old_tty_output) -+ -+ try: -+ os.dup2(tty_input.fileno(), 0) -+ os.dup2(tty_output.fileno(), 1) -+ os.dup2(tty_output.fileno(), 2) -+ except OSError as e: -+ print(f"Could not set up standard i/o: {e}", file=old_tty_output) -+ -+ try: -+ os.initgroups(user, gid) -+ os.setgid(gid) -+ os.setuid(uid); -+ except OSError as e: -+ print(f"Could not become user {user} (uid={uid}): {e}", file=old_tty_output) -+ -+ try: -+ os.execvpe(args[0], args, session_environment) -+ except OSError as e: -+ print(f"Could not run program \"{' '.join(arguments)}\": {e}", file=old_tty_output) -+ os._exit(1) -+ -+ -+ def signal_handler(sig, frame): -+ os.kill(pid, sig) -+ -+ signal.signal(signal.SIGTERM, signal_handler) -+ -+ try: -+ (_, exit_code) = os.waitpid(pid, 0); -+ except KeyboardInterrupt: -+ os.kill(pid, signal.SIGTERM) -+ except OSError as e: -+ print(f"Could not wait for program to finish: {e}", file=old_tty_output) -+ -+ if os.WIFEXITED(exit_code): -+ exit_code = os.WEXITSTATUS(exit_code) -+ else: -+ os.kill(os.getpid(), os.WTERMSIG(exit_code)) -+ old_tty_output.close() -+ -+ if pam_handle.close_session() != pam.PAM_SUCCESS: -+ raise Exception("Failed to close PAM session") -+ -+ pam_handle.end() -+ -+ return exit_code -+ -+def wait_for_user_data(user): -+ main_context = GLib.MainContext.default() -+ while not user.is_loaded(): -+ main_context.iteration(True) -+ -+def main(): -+ parser = argparse.ArgumentParser(description='Run a desktop session in a PAM session as a specified user.') -+ parser.add_argument('--user', help='Username for which to run the session') -+ -+ args = parser.parse_args() -+ -+ if args.user is None: -+ parser.print_usage() -+ sys.exit(1) -+ -+ try: -+ tty_path = '/dev/null' -+ -+ tty_input = open(tty_path, 'r') -+ tty_output = open(tty_path, 'w') -+ except OSError as e: -+ raise Exception(f"Error opening /dev/null as tty associated with VT {vt}: {e}") -+ -+ user_manager = AccountsService.UserManager().get_default() -+ user = user_manager.get_user(args.user) -+ wait_for_user_data(user) -+ session_desktop = user.get_session() -+ if not session_desktop: -+ session_desktop = 'gnome' -+ -+ pam_environment = {} -+ pam_environment['XDG_SESSION_TYPE'] = 'wayland' -+ pam_environment['XDG_SESSION_CLASS'] = 'user' -+ pam_environment['XDG_SESSION_DESKTOP'] = session_desktop -+ -+ try: -+ result = run_desktop_in_new_session(pam_environment, args.user, session_desktop, tty_input, tty_output) -+ except Exception as e: -+ raise Exception(f"Error running desktop session \"{session_desktop}\": {e}") -+ tty_input.close() -+ tty_output.close() -+ sys.exit(result) -+ -+if __name__ == '__main__': -+ main() -diff --git a/utils/meson.build b/utils/meson.build -index e4141fb13..57dd6519f 100644 ---- a/utils/meson.build -+++ b/utils/meson.build -@@ -65,3 +65,8 @@ if distro != 'none' - install_dir: get_option('libexecdir'), - ) - endif -+ -+gdm_headless_login_session = install_data('gdm-headless-login-session', -+ install_mode: 'rwxr-xr-x', -+ install_dir: get_option('libexecdir'), -+ ) --- -2.44.0 - diff --git a/0001-Introduce-gdm-new-session-tool.patch b/0001-Introduce-gdm-new-session-tool.patch new file mode 100644 index 0000000..496593f --- /dev/null +++ b/0001-Introduce-gdm-new-session-tool.patch @@ -0,0 +1,1937 @@ +From ad5b372db6d05698cf6c79f4b7fd98e1b03547f7 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 14:54:34 +0200 +Subject: [PATCH 1/8] common: Add find graphical sessions for username + +This will be used in the next commits to check if starting a new user +session is allowed or there's already one started. + +Part-of: +--- + common/gdm-common.c | 91 ++++++++++++++++++++++++++++++++------------- + common/gdm-common.h | 4 ++ + 2 files changed, 69 insertions(+), 26 deletions(-) + +diff --git a/common/gdm-common.c b/common/gdm-common.c +index 4e538a5e2..7263e6d39 100644 +--- a/common/gdm-common.c ++++ b/common/gdm-common.c +@@ -960,6 +960,64 @@ _systemd_session_is_active (const char *session_id) + return TRUE; + } + ++static gboolean ++find_graphical_sessions (const uid_t uid, ++ char ***out_sessions) ++{ ++ g_auto (GStrv) sessions = NULL; ++ g_autoptr (GStrvBuilder) builder = NULL; ++ int n_sessions; ++ ++ g_debug ("Finding a graphical session for user %d", uid); ++ ++ n_sessions = sd_uid_get_sessions (uid, ++ GDM_SYSTEMD_SESSION_REQUIRE_ONLINE, ++ &sessions); ++ if (n_sessions < 0) ++ return FALSE; ++ ++ builder = g_strv_builder_new (); ++ ++ for (int i = 0; i < n_sessions; ++i) { ++ g_debug ("Considering session '%s'", sessions[i]); ++ ++ if (!_systemd_session_is_graphical (sessions[i])) ++ continue; ++ ++ if (!_systemd_session_is_active (sessions[i])) ++ continue; ++ ++ g_strv_builder_add (builder, g_strdup (sessions[i])); ++ } ++ ++ *out_sessions = g_strv_builder_end (builder); ++ ++ return TRUE; ++} ++ ++gboolean ++gdm_find_graphical_sessions_for_username (const char *username, ++ char ***sessions, ++ GError **error) ++{ ++ struct passwd *pwent; ++ ++ gdm_get_pwent_for_name (username, &pwent); ++ if (pwent == NULL) { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Couldn't get pw entry for username %s", username); ++ return FALSE; ++ } ++ ++ if (!find_graphical_sessions (pwent->pw_uid, sessions)) { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Couldn't find sessions for username %s", username); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + gboolean + gdm_find_display_session (GPid pid, + const uid_t uid, +@@ -968,7 +1026,6 @@ gdm_find_display_session (GPid pid, + { + char *local_session_id = NULL; + g_auto(GStrv) sessions = NULL; +- int n_sessions; + int res; + + g_return_val_if_fail (out_session_id != NULL, FALSE); +@@ -993,13 +1050,7 @@ gdm_find_display_session (GPid pid, + pid, strerror (-res)); + } + +- g_debug ("Finding a graphical session for user %d", uid); +- +- n_sessions = sd_uid_get_sessions (uid, +- GDM_SYSTEMD_SESSION_REQUIRE_ONLINE, +- &sessions); +- +- if (n_sessions < 0) { ++ if (!find_graphical_sessions (uid, &sessions)) { + g_set_error (error, + GDM_COMMON_ERROR, + 0, +@@ -1008,23 +1059,7 @@ gdm_find_display_session (GPid pid, + return FALSE; + } + +- for (int i = 0; i < n_sessions; ++i) { +- g_debug ("Considering session '%s'", sessions[i]); +- +- if (!_systemd_session_is_graphical (sessions[i])) +- continue; +- +- if (!_systemd_session_is_active (sessions[i])) +- continue; +- +- /* +- * We get the sessions from newest to oldest, so take the last +- * one we find that's good +- */ +- local_session_id = sessions[i]; +- } +- +- if (local_session_id == NULL) { ++ if (g_strv_length (sessions) == 0) { + g_set_error (error, + GDM_COMMON_ERROR, + 0, +@@ -1033,7 +1068,11 @@ gdm_find_display_session (GPid pid, + return FALSE; + } + +- *out_session_id = g_strdup (local_session_id); ++ /* ++ * We get the sessions from newest to oldest, so take the last ++ * one we find that's good ++ */ ++ *out_session_id = g_strdup (sessions[0]); + + return TRUE; + } +diff --git a/common/gdm-common.h b/common/gdm-common.h +index 3e210b87b..641c76f2b 100644 +--- a/common/gdm-common.h ++++ b/common/gdm-common.h +@@ -81,6 +81,10 @@ gboolean gdm_get_login_window_session_id (const char *seat_id, + gboolean gdm_goto_login_session (GCancellable *cancellable, + GError **error); + ++gboolean gdm_find_graphical_sessions_for_username (const char *username, ++ char ***sessions, ++ GError **error); ++ + GPtrArray *gdm_get_script_environment (const char *username, + const char *display_name, + const char *display_hostname, +-- +2.51.0 + + +From a3630f535c72fabc1ff0ed5ba72cd2109baed4df Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 16:54:57 +0200 +Subject: [PATCH 2/8] display: Add GetSessionId method on its dbus iface + +This will be used when creating a new user session to track when it gets +destroyed. + +Part-of: +--- + daemon/gdm-display.c | 17 +++++++++++++++++ + daemon/gdm-display.xml | 3 +++ + data/gdm.conf.in | 3 +++ + 3 files changed, 23 insertions(+) + +diff --git a/daemon/gdm-display.c b/daemon/gdm-display.c +index 54e054808..41260cb8a 100644 +--- a/daemon/gdm-display.c ++++ b/daemon/gdm-display.c +@@ -1132,6 +1132,21 @@ handle_get_seat_id (GdmDBusDisplay *skeleton, + return TRUE; + } + ++static gboolean ++handle_get_session_id (GdmDBusDisplay *skeleton, ++ GDBusMethodInvocation *invocation, ++ GdmDisplay *self) ++{ ++ GdmDisplayPrivate *priv = gdm_display_get_instance_private (self); ++ ++ gdm_dbus_display_complete_get_session_id (skeleton, ++ invocation, ++ priv->session_id ? ++ priv->session_id : ""); ++ ++ return TRUE; ++} ++ + static gboolean + handle_get_x11_display_name (GdmDBusDisplay *skeleton, + GDBusMethodInvocation *invocation, +@@ -1197,6 +1212,8 @@ register_display (GdmDisplay *self) + G_CALLBACK (handle_get_remote_hostname), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-get-seat-id", + G_CALLBACK (handle_get_seat_id), self, 0); ++ g_signal_connect_object (priv->display_skeleton, "handle-get-session-id", ++ G_CALLBACK (handle_get_session_id), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-get-x11-display-name", + G_CALLBACK (handle_get_x11_display_name), self, 0); + g_signal_connect_object (priv->display_skeleton, "handle-is-local", +diff --git a/daemon/gdm-display.xml b/daemon/gdm-display.xml +index 3e9b5d86f..18bd2a8b3 100644 +--- a/daemon/gdm-display.xml ++++ b/daemon/gdm-display.xml +@@ -10,6 +10,9 @@ + + + ++ ++ ++ + + + +diff --git a/data/gdm.conf.in b/data/gdm.conf.in +index eda131e02..425143d52 100644 +--- a/data/gdm.conf.in ++++ b/data/gdm.conf.in +@@ -56,6 +56,9 @@ + ++ + +-- +2.51.0 + + +From 5dbd23f70423a37e4e50647c81bebae3ccb73592 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 17:03:36 +0200 +Subject: [PATCH 3/8] manager: Check all autologin details in the same function + +In the next commits autologin will be possible in a different way, +having all checks in the same function is needed for that. + +Part-of: +--- + daemon/gdm-manager.c | 84 +++++++++++++++----------------------------- + 1 file changed, 29 insertions(+), 55 deletions(-) + +diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c +index e048917..99e451c 100644 +--- a/daemon/gdm-manager.c ++++ b/daemon/gdm-manager.c +@@ -1324,45 +1324,38 @@ get_timed_login_details (GdmManager *manager, + } + + static gboolean +-get_automatic_login_details (GdmManager *manager, +- char **usernamep) ++get_automatic_login_details (GdmManager *manager, ++ GdmDisplay *display, ++ char **out_username) + { +- gboolean res; + gboolean enabled; +- char *username = NULL; ++ g_autofree char *seat_id = NULL; ++ g_autofree char *username = NULL; + +- enabled = FALSE; +- username = NULL; ++ g_object_get (G_OBJECT (display), ++ "seat-id", &seat_id, ++ NULL); + +- res = gdm_settings_direct_get_boolean (GDM_KEY_AUTO_LOGIN_ENABLE, &enabled); +- if (res && enabled) { +- res = gdm_settings_direct_get_string (GDM_KEY_AUTO_LOGIN_USER, &username); +- } ++ if (seat_id == NULL || !g_str_equal (seat_id, "seat0")) ++ return FALSE; + +- if (enabled && res && username != NULL && username[0] != '\0') { +- goto out; +- } ++ if (manager->did_automatic_login || manager->automatic_login_display != NULL) ++ return FALSE; + +- g_free (username); +- username = NULL; +- enabled = FALSE; ++ if (!gdm_settings_direct_get_boolean (GDM_KEY_AUTO_LOGIN_ENABLE, &enabled)) ++ return FALSE; + +- out: +- if (enabled) { +- g_debug ("GdmDisplay: Got automatic login details for display: %d %s", +- enabled, +- username); +- } else { +- g_debug ("GdmDisplay: Got automatic login details for display: 0"); +- } ++ if (!gdm_settings_direct_get_string (GDM_KEY_AUTO_LOGIN_USER, &username)) ++ return FALSE; + +- if (usernamep != NULL) { +- *usernamep = username; +- } else { +- g_free (username); +- } ++ if (!enabled || username == NULL || username[0] == '\0') ++ return FALSE; + +- return enabled; ++ g_debug ("GdmDisplay: Got automatic login details for user: %s", username); ++ ++ *out_username = g_steal_pointer (&username); ++ ++ return TRUE; + } + + static const char * +@@ -1496,25 +1489,12 @@ set_up_session (GdmManager *manager, + ActUserManager *user_manager; + ActUser *user; + gboolean loaded; +- gboolean seat_can_autologin = FALSE, seat_did_autologin = FALSE; +- gboolean autologin_enabled = FALSE; +- g_autofree char *seat_id = NULL; +- char *username = NULL; +- +- g_object_get (G_OBJECT (display), "seat-id", &seat_id, NULL); ++ gboolean autologin_enabled; ++ g_autofree char *username = NULL; + +- if (g_strcmp0 (seat_id, "seat0") == 0) +- seat_can_autologin = TRUE; +- +- if (manager->did_automatic_login || manager->automatic_login_display != NULL) +- seat_did_autologin = TRUE; +- +- if (seat_can_autologin && !seat_did_autologin) +- autologin_enabled = get_automatic_login_details (manager, &username); ++ autologin_enabled = get_automatic_login_details (manager, display, &username); + + if (!autologin_enabled) { +- g_free (username); +- + #ifdef HAVE_LIBXDMCP + if (GDM_IS_XDMCP_CHOOSER_DISPLAY (display)) { + set_up_chooser_session (manager, display); +@@ -1539,7 +1519,7 @@ set_up_session (GdmManager *manager, + operation = g_new (UsernameLookupOperation, 1); + operation->manager = g_object_ref (manager); + operation->display = g_object_ref (display); +- operation->username = username; ++ operation->username = g_steal_pointer (&username); + + g_signal_connect (user, + "notify::is-loaded", +@@ -2367,7 +2347,7 @@ on_session_conversation_started (GdmSession *session, + { + GdmDisplay *display; + gboolean enabled; +- char *username; ++ g_autofree char *username = NULL; + + g_debug ("GdmManager: session conversation started for service %s on session", service_name); + +@@ -2383,11 +2363,7 @@ on_session_conversation_started (GdmSession *session, + return; + } + +- if (!display_is_on_seat0 (display)) { +- return; +- } +- +- enabled = get_automatic_login_details (manager, &username); ++ enabled = get_automatic_login_details (manager, display, &username); + + if (! enabled) { + return; +@@ -2398,8 +2374,6 @@ on_session_conversation_started (GdmSession *session, + /* service_name will be "gdm-autologin" + */ + gdm_session_setup_for_user (session, service_name, username); +- +- g_free (username); + } + + static void +-- +2.51.0 + +From a61080680aa4206b172de0a0233aced0b41872f0 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 17:06:30 +0200 +Subject: [PATCH 4/8] display: Add "autologin-user" property + +This will be used to make it start a user session doing autologin instead of +a starting a greeter session. + +Part-of: +--- + daemon/gdm-display.c | 28 ++++++++++++++++++++++++++++ + daemon/gdm-manager.c | 5 +++++ + 2 files changed, 33 insertions(+) + +diff --git a/daemon/gdm-display.c b/daemon/gdm-display.c +index 41260cb8a..acf9b4076 100644 +--- a/daemon/gdm-display.c ++++ b/daemon/gdm-display.c +@@ -70,6 +70,8 @@ typedef struct _GdmDisplayPrivate + int status; + time_t creation_time; + ++ char *autologin_user; ++ + char *x11_cookie; + gsize x11_cookie_size; + GdmDisplayAccessFile *access_file; +@@ -119,6 +121,7 @@ enum { + PROP_IS_LOCAL, + PROP_LAUNCH_ENVIRONMENT, + PROP_IS_INITIAL, ++ PROP_AUTOLOGIN_USER, + PROP_ALLOW_TIMED_LOGIN, + PROP_HAVE_EXISTING_USER_ACCOUNTS, + PROP_DOING_INITIAL_SETUP, +@@ -872,6 +875,17 @@ _gdm_display_set_is_local (GdmDisplay *self, + priv->is_local = is_local; + } + ++static void ++_gdm_display_set_autologin_user (GdmDisplay *self, ++ const char *user) ++{ ++ GdmDisplayPrivate *priv; ++ ++ priv = gdm_display_get_instance_private (self); ++ g_debug ("GdmDisplay: autologin user: %s", user); ++ g_set_str (&priv->autologin_user, user); ++} ++ + static void + _gdm_display_set_session_registered (GdmDisplay *self, + gboolean registered) +@@ -979,6 +993,9 @@ gdm_display_set_property (GObject *object, + case PROP_IS_LOCAL: + _gdm_display_set_is_local (self, g_value_get_boolean (value)); + break; ++ case PROP_AUTOLOGIN_USER: ++ _gdm_display_set_autologin_user (self, g_value_get_string (value)); ++ break; + case PROP_ALLOW_TIMED_LOGIN: + _gdm_display_set_allow_timed_login (self, g_value_get_boolean (value)); + break; +@@ -1051,6 +1068,9 @@ gdm_display_get_property (GObject *object, + case PROP_IS_LOCAL: + g_value_set_boolean (value, priv->is_local); + break; ++ case PROP_AUTOLOGIN_USER: ++ g_value_set_string (value, priv->autologin_user); ++ break; + case PROP_IS_CONNECTED: + #ifdef ENABLE_X11_SUPPORT + g_value_set_boolean (value, priv->xcb_connection != NULL); +@@ -1272,6 +1292,7 @@ gdm_display_dispose (GObject *object) + g_clear_handle_id (&priv->finish_idle_id, g_source_remove); + g_clear_object (&priv->launch_environment); + g_clear_pointer (&priv->supported_session_types, g_strfreev); ++ g_clear_pointer (&priv->autologin_user, g_free); + + g_warn_if_fail (priv->status != GDM_DISPLAY_MANAGED); + g_warn_if_fail (priv->user_access_file == NULL); +@@ -1358,6 +1379,13 @@ gdm_display_class_init (GdmDisplayClass *klass) + NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); ++ g_object_class_install_property (object_class, ++ PROP_AUTOLOGIN_USER, ++ g_param_spec_string ("autologin-user", ++ NULL, ++ NULL, ++ NULL, ++ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_ALLOW_TIMED_LOGIN, + g_param_spec_boolean ("allow-timed-login", +diff --git a/daemon/gdm-manager.c b/daemon/gdm-manager.c +index f1d966152..e455dad36 100644 +--- a/daemon/gdm-manager.c ++++ b/daemon/gdm-manager.c +@@ -1314,8 +1314,12 @@ get_automatic_login_details (GdmManager *manager, + + g_object_get (G_OBJECT (display), + "seat-id", &seat_id, ++ "autologin-user", &username, + NULL); + ++ if (username != NULL) ++ goto out; ++ + if (seat_id == NULL || !g_str_equal (seat_id, "seat0")) + return FALSE; + +@@ -1331,6 +1335,7 @@ get_automatic_login_details (GdmManager *manager, + if (!enabled || username == NULL || username[0] == '\0') + return FALSE; + ++out: + g_debug ("GdmDisplay: Got automatic login details for user: %s", username); + + *out_username = g_steal_pointer (&username); +-- +2.51.0 + + +From 3dfde2fb65cb4fec6aad9cd624608992f21c7080 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 17:27:07 +0200 +Subject: [PATCH 5/8] display-factories: Some small cleanups + +- Make the naming style of creator funcs the same. +- Make private local_display_factory_create_display func. +- Remove on remote factory unused display status changed callback. +- Return dbus method invocation enum on dbus handlers. + +Part-of: +--- + daemon/gdm-local-display-factory.c | 52 +++++++++++++---------------- + daemon/gdm-local-display-factory.h | 3 -- + daemon/gdm-remote-display-factory.c | 27 +++------------ + 3 files changed, 29 insertions(+), 53 deletions(-) + +diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c +index 23bc8a585..48c08cfa1 100644 +--- a/daemon/gdm-local-display-factory.c ++++ b/daemon/gdm-local-display-factory.c +@@ -395,21 +395,16 @@ store_display (GdmLocalDisplayFactory *factory, + /org/gnome/DisplayManager/Manager \ + org.gnome.DisplayManager.Manager.GetDisplays + */ +-gboolean +-gdm_local_display_factory_create_transient_display (GdmLocalDisplayFactory *factory, +- char **id, +- GError **error) ++static gboolean ++gdm_local_display_factory_create_display (GdmLocalDisplayFactory *factory, ++ char **id, ++ GError **error) + { +- gboolean ret; +- GdmDisplay *display = NULL; +- gboolean is_initial = FALSE; ++ gboolean is_initial = FALSE; ++ g_autoptr (GdmDisplay) display = NULL; + g_autofree gchar *preferred_display_server = NULL; + +- g_return_val_if_fail (GDM_IS_LOCAL_DISPLAY_FACTORY (factory), FALSE); +- +- ret = FALSE; +- +- g_debug ("GdmLocalDisplayFactory: Creating transient display"); ++ g_debug ("GdmLocalDisplayFactory: Creating local display"); + + preferred_display_server = get_preferred_display_server (factory); + +@@ -460,22 +455,23 @@ gdm_local_display_factory_create_transient_display (GdmLocalDisplayFactory *fact + + store_display (factory, display); + +- if (! gdm_display_manage (display)) { +- display = NULL; +- goto out; ++ if (!gdm_display_manage (display)) { ++ g_set_error_literal (error, ++ GDM_DISPLAY_ERROR, ++ GDM_DISPLAY_ERROR_GENERAL, ++ "Failed managing display"); ++ return FALSE; + } + +- if (! gdm_display_get_id (display, id, NULL)) { +- display = NULL; +- goto out; ++ if (!gdm_display_get_id (display, id, NULL)) { ++ g_set_error_literal (error, ++ GDM_DISPLAY_ERROR, ++ GDM_DISPLAY_ERROR_GENERAL, ++ "Failed getting display id"); ++ return FALSE; + } + +- ret = TRUE; +- out: +- /* ref either held by store or not at all */ +- g_object_unref (display); +- +- return ret; ++ return TRUE; + } + + static void +@@ -1590,16 +1586,16 @@ handle_create_transient_display (GdmDBusLocalDisplayFactory *skeleton, + g_autofree char *id = NULL; + gboolean created; + +- created = gdm_local_display_factory_create_transient_display (factory, +- &id, +- &error); ++ created = gdm_local_display_factory_create_display (factory, ++ &id, ++ &error); + if (!created) { + g_dbus_method_invocation_return_gerror (invocation, error); + } else { + gdm_dbus_local_display_factory_complete_create_transient_display (skeleton, invocation, id); + } + +- return TRUE; ++ return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + static gboolean +diff --git a/daemon/gdm-local-display-factory.h b/daemon/gdm-local-display-factory.h +index 49c7140f9..63da3c69c 100644 +--- a/daemon/gdm-local-display-factory.h ++++ b/daemon/gdm-local-display-factory.h +@@ -43,9 +43,6 @@ GQuark gdm_local_display_factory_error_quark (v + + GdmLocalDisplayFactory * gdm_local_display_factory_new (GdmDisplayStore *display_store); + +-gboolean gdm_local_display_factory_create_transient_display (GdmLocalDisplayFactory *factory, +- char **id, +- GError **error); + G_END_DECLS + + #endif /* __GDM_LOCAL_DISPLAY_FACTORY_H */ +diff --git a/daemon/gdm-remote-display-factory.c b/daemon/gdm-remote-display-factory.c +index deb6d402b..d3e2afe89 100644 +--- a/daemon/gdm-remote-display-factory.c ++++ b/daemon/gdm-remote-display-factory.c +@@ -47,20 +47,11 @@ static gpointer remote_display_factory_object = NULL; + + G_DEFINE_TYPE (GdmRemoteDisplayFactory, gdm_remote_display_factory, GDM_TYPE_DISPLAY_FACTORY) + +-static void +-on_display_status_changed (GdmDisplay *display, +- GParamSpec *arg1, +- GdmRemoteDisplayFactory *factory) +-{ +- g_debug ("GdmRemoteDisplayFactory: remote display status changed: %d", +- gdm_display_get_status (display)); +-} +- + static gboolean +-gdm_remote_display_factory_create_remote_display (GdmRemoteDisplayFactory *factory, +- const char *remote_id) ++gdm_remote_display_factory_create_display (GdmRemoteDisplayFactory *factory, ++ const char *remote_id) + { +- GdmDisplay *display = NULL; ++ g_autoptr (GdmDisplay) display = NULL; + GdmDisplayStore *store; + + g_debug ("GdmRemoteDisplayFactory: Creating remote display"); +@@ -72,17 +63,9 @@ gdm_remote_display_factory_create_remote_display (GdmRemoteDisplayFactory *facto + + if (!gdm_display_prepare (display)) { + gdm_display_unmanage (display); +- g_object_unref (display); + return FALSE; + } + +- g_signal_connect_after (display, +- "notify::status", +- G_CALLBACK (on_display_status_changed), +- factory); +- +- g_object_unref (display); +- + return TRUE; + } + +@@ -92,7 +75,7 @@ handle_create_remote_display (GdmDBusRemoteDisplayFactory *skeleton, + const char *remote_id, + GdmRemoteDisplayFactory *factory) + { +- if (!gdm_remote_display_factory_create_remote_display (factory, remote_id)) ++ if (!gdm_remote_display_factory_create_display (factory, remote_id)) + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, +@@ -101,7 +84,7 @@ handle_create_remote_display (GdmDBusRemoteDisplayFactory *skeleton, + gdm_dbus_remote_display_factory_complete_create_remote_display (factory->skeleton, + invocation); + +- return TRUE; ++ return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + static gboolean +-- +2.51.0 + + +From 1cfc7a686510b9fe655586ca891ab389cbeaeffc Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 17:34:20 +0200 +Subject: [PATCH 6/8] display-factories: Add UserDisplay on its + dbus ifaces + +This allows creating or destroying a graphical user session either locally or +headlessly. + +It uses a polkit policy to allow only processes in GDM group to call +these methods. + +In next commits the policy rule will be enhanced to only authorize processes +in GDM group running from specific systemd system units. + +Part-of: +--- + daemon/gdm-display-factory.c | 171 ++++++++++++++++++++++++++ + daemon/gdm-display-factory.h | 9 ++ + daemon/gdm-local-display-factory.c | 84 +++++++++++++ + daemon/gdm-local-display-factory.xml | 6 + + daemon/gdm-remote-display-factory.c | 90 +++++++++++++- + daemon/gdm-remote-display-factory.xml | 6 + + daemon/meson.build | 1 + + data/meson.build | 14 +++ + data/org.gnome.displaymanager.policy | 20 +++ + data/polkit-gdm.rules.in | 8 ++ + meson.build | 1 + + 11 files changed, 409 insertions(+), 1 deletion(-) + create mode 100644 data/org.gnome.displaymanager.policy + create mode 100644 data/polkit-gdm.rules.in + +diff --git a/daemon/gdm-display-factory.c b/daemon/gdm-display-factory.c +index 8ff40ce..640db4a 100644 +--- a/daemon/gdm-display-factory.c ++++ b/daemon/gdm-display-factory.c +@@ -26,14 +26,22 @@ + #include + #include + #include ++#include + + #include "gdm-display-factory.h" ++#include "gdm-common.h" + #include "gdm-display-store.h" + ++#define GDM_DISPLAY_FACTORY_MANAGE_DISPLAYS_POLKIT_ACTION "org.gnome.displaymanager.displayfactory.manage-user-displays" ++ + typedef struct _GdmDisplayFactoryPrivate + { + GdmDisplayStore *display_store; + guint purge_displays_id; ++ ++ GHashTable *display_creation_users; ++ ++ PolkitAuthority *authority; + } GdmDisplayFactoryPrivate; + + enum { +@@ -142,6 +150,157 @@ gdm_display_factory_stop (GdmDisplayFactory *factory) + return ret; + } + ++static gboolean ++ensure_polkit_authority (GdmDisplayFactory *factory, ++ GError **error) ++{ ++ GdmDisplayFactoryPrivate *priv; ++ ++ priv = gdm_display_factory_get_instance_private (factory); ++ ++ if (priv->authority) ++ return TRUE; ++ ++ priv->authority = polkit_authority_get_sync (NULL, error); ++ ++ return priv->authority != NULL; ++} ++ ++gboolean ++gdm_display_factory_authorize_manage_user_displays (GdmDisplayFactory *factory, ++ GDBusMethodInvocation *invocation, ++ GError **error) ++{ ++ g_autoptr (PolkitAuthorizationResult) result = NULL; ++ g_autoptr (PolkitSubject) subject = NULL; ++ g_autoptr (GError) local_error = NULL; ++ const char *action; ++ const char *method; ++ const char *sender; ++ PolkitCheckAuthorizationFlags flags; ++ GdmDisplayFactoryPrivate *priv; ++ ++ g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), FALSE); ++ ++ method = g_dbus_method_invocation_get_method_name (invocation); ++ if (g_strcmp0 (method, "CreateUserDisplay") != 0 && ++ g_strcmp0 (method, "DestroyUserDisplay") != 0) ++ return TRUE; ++ ++ if (!ensure_polkit_authority (factory, &local_error)) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "Error getting polkit authority: %s", ++ local_error->message); ++ return FALSE; ++ } ++ ++ priv = gdm_display_factory_get_instance_private (factory); ++ ++ sender = g_dbus_method_invocation_get_sender (invocation); ++ subject = polkit_system_bus_name_new (sender); ++ action = GDM_DISPLAY_FACTORY_MANAGE_DISPLAYS_POLKIT_ACTION; ++ flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; ++ result = polkit_authority_check_authorization_sync (priv->authority, ++ subject, action, ++ NULL, flags, NULL, ++ &local_error); ++ if (!result) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "Failed to check authorization: %s", ++ local_error->message); ++ return FALSE; ++ } ++ ++ if (!polkit_authorization_result_get_is_authorized (result)) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "Not authorized for action %s", action); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++gboolean ++gdm_display_factory_on_user_display_creation (GdmDisplayFactory *factory, ++ const char *user, ++ GError **error) ++{ ++ g_auto (GStrv) sessions = NULL; ++ GdmDisplayFactoryPrivate *priv; ++ ++ g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), FALSE); ++ ++ priv = gdm_display_factory_get_instance_private (factory); ++ ++ if (g_hash_table_lookup (priv->display_creation_users, user) != NULL) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "A display for user %s has already been created", user); ++ return FALSE; ++ } ++ ++ if (!gdm_find_graphical_sessions_for_username (user, &sessions, error)) ++ return FALSE; ++ ++ if (g_strv_length (sessions) > 0) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "There's already an opened session for user %s", user); ++ return FALSE; ++ } ++ ++ g_hash_table_insert (priv->display_creation_users, g_strdup (user), NULL); ++ ++ return TRUE; ++} ++ ++gboolean ++gdm_display_factory_on_user_display_destruction (GdmDisplayFactory *factory, ++ const char *user, ++ GError **error) ++{ ++ g_auto (GStrv) session_ids = NULL; ++ g_autoptr (GError) local_error = NULL; ++ g_autoptr (GDBusConnection) connection = NULL; ++ GdmDisplayFactoryPrivate *priv; ++ int i; ++ ++ g_return_val_if_fail (GDM_IS_DISPLAY_FACTORY (factory), FALSE); ++ ++ priv = gdm_display_factory_get_instance_private (factory); ++ ++ if (!g_hash_table_remove (priv->display_creation_users, user)) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "A display for user %s wasn't created", user); ++ return FALSE; ++ } ++ ++ if (!gdm_find_graphical_sessions_for_username (user, &session_ids, error)) ++ return FALSE; ++ ++ if (g_strv_length (session_ids) == 0) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "There's no display for user %s", user); ++ return FALSE; ++ } ++ ++ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error); ++ if (connection == NULL) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "Failed getting system bus %s", local_error->message); ++ return FALSE; ++ } ++ ++ for (i = 0; session_ids[i] != NULL; i++) { ++ if (!gdm_terminate_session_by_id (connection, NULL, session_ids[i])) { ++ g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, ++ "Failed to terminate session %s", ++ session_ids[i]); ++ return FALSE; ++ } ++ } ++ ++ return TRUE; ++} ++ + static void + gdm_display_factory_set_display_store (GdmDisplayFactory *factory, + GdmDisplayStore *display_store) +@@ -219,6 +378,14 @@ gdm_display_factory_class_init (GdmDisplayFactoryClass *klass) + static void + gdm_display_factory_init (GdmDisplayFactory *factory) + { ++ GdmDisplayFactoryPrivate *priv; ++ ++ priv = gdm_display_factory_get_instance_private (factory); ++ ++ priv->display_creation_users = g_hash_table_new_full (g_str_hash, ++ g_str_equal, ++ (GDestroyNotify) g_free, ++ NULL); + } + + static void +@@ -235,7 +402,11 @@ gdm_display_factory_finalize (GObject *object) + + g_return_if_fail (priv != NULL); + ++ g_clear_pointer (&priv->display_creation_users, g_hash_table_destroy); ++ + g_clear_handle_id (&priv->purge_displays_id, g_source_remove); + ++ g_clear_object (&priv->authority); ++ + G_OBJECT_CLASS (gdm_display_factory_parent_class)->finalize (object); + } +diff --git a/daemon/gdm-display-factory.h b/daemon/gdm-display-factory.h +index b17cb1c..afb2b0a 100644 +--- a/daemon/gdm-display-factory.h ++++ b/daemon/gdm-display-factory.h +@@ -53,6 +53,15 @@ gboolean gdm_display_factory_start (GdmDispl + gboolean gdm_display_factory_stop (GdmDisplayFactory *manager); + GdmDisplayStore * gdm_display_factory_get_display_store (GdmDisplayFactory *manager); + void gdm_display_factory_queue_purge_displays (GdmDisplayFactory *manager); ++gboolean gdm_display_factory_authorize_manage_user_displays (GdmDisplayFactory *factory, ++ GDBusMethodInvocation *invocation, ++ GError **error); ++gboolean gdm_display_factory_on_user_display_creation (GdmDisplayFactory *factory, ++ const char *user, ++ GError **error); ++gboolean gdm_display_factory_on_user_display_destruction (GdmDisplayFactory *factory, ++ const char *user, ++ GError **error); + + G_END_DECLS + +diff --git a/daemon/gdm-local-display-factory.c b/daemon/gdm-local-display-factory.c +index 7fd3db5..2d4f2c0 100644 +--- a/daemon/gdm-local-display-factory.c ++++ b/daemon/gdm-local-display-factory.c +@@ -398,6 +398,7 @@ store_display (GdmLocalDisplayFactory *factory, + */ + static gboolean + gdm_local_display_factory_create_display (GdmLocalDisplayFactory *factory, ++ const char *autologin_user, + char **id, + GError **error) + { +@@ -454,6 +455,7 @@ gdm_local_display_factory_create_display (GdmLocalDisplayFactory *factory, + "seat-id", "seat0", + "allow-timed-login", FALSE, + "is-initial", is_initial, ++ "autologin-user", autologin_user, + NULL); + + store_display (factory, display); +@@ -1603,6 +1605,7 @@ handle_create_transient_display (GdmDBusLocalDisplayFactory *skeleton, + gboolean created; + + created = gdm_local_display_factory_create_display (factory, ++ NULL, + &id, + &error); + if (!created) { +@@ -1614,6 +1617,73 @@ handle_create_transient_display (GdmDBusLocalDisplayFactory *skeleton, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++static gboolean ++handle_create_user_display (GdmDBusLocalDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ const char *user, ++ GdmLocalDisplayFactory *factory) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_on_user_display_creation (GDM_DISPLAY_FACTORY (factory), ++ user, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!gdm_local_display_factory_create_display (factory, ++ user, ++ NULL, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ gdm_dbus_local_display_factory_complete_create_user_display (skeleton, ++ invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++handle_destroy_user_display (GdmDBusLocalDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ const char *user, ++ GdmLocalDisplayFactory *factory) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_on_user_display_destruction (GDM_DISPLAY_FACTORY (factory), ++ user, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ gdm_dbus_local_display_factory_complete_destroy_user_display (skeleton, ++ invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++on_authorize_method (GdmDBusLocalDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ GdmLocalDisplayFactory *factory) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_authorize_manage_user_displays (GDM_DISPLAY_FACTORY (factory), ++ invocation, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static gboolean + register_factory (GdmLocalDisplayFactory *factory) + { +@@ -1634,6 +1704,20 @@ register_factory (GdmLocalDisplayFactory *factory) + G_CALLBACK (handle_create_transient_display), + factory); + ++ g_signal_connect (factory->skeleton, ++ "handle-create-user-display", ++ G_CALLBACK (handle_create_user_display), ++ factory); ++ ++ g_signal_connect (factory->skeleton, ++ "handle-destroy-user-display", ++ G_CALLBACK (handle_destroy_user_display), ++ factory); ++ ++ g_signal_connect (factory->skeleton, "g-authorize-method", ++ G_CALLBACK (on_authorize_method), ++ factory); ++ + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (factory->skeleton), + factory->connection, + GDM_LOCAL_DISPLAY_FACTORY_DBUS_PATH, +diff --git a/daemon/gdm-local-display-factory.xml b/daemon/gdm-local-display-factory.xml +index 515d991..2241bc4 100644 +--- a/daemon/gdm-local-display-factory.xml ++++ b/daemon/gdm-local-display-factory.xml +@@ -4,5 +4,11 @@ + + + ++ ++ ++ ++ ++ ++ + + +diff --git a/daemon/gdm-remote-display-factory.c b/daemon/gdm-remote-display-factory.c +index d3e2afe..7fb50ef 100644 +--- a/daemon/gdm-remote-display-factory.c ++++ b/daemon/gdm-remote-display-factory.c +@@ -20,6 +20,7 @@ + + #include "config.h" + ++#include "gdm-common.h" + #include "gdm-remote-display.h" + #include "gdm-remote-display-factory.h" + #include "gdm-remote-display-factory-glue.h" +@@ -49,6 +50,7 @@ G_DEFINE_TYPE (GdmRemoteDisplayFactory, gdm_remote_display_factory, GDM_TYPE_DIS + + static gboolean + gdm_remote_display_factory_create_display (GdmRemoteDisplayFactory *factory, ++ const char *autologin_user, + const char *remote_id) + { + g_autoptr (GdmDisplay) display = NULL; +@@ -58,6 +60,10 @@ gdm_remote_display_factory_create_display (GdmRemoteDisplayFactory *factory, + + display = gdm_remote_display_new (remote_id); + ++ g_object_set (display, ++ "autologin-user", autologin_user, ++ NULL); ++ + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_add (store, display); + +@@ -75,7 +81,7 @@ handle_create_remote_display (GdmDBusRemoteDisplayFactory *skeleton, + const char *remote_id, + GdmRemoteDisplayFactory *factory) + { +- if (!gdm_remote_display_factory_create_display (factory, remote_id)) ++ if (!gdm_remote_display_factory_create_display (factory, NULL, remote_id)) + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, +@@ -87,6 +93,74 @@ handle_create_remote_display (GdmDBusRemoteDisplayFactory *skeleton, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++static gboolean ++handle_create_user_display (GdmDBusRemoteDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ const char *user, ++ GdmRemoteDisplayFactory *factory) ++{ ++ g_auto (GStrv) sessions = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_on_user_display_creation (GDM_DISPLAY_FACTORY (factory), ++ user, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!gdm_remote_display_factory_create_display (factory, user, NULL)) { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Error creating remote display"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ gdm_dbus_remote_display_factory_complete_create_user_display (factory->skeleton, ++ invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++handle_destroy_user_display (GdmDBusRemoteDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ const char *user, ++ GdmRemoteDisplayFactory *factory) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_on_user_display_destruction (GDM_DISPLAY_FACTORY (factory), ++ user, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ gdm_dbus_remote_display_factory_complete_destroy_user_display (skeleton, ++ invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ ++static gboolean ++on_authorize_method (GdmDBusRemoteDisplayFactory *skeleton, ++ GDBusMethodInvocation *invocation, ++ GdmRemoteDisplayFactory *factory) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ if (!gdm_display_factory_authorize_manage_user_displays (GDM_DISPLAY_FACTORY (factory), ++ invocation, ++ &error)) { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static gboolean + register_factory (GdmRemoteDisplayFactory *factory) + { +@@ -106,6 +180,20 @@ register_factory (GdmRemoteDisplayFactory *factory) + G_CALLBACK (handle_create_remote_display), + factory); + ++ g_signal_connect (factory->skeleton, ++ "handle-create-user-display", ++ G_CALLBACK (handle_create_user_display), ++ factory); ++ ++ g_signal_connect (factory->skeleton, ++ "handle-destroy-user-display", ++ G_CALLBACK (handle_destroy_user_display), ++ factory); ++ ++ g_signal_connect (factory->skeleton, "g-authorize-method", ++ G_CALLBACK (on_authorize_method), ++ factory); ++ + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (factory->skeleton), + factory->connection, + GDM_REMOTE_DISPLAY_FACTORY_DBUS_PATH, +diff --git a/daemon/gdm-remote-display-factory.xml b/daemon/gdm-remote-display-factory.xml +index 84ce477..27cb9d1 100644 +--- a/daemon/gdm-remote-display-factory.xml ++++ b/daemon/gdm-remote-display-factory.xml +@@ -4,5 +4,11 @@ + + + ++ ++ ++ ++ ++ ++ + + +diff --git a/daemon/meson.build b/daemon/meson.build +index 31f9baa..39e93ad 100644 +--- a/daemon/meson.build ++++ b/daemon/meson.build +@@ -74,6 +74,7 @@ gdm_daemon_deps = [ + gio_unix_dep, + json_glib, + libpam_dep, ++ polkit_dep, + ] + if have_x11_support + gdm_daemon_deps += [ +diff --git a/data/meson.build b/data/meson.build +index 0653029..06b63e7 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -223,3 +223,17 @@ if get_option('gdm-xsession') and have_x11_support + install_dir: gdmconfdir, + ) + endif ++ ++# Polkit ++configure_file( ++ input: 'polkit-gdm.rules.in', ++ output: '20-gdm.rules', ++ configuration: { ++ 'GDM_USERNAME': get_option('user'), ++ }, ++ install_dir: get_option('datadir') / 'polkit-1' / 'rules.d', ++) ++install_data( ++ 'org.gnome.displaymanager.policy', ++ install_dir: get_option('datadir') / 'polkit-1' / 'actions', ++) +diff --git a/data/org.gnome.displaymanager.policy b/data/org.gnome.displaymanager.policy +new file mode 100644 +index 0000000..e65da39 +--- /dev/null ++++ b/data/org.gnome.displaymanager.policy +@@ -0,0 +1,20 @@ ++ ++ ++ ++ ++ The GNOME Project ++ http://www.gnome.org/ ++ ++ ++ Allow managing user sessions ++ Authentication is required to start or stop a user session ++ ++ auth_admin ++ auth_admin ++ auth_admin ++ ++ ++ ++ +diff --git a/data/polkit-gdm.rules.in b/data/polkit-gdm.rules.in +new file mode 100644 +index 0000000..c9ef490 +--- /dev/null ++++ b/data/polkit-gdm.rules.in +@@ -0,0 +1,8 @@ ++polkit.addRule(function(action, subject) { ++ if (subject.user !== "@GDM_USERNAME@") ++ return undefined; ++ ++ if (action.id == "org.gnome.displaymanager.displayfactory.manage-user-displays") { ++ return polkit.Result.YES; ++ } ++}); +diff --git a/meson.build b/meson.build +index 623ec15..f3896df 100644 +--- a/meson.build ++++ b/meson.build +@@ -69,6 +69,7 @@ json_glib = dependency('json-glib-1.0', version: '>= 1.2.0', + accountsservice_dep = dependency('accountsservice', version: '>= 0.6.35') + keyutils_dep = dependency('libkeyutils', required: false) + libselinux_dep = dependency('libselinux', required: get_option('selinux')) ++polkit_dep = dependency('polkit-gobject-1') + + # udev + if udev_dir == '' +-- +2.51.0 + +From 812416db531899b85c69626ad9664d849246a47b Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Thu, 15 May 2025 21:12:20 +0200 +Subject: [PATCH 7/8] Introduce gdm-new-session + +This daemon/tool allows starting a user session either locally or headlessly. + +It communicates with the GDM daemon to start/stop the sessions using +the dbus ifaces RemoteDisplayFactory and LocalDisplayFactory. + +It watches the GDM Displays dbus object manager to: + 1. Check when a new session is created to ensure it has + successfully started the session for the user. + 2. Check when the session is finalized to terminate itself. + +When it finds out it has successfully started the session, it stores the +session_id. This allows terminating the session in the case this is +terminated. + +Install also a service that allows starting a headless user session: +gnome-headless-session@.service + +This provides the same functionality than TigerVNC vncserver@.service: + vncserver@.service allows to start a headless Xvnc session for a user. + There's a conf file that maps a display number and how that display + session should be, its username... + It is started as a system service. e.g. vncserver@:1.service + +In the case of gnome-headless-session@.service, there's no conf file. +It is a system service that starts a headless gnome desktop session +for the user passed as the argument. e.g. gnome-headless-session@joan.service + +In order to get remote access, it is needed to: + 1. Configure gnome-remote-desktop for the user with grdctl --headless ... + 2. Enable gnome-remote-desktop-headless.service. + +Now the polkit rule to use the dbus methods for create/destroy user +displays is updated and only authorizes processes run from the unit +service gnome-headless-session@.service and for the user passed as the. +argument. + +Part-of: +--- + daemon/gdm-display-factory.c | 10 +- + data/gnome-headless-session@.service.in | 34 +++ + data/meson.build | 10 + + data/polkit-gdm.rules.in | 9 +- + utils/gdm-new-session.c | 293 ++++++++++++++++++++++++ + utils/meson.build | 15 ++ + 6 files changed, 369 insertions(+), 2 deletions(-) + create mode 100644 data/gnome-headless-session@.service.in + create mode 100644 utils/gdm-new-session.c + +diff --git a/daemon/gdm-display-factory.c b/daemon/gdm-display-factory.c +index 640db4a..ca05f96 100644 +--- a/daemon/gdm-display-factory.c ++++ b/daemon/gdm-display-factory.c +@@ -173,10 +173,13 @@ gdm_display_factory_authorize_manage_user_displays (GdmDisplayFactory *fact + { + g_autoptr (PolkitAuthorizationResult) result = NULL; + g_autoptr (PolkitSubject) subject = NULL; ++ g_autoptr (PolkitDetails) details = NULL; + g_autoptr (GError) local_error = NULL; ++ const char *user = NULL; + const char *action; + const char *method; + const char *sender; ++ GVariant *parameters; + PolkitCheckAuthorizationFlags flags; + GdmDisplayFactoryPrivate *priv; + +@@ -196,13 +199,18 @@ gdm_display_factory_authorize_manage_user_displays (GdmDisplayFactory *fact + + priv = gdm_display_factory_get_instance_private (factory); + ++ parameters = g_dbus_method_invocation_get_parameters (invocation); ++ g_variant_get (parameters, "(s)", &user); ++ details = polkit_details_new (); ++ polkit_details_insert (details, "user", user); ++ + sender = g_dbus_method_invocation_get_sender (invocation); + subject = polkit_system_bus_name_new (sender); + action = GDM_DISPLAY_FACTORY_MANAGE_DISPLAYS_POLKIT_ACTION; + flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; + result = polkit_authority_check_authorization_sync (priv->authority, + subject, action, +- NULL, flags, NULL, ++ details, flags, NULL, + &local_error); + if (!result) { + g_set_error (error, GDM_DISPLAY_ERROR, GDM_DISPLAY_ERROR_GENERAL, +diff --git a/data/gnome-headless-session@.service.in b/data/gnome-headless-session@.service.in +new file mode 100644 +index 0000000..77d7f6f +--- /dev/null ++++ b/data/gnome-headless-session@.service.in +@@ -0,0 +1,34 @@ ++[Unit] ++Description=GNOME Headless Desktop Session ++Requires=gdm.service ++After=gdm.service ++ ++[Service] ++User=@GDM_USERNAME@ ++ExecStart=@libexecdir@/gdm-new-session %i --headless ++Restart=on-failure ++ ++LockPersonality=yes ++MemoryDenyWriteExecute=yes ++RestrictNamespaces=yes ++RestrictAddressFamilies=AF_UNIX ++RestrictRealtime=yes ++RestrictSUIDSGID=yes ++SystemCallArchitectures=native ++CapabilityBoundingSet= ++ProtectKernelModules=yes ++ProtectKernelLogs=yes ++ProtectKernelTunables=yes ++ProtectClock=yes ++ProtectHostname=yes ++ProtectControlGroups=yes ++ProtectHostname=yes ++ProtectProc=invisible ++PrivateDevices=yes ++PrivateNetwork=yes ++NoNewPrivileges=yes ++SystemCallArchitectures=native ++SystemCallFilter=@system-service ++ ++[Install] ++WantedBy=graphical.target +diff --git a/data/meson.build b/data/meson.build +index 06b63e7..af8bcf7 100644 +--- a/data/meson.build ++++ b/data/meson.build +@@ -192,6 +192,16 @@ if systemdsystemunitdir != 'no' + install_dir: systemd_systemunitdir, + format: 'cmake' + ) ++ ++ configure_file( ++ input: 'gnome-headless-session@.service.in', ++ output: '@BASENAME@', ++ configuration: { ++ 'GDM_USERNAME': get_option('user'), ++ 'libexecdir': gdm_prefix / get_option('libexecdir'), ++ }, ++ install_dir: systemd_systemunitdir, ++ ) + endif + + gdm_gnome_session_wanted_targets = [] +diff --git a/data/polkit-gdm.rules.in b/data/polkit-gdm.rules.in +index c9ef490..0e2327d 100644 +--- a/data/polkit-gdm.rules.in ++++ b/data/polkit-gdm.rules.in +@@ -3,6 +3,13 @@ polkit.addRule(function(action, subject) { + return undefined; + + if (action.id == "org.gnome.displaymanager.displayfactory.manage-user-displays") { +- return polkit.Result.YES; ++ const user = action.lookup("user"); ++ const system_unit = subject.system_unit; ++ ++ if (user && system_unit) { ++ const match = system_unit.match(/^gnome-headless-session@(.+?)\.service$/); ++ if (match && user == match[1]) ++ return polkit.Result.YES; ++ } + } + }); +diff --git a/utils/gdm-new-session.c b/utils/gdm-new-session.c +new file mode 100644 +index 0000000..898ca51 +--- /dev/null ++++ b/utils/gdm-new-session.c +@@ -0,0 +1,293 @@ ++/* ++ * SPDX-FileCopyrightText: 2025 Joan Torres Lopez ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++ ++#define GDM_DBUS_NAME "org.gnome.DisplayManager" ++#define GDM_LOCAL_DISPLAY_FACTORY_PATH "/org/gnome/DisplayManager/LocalDisplayFactory" ++#define GDM_REMOTE_DISPLAY_FACTORY_PATH "/org/gnome/DisplayManager/RemoteDisplayFactory" ++#define GDM_DISPLAYS_PATH "/org/gnome/DisplayManager/Displays" ++#define GDM_LOCAL_DISPLAY_FACTORY_INTERFACE "org.gnome.DisplayManager.LocalDisplayFactory" ++#define GDM_REMOTE_DISPLAY_FACTORY_INTERFACE "org.gnome.DisplayManager.RemoteDisplayFactory" ++#define GDM_DISPLAY_INTERFACE "org.gnome.DisplayManager.Display" ++ ++typedef struct _State { ++ GMainLoop *event_loop; ++ ++ GDBusConnection *connection; ++ GDBusObjectManager *displays; ++ ++ char *username; ++ gboolean headless; ++ ++ char *display_path; ++ ++ gboolean terminate_requested; ++} State; ++ ++static gboolean terminate_session (State *state, ++ GError **error); ++ ++static void ++state_free (State *state) ++{ ++ g_clear_pointer (&state->event_loop, g_main_loop_unref); ++ g_clear_object (&state->connection); ++ g_clear_object (&state->displays); ++ g_clear_pointer (&state->username, g_free); ++ g_clear_pointer (&state->display_path, g_free); ++ g_free (state); ++} ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (State, state_free); ++ ++static void ++on_display_added (GDBusObjectManager *displays, ++ GDBusObject *display_object, ++ State *state) ++{ ++ g_autoptr (GDBusInterface) display_proxy = NULL; ++ g_autoptr (GVariant) reply = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autofree char *session_id = NULL; ++ g_autofree char *username = NULL; ++ ++ display_proxy = g_dbus_object_get_interface (display_object, GDM_DISPLAY_INTERFACE); ++ if (display_proxy == NULL) { ++ g_warning ("GDM exported a non Display object on dbus"); ++ return; ++ } ++ ++ reply = g_dbus_proxy_call_sync (G_DBUS_PROXY (display_proxy), ++ "GetSessionId", ++ NULL, ++ G_DBUS_CALL_FLAGS_NONE, ++ -1, ++ NULL, ++ &error); ++ if (reply == NULL) { ++ g_warning ("Failed getting SessionId: %s", error->message); ++ return; ++ } ++ ++ g_variant_get (reply, "(s)", &session_id); ++ ++ if (sd_session_get_username (session_id, &username) < 0) ++ return; ++ ++ if (g_strcmp0 (state->username, username) == 0) { ++ g_print ("Session started\n"); ++ ++ g_set_str (&state->display_path, g_dbus_object_get_object_path (display_object)); ++ ++ g_signal_handlers_disconnect_by_func (displays, ++ G_CALLBACK (on_display_added), ++ state); ++ ++ if (state->terminate_requested) { ++ if (!terminate_session (state, &error)) ++ g_warning ("Terminating session failed: %s", error->message); ++ } ++ } ++} ++static void ++on_display_removed (GDBusObjectManager *displays, ++ GDBusObject *display_object, ++ State *state) ++{ ++ const char *display_path = g_dbus_object_get_object_path (display_object); ++ ++ if (g_strcmp0 (state->display_path, display_path) == 0) { ++ g_print ("Session finished\n"); ++ ++ g_set_str (&state->display_path, NULL); ++ g_main_loop_quit (state->event_loop); ++ } ++} ++ ++static gboolean ++watch_displays (State *state, ++ GError **error) ++{ ++ g_autoptr (GDBusObjectManager) displays = NULL; ++ ++ displays = g_dbus_object_manager_client_new_sync (state->connection, ++ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, ++ GDM_DBUS_NAME, ++ GDM_DISPLAYS_PATH, ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ error); ++ if (displays == NULL) { ++ g_dbus_error_strip_remote_error (*error); ++ return FALSE; ++ } ++ ++ g_signal_connect (displays, ++ "object-added", ++ G_CALLBACK (on_display_added), ++ state); ++ ++ g_signal_connect (displays, ++ "object-removed", ++ G_CALLBACK (on_display_removed), ++ state); ++ ++ state->displays = g_steal_pointer (&displays); ++ ++ return TRUE; ++} ++ ++static gboolean ++setup (State *state, ++ GError **error) ++{ ++ g_autoptr (GDBusConnection) connection = NULL; ++ ++ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); ++ if (connection == NULL) { ++ g_dbus_error_strip_remote_error (*error); ++ return FALSE; ++ } ++ ++ state->connection = g_steal_pointer (&connection); ++ ++ if (!watch_displays (state, error)) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++static gboolean ++call_gdm_display_factory_method (State *state, ++ const char *method, ++ GError **error) ++{ ++ GDBusConnection *connection = state->connection; ++ gboolean headless = state->headless; ++ g_autoptr (GVariant) reply = NULL; ++ const char *dbus_path; ++ const char *dbus_iface; ++ ++ if (headless) { ++ dbus_path = GDM_REMOTE_DISPLAY_FACTORY_PATH; ++ dbus_iface = GDM_REMOTE_DISPLAY_FACTORY_INTERFACE; ++ } else { ++ dbus_path = GDM_LOCAL_DISPLAY_FACTORY_PATH; ++ dbus_iface = GDM_LOCAL_DISPLAY_FACTORY_INTERFACE; ++ } ++ ++ reply = g_dbus_connection_call_sync (connection, ++ GDM_DBUS_NAME, ++ dbus_path, ++ dbus_iface, ++ method, ++ g_variant_new ("(s)", state->username), ++ NULL, ++ G_DBUS_CALL_FLAGS_NONE, ++ -1, NULL, error); ++ if (reply == NULL) { ++ g_dbus_error_strip_remote_error (*error); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++create_session (State *state, ++ GError **error) ++{ ++ return call_gdm_display_factory_method (state, ++ "CreateUserDisplay", ++ error); ++} ++ ++static gboolean ++terminate_session (State *state, ++ GError **error) ++{ ++ return call_gdm_display_factory_method (state, ++ "DestroyUserDisplay", ++ error); ++} ++ ++static gboolean ++on_sigterm (State *state) ++{ ++ g_autoptr (GError) error = NULL; ++ ++ // Got SIGTERM before the session is created, ++ // wait for it to be created and then terminate it ++ if (state->display_path == NULL) { ++ state->terminate_requested = TRUE; ++ return G_SOURCE_REMOVE; ++ } ++ ++ if (!terminate_session (state, &error)) ++ g_warning ("Terminating session failed: %s", error->message); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++int ++main (int argc, ++ char *argv[]) ++{ ++ g_autoptr (GOptionContext) context = NULL; ++ g_autoptr (State) state = NULL; ++ g_autoptr (GError) error = NULL; ++ g_autofree char *username = NULL; ++ gboolean headless = FALSE; ++ ++ GOptionEntry entries[] = ++ { ++ { "headless", 'h', 0, G_OPTION_ARG_NONE, &headless, "Run headless session", NULL }, ++ { NULL } ++ }; ++ ++ context = g_option_context_new ("USER - Run a graphical session as a specified user"); ++ g_option_context_add_main_entries (context, entries, NULL); ++ ++ if (!g_option_context_parse (context, &argc, &argv, &error)) { ++ g_printerr ("Option parsing failed: %s\n", error->message); ++ return EX_USAGE; ++ } ++ ++ if (argc < 2) { ++ g_printerr ("Username is required\n"); ++ return EX_USAGE; ++ } ++ ++ username = g_strdup (argv[1]); ++ ++ state = g_new0 (State, 1); ++ state->username = g_steal_pointer (&username); ++ state->event_loop = g_main_loop_new (NULL, FALSE); ++ state->headless = headless; ++ ++ if (!setup (state, &error)) { ++ g_printerr ("Failed: %s\n", error->message); ++ return EX_SOFTWARE; ++ } ++ ++ if (!create_session (state, &error)) { ++ g_printerr ("Starting session failed: %s\n", error->message); ++ return EX_SOFTWARE; ++ } ++ ++ g_unix_signal_add (SIGTERM, (GSourceFunc) on_sigterm, state); ++ ++ g_main_loop_run (state->event_loop); ++ ++ return EX_OK; ++} +diff --git a/utils/meson.build b/utils/meson.build +index 2cfbd14..a873b9f 100644 +--- a/utils/meson.build ++++ b/utils/meson.build +@@ -35,6 +35,21 @@ gdm_auth_config = executable('gdm-config', + install: true, + ) + ++# gdm-new-session ++gdm_new_session_deps = [ ++ glib_dep, ++ gio_dep, ++ logind_dep, ++] ++ ++gdm_new_session = executable('gdm-new-session', ++ 'gdm-new-session.c', ++ dependencies: gdm_new_session_deps, ++ include_directories: config_h_dir, ++ install: true, ++ install_dir: get_option('libexecdir'), ++) ++ + auth_config_distro_hooks = [ + 'redhat', + 'generic', +-- +2.51.0 + +From d383da48f4ea9a228f21e4da84a50f1be6349153 Mon Sep 17 00:00:00 2001 +From: Joan Torres +Date: Sun, 18 May 2025 20:52:35 +0200 +Subject: [PATCH 8/8] remote-display: Add SetRemoteId method + +Now accessing these created headless user sessions has two different ways: + 1. Directly connecting to the gnome-remote-desktop daemon headless + which is connected to that user session, as explained in the + previous commit. + 2. Through the remote login. i.e. connecting to the gnome-remote-desktop + system daemon that provides a headless login screen and after + authenticating, get that headless user session. + +To make the option 2 work, the system daemon needs to know the remote_id +of the headless display to be able to do the handover from the login +headless display to the user headless display. + +This allows the system daemon generate a valid remote_id and assign it to +this new headless display. + +Part-of: +--- + daemon/gdm-remote-display.c | 18 ++++++++++++++++++ + daemon/gdm-remote-display.xml | 3 +++ + 2 files changed, 21 insertions(+) + +diff --git a/daemon/gdm-remote-display.c b/daemon/gdm-remote-display.c +index 83954620f..0d243ff20 100644 +--- a/daemon/gdm-remote-display.c ++++ b/daemon/gdm-remote-display.c +@@ -60,6 +60,19 @@ gdm_remote_display_set_remote_id (GdmRemoteDisplay *display, + g_object_set (G_OBJECT (display->skeleton), "remote-id", remote_id, NULL); + } + ++static gboolean ++handle_set_remote_id (GdmDBusRemoteDisplay *skeleton, ++ GDBusMethodInvocation *invocation, ++ const char *remote_id, ++ GdmRemoteDisplay *display) ++{ ++ g_object_set (G_OBJECT (display->skeleton), "remote-id", remote_id, NULL); ++ ++ gdm_dbus_remote_display_complete_set_remote_id (skeleton, invocation); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ + static GObject * + gdm_remote_display_constructor (GType type, + guint n_construct_properties, +@@ -78,6 +91,11 @@ gdm_remote_display_constructor (GType type, + + g_object_bind_property (display, "session-id", display->skeleton, "session-id", G_BINDING_SYNC_CREATE); + ++ g_signal_connect (display->skeleton, ++ "handle-set-remote-id", ++ G_CALLBACK (handle_set_remote_id), ++ display); ++ + return G_OBJECT (display); + } + +diff --git a/daemon/gdm-remote-display.xml b/daemon/gdm-remote-display.xml +index 2fdf9ad7f..4d9542f24 100644 +--- a/daemon/gdm-remote-display.xml ++++ b/daemon/gdm-remote-display.xml +@@ -3,5 +3,8 @@ + + + ++ ++ ++ + + +-- +2.51.0 + diff --git a/0001-headless-session-Fix-autostarting-on-boot.patch b/0001-headless-session-Fix-autostarting-on-boot.patch deleted file mode 100644 index 6cf37e5..0000000 --- a/0001-headless-session-Fix-autostarting-on-boot.patch +++ /dev/null @@ -1,28 +0,0 @@ -From e938a72b8ee65b7db2ad76f63dc3f77713871a82 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20=C3=85dahl?= -Date: Thu, 3 Jul 2025 12:09:40 +0200 -Subject: [PATCH] headless-session: Fix autostarting on boot - -Make it wanted by graphical.target, and make sure it launches after gdm. ---- - data/gnome-headless-session@.service | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/data/gnome-headless-session@.service b/data/gnome-headless-session@.service -index 269d16288..cba7526f8 100644 ---- a/data/gnome-headless-session@.service -+++ b/data/gnome-headless-session@.service -@@ -1,6 +1,10 @@ - [Unit] - Description=Headless desktop session -+After=multi-user.target rescue.service rescue.target display-manager.service - - [Service] - ExecStart=/usr/libexec/gdm-headless-login-session --user=%i - Restart=on-failure -+ -+[Install] -+WantedBy=graphical.target --- -2.49.0 - diff --git a/gdm.spec b/gdm.spec index 83170fc..a6602d6 100644 --- a/gdm.spec +++ b/gdm.spec @@ -23,7 +23,6 @@ Source6: gdm.sysusers # Downstream patches Patch: 0001-Honor-initial-setup-being-disabled-by-distro-install.patch Patch: 0001-data-add-system-dconf-databases-to-gdm-profile.patch -Patch: 0001-Add-headless-session-files.patch Patch: 0002-data-Drop-X11-fallback-rules.patch # RHEL-4104 @@ -38,12 +37,14 @@ Patch: 0001-meson-Define-missing-HAVE_LIBAUDIT.patch # https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/298 Patch: 0001-session-Fix-memory-leak-on-new-outside-connection.patch -# RHEL-69319 -Patch: 0001-headless-session-Fix-autostarting-on-boot.patch - -#RHEL-62663 +# RHEL-62663 +# https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/303 Patch: 0001-manager-Quit-plymouth-when-no-local-display-is-avail.patch +# RHEL-126603 +# https://gitlab.gnome.org/GNOME/gdm/-/merge_requests/288 +Patch: 0001-Introduce-gdm-new-session-tool.patch + BuildRequires: dconf BuildRequires: desktop-file-utils BuildRequires: gettext-devel @@ -255,7 +256,7 @@ fi %{_libexecdir}/gdm-runtime-config %{_libexecdir}/gdm-session-worker %{_libexecdir}/gdm-wayland-session -%{_libexecdir}/gdm-headless-login-session +%{_libexecdir}/gdm-new-session %{_sbindir}/gdm %{_bindir}/gdmflexiserver %{_bindir}/gdm-config @@ -271,6 +272,8 @@ fi %{_datadir}/gdm/locale.alias %{_datadir}/gdm/gdb-cmd %{_datadir}/gnome-session/sessions/gnome-login.session +%{_datadir}/polkit-1/rules.d/20-gdm.rules +%{_datadir}/polkit-1/actions/org.gnome.displaymanager.policy %{_libdir}/girepository-1.0/Gdm-1.0.typelib %{_libdir}/security/pam_gdm.so %{_libdir}/libgdm*.so*