gardesk/gardm / e7cad66

Browse files

Add session_type parameter to UserSession::start

Update UserSession::start signature to accept session_type and
optional display. Set XDG_SESSION_TYPE based on session type.
For Wayland sessions, set up controlling TTY via setsid/TIOCSCTTY.

Update daemon to pass session_type through SessionStartInfo and
branch on session type for X11 vs Wayland handling. For Wayland,
stop X server before launching compositor and restart afterward.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
e7cad66594e14774cc09e006bb4a6f1e9db236b8
Parents
2ad9cba
Tree
2491726

2 changed files

StatusFile+-
M gardmd/src/main.rs 71 21
M gardmd/src/session.rs 49 9
gardmd/src/main.rsmodified
@@ -108,7 +108,7 @@ async fn run_test_mode(config: Config) -> Result<()> {
108108
 /// Run as full display manager
109109
 async fn run_display_manager(args: Args, config: Config) -> Result<()> {
110110
     // Determine display and VT
111
-    let (x_display, x_server) = if args.no_x {
111
+    let (x_display, mut x_server) = if args.no_x {
112112
         // Use existing X server
113113
         let x_display = args
114114
             .display
@@ -184,27 +184,76 @@ async fn run_display_manager(args: Args, config: Config) -> Result<()> {
184184
 
185185
         match session_result {
186186
             Ok(Some(session_info)) => {
187
-                // Start user session
188187
                 let vt = x_server.as_ref().map(|x| x.vt()).unwrap_or(1);
189
-                let mut session = UserSession::start(
190
-                    &session_info.username,
191
-                    &session_info.cmd,
192
-                    &x_display,
193
-                    vt,
194
-                )?;
195
-
196
-                // Wait for session to end
197
-                tokio::select! {
198
-                    _ = tokio::task::spawn_blocking(move || session.wait()) => {
199
-                        tracing::info!("User session ended, restarting greeter");
188
+                let is_wayland = session_info.session_type == "wayland";
189
+
190
+                if is_wayland {
191
+                    // WAYLAND SESSION: Stop X server first, then start compositor
192
+                    tracing::info!("Wayland session selected, stopping X server");
193
+
194
+                    // Stop X server - compositor needs direct VT access
195
+                    if let Some(x) = x_server.take() {
196
+                        drop(x); // XServer::drop() handles graceful shutdown
197
+                        tracing::info!("X server stopped");
200198
                     }
201
-                    _ = sigterm.recv() => {
202
-                        tracing::info!("Received SIGTERM during session");
199
+
200
+                    // Start Wayland compositor directly on VT
201
+                    let mut session = UserSession::start(
202
+                        &session_info.username,
203
+                        &session_info.cmd,
204
+                        "wayland",
205
+                        None, // No DISPLAY for Wayland
206
+                        vt,
207
+                    )?;
208
+
209
+                    // Wait for session to end
210
+                    let session_ended = tokio::select! {
211
+                        _ = tokio::task::spawn_blocking(move || session.wait()) => {
212
+                            tracing::info!("Wayland session ended");
213
+                            true
214
+                        }
215
+                        _ = sigterm.recv() => {
216
+                            tracing::info!("Received SIGTERM during Wayland session");
217
+                            false
218
+                        }
219
+                        _ = sigint.recv() => {
220
+                            tracing::info!("Received SIGINT during Wayland session");
221
+                            false
222
+                        }
223
+                    };
224
+
225
+                    if !session_ended {
203226
                         break;
204227
                     }
205
-                    _ = sigint.recv() => {
206
-                        tracing::info!("Received SIGINT during session");
207
-                        break;
228
+
229
+                    // Restart X server for greeter
230
+                    tracing::info!("Restarting X server for greeter");
231
+                    let new_x = XServer::start(&x_display, vt)?;
232
+                    vt::switch_to_vt(vt)?;
233
+                    x_server = Some(new_x);
234
+                } else {
235
+                    // X11 SESSION: Keep X server running (existing behavior)
236
+                    let mut session = UserSession::start(
237
+                        &session_info.username,
238
+                        &session_info.cmd,
239
+                        "x11",
240
+                        Some(&x_display),
241
+                        vt,
242
+                    )?;
243
+
244
+                    // Wait for session to end
245
+                    tokio::select! {
246
+                        _ = tokio::task::spawn_blocking(move || session.wait()) => {
247
+                            tracing::info!("X11 session ended, restarting greeter");
248
+                        }
249
+                        _ = sigterm.recv() => {
250
+                            tracing::info!("Received SIGTERM during session");
251
+                            break;
252
+                        }
253
+                        _ = sigint.recv() => {
254
+                            tracing::info!("Received SIGINT during session");
255
+                            break;
256
+                        }
208257
                     }
209258
                 }
210259
             }
@@ -230,6 +279,7 @@ async fn run_display_manager(args: Args, config: Config) -> Result<()> {
230279
 struct SessionStartInfo {
231280
     username: String,
232281
     cmd: Vec<String>,
282
+    session_type: String,
233283
 }
234284
 
235285
 /// Handle greeter IPC until we get a successful auth and StartSession
@@ -285,12 +335,12 @@ async fn handle_greeter_request(
285335
             (response, None)
286336
         }
287337
 
288
-        Request::StartSession { cmd, env: _ } => {
338
+        Request::StartSession { cmd, session_type, env: _ } => {
289339
             if let Some(username) = auth.take_authenticated() {
290
-                tracing::info!(%username, ?cmd, "Session start requested");
340
+                tracing::info!(%username, ?cmd, %session_type, "Session start requested");
291341
                 (
292342
                     Response::Success,
293
-                    Some(SessionStartInfo { username, cmd }),
343
+                    Some(SessionStartInfo { username, cmd, session_type }),
294344
                 )
295345
             } else {
296346
                 (
gardmd/src/session.rsmodified
@@ -5,6 +5,7 @@
55
 use anyhow::{Context, Result};
66
 use nix::unistd::User;
77
 use std::ffi::CString;
8
+use std::os::fd::AsRawFd;
89
 use std::os::unix::process::CommandExt;
910
 use std::process::{Child, Command, ExitStatus, Stdio};
1011
 
@@ -16,10 +17,18 @@ pub struct UserSession {
1617
 
1718
 impl UserSession {
1819
     /// Start a user session
20
+    ///
21
+    /// # Arguments
22
+    /// * `username` - The user to run the session as
23
+    /// * `session_cmd` - Command to execute (e.g., ["Hyprland"] or ["gar-session.sh"])
24
+    /// * `session_type` - "x11" or "wayland"
25
+    /// * `display` - X11 display (e.g., ":0"), None for Wayland sessions
26
+    /// * `vt` - Virtual terminal number
1927
     pub fn start(
2028
         username: &str,
2129
         session_cmd: &[String],
22
-        display: &str,
30
+        session_type: &str,
31
+        display: Option<&str>,
2332
         vt: u32,
2433
     ) -> Result<Self> {
2534
         let user = User::from_name(username)
@@ -32,6 +41,8 @@ impl UserSession {
3241
         let gid = user.gid;
3342
         let username_cstr = CString::new(username).context("Invalid username")?;
3443
 
44
+        let is_wayland = session_type == "wayland";
45
+
3546
         // Determine session command
3647
         let (cmd_path, cmd_args) = if session_cmd.is_empty() {
3748
             // Default: try gar-session.sh, fall back to shell
@@ -45,25 +56,17 @@ impl UserSession {
4556
             (session_cmd[0].clone(), session_cmd[1..].to_vec())
4657
         };
4758
 
48
-        // Set up XAUTHORITY path (even though we use -auth /dev/null, some apps expect it)
49
-        let xauthority = format!("{}/.Xauthority", home);
50
-
5159
         let mut cmd = Command::new(&cmd_path);
5260
         cmd.args(&cmd_args)
5361
             .env_clear()
54
-            .env("DISPLAY", display)
55
-            .env("XAUTHORITY", &xauthority)
5662
             .env("HOME", &home)
5763
             .env("USER", username)
5864
             .env("LOGNAME", username)
5965
             .env("SHELL", &shell)
6066
             .env("PATH", format!("{}/.local/bin:{}/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", home, home))
61
-            .env("XDG_SESSION_TYPE", "x11")
6267
             .env("XDG_VTNR", vt.to_string())
6368
             .env("XDG_SEAT", "seat0")
6469
             .env("XDG_SESSION_CLASS", "user")
65
-            .env("XDG_SESSION_DESKTOP", "gar")
66
-            .env("XDG_CURRENT_DESKTOP", "gar")
6770
             .env("DBUS_SESSION_BUS_ADDRESS", format!("unix:path=/run/user/{}/bus", uid.as_raw()))
6871
             .env("XDG_RUNTIME_DIR", format!("/run/user/{}", uid.as_raw()))
6972
             .env("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
@@ -71,7 +74,25 @@ impl UserSession {
7174
             .stdout(Stdio::inherit())
7275
             .stderr(Stdio::inherit());
7376
 
77
+        // Session-type specific environment
78
+        if is_wayland {
79
+            cmd.env("XDG_SESSION_TYPE", "wayland");
80
+            // Wayland compositors create their own WAYLAND_DISPLAY
81
+            // Don't set DISPLAY - that's X11-specific
82
+            tracing::info!(vt, "Configuring Wayland session environment");
83
+        } else {
84
+            // X11 session
85
+            cmd.env("XDG_SESSION_TYPE", "x11");
86
+            if let Some(display) = display {
87
+                cmd.env("DISPLAY", display);
88
+                cmd.env("XAUTHORITY", format!("{}/.Xauthority", home));
89
+            }
90
+            cmd.env("XDG_SESSION_DESKTOP", "gar");
91
+            cmd.env("XDG_CURRENT_DESKTOP", "gar");
92
+        }
93
+
7494
         // Set up process to run as the user
95
+        let tty_path = format!("/dev/tty{}", vt);
7596
         unsafe {
7697
             let home_for_closure = home.clone();
7798
             cmd.pre_exec(move || {
@@ -85,6 +106,24 @@ impl UserSession {
85106
                 // Change to home directory
86107
                 std::env::set_current_dir(&home_for_closure)?;
87108
 
109
+                // For Wayland sessions, set up controlling TTY
110
+                // Wayland compositors need direct TTY access for input/output
111
+                if is_wayland {
112
+                    // Create new session (detach from parent's controlling terminal)
113
+                    libc::setsid();
114
+
115
+                    // Open the VT and make it our controlling terminal
116
+                    let tty = std::fs::OpenOptions::new()
117
+                        .read(true)
118
+                        .write(true)
119
+                        .open(&tty_path)?;
120
+
121
+                    // Set as controlling terminal (TIOCSCTTY)
122
+                    if libc::ioctl(tty.as_raw_fd(), libc::TIOCSCTTY, 0) < 0 {
123
+                        tracing::warn!("Failed to set controlling TTY, compositor may handle this");
124
+                    }
125
+                }
126
+
88127
                 Ok(())
89128
             });
90129
         }
@@ -94,6 +133,7 @@ impl UserSession {
94133
         tracing::info!(
95134
             username,
96135
             session = cmd_path,
136
+            session_type,
97137
             pid = process.id(),
98138
             "Started user session"
99139
         );