@@ -5,6 +5,7 @@ |
| 5 | 5 | use anyhow::{Context, Result}; |
| 6 | 6 | use nix::unistd::User; |
| 7 | 7 | use std::ffi::CString; |
| 8 | +use std::os::fd::AsRawFd; |
| 8 | 9 | use std::os::unix::process::CommandExt; |
| 9 | 10 | use std::process::{Child, Command, ExitStatus, Stdio}; |
| 10 | 11 | |
@@ -16,10 +17,18 @@ pub struct UserSession { |
| 16 | 17 | |
| 17 | 18 | impl UserSession { |
| 18 | 19 | /// 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 |
| 19 | 27 | pub fn start( |
| 20 | 28 | username: &str, |
| 21 | 29 | session_cmd: &[String], |
| 22 | | - display: &str, |
| 30 | + session_type: &str, |
| 31 | + display: Option<&str>, |
| 23 | 32 | vt: u32, |
| 24 | 33 | ) -> Result<Self> { |
| 25 | 34 | let user = User::from_name(username) |
@@ -32,6 +41,8 @@ impl UserSession { |
| 32 | 41 | let gid = user.gid; |
| 33 | 42 | let username_cstr = CString::new(username).context("Invalid username")?; |
| 34 | 43 | |
| 44 | + let is_wayland = session_type == "wayland"; |
| 45 | + |
| 35 | 46 | // Determine session command |
| 36 | 47 | let (cmd_path, cmd_args) = if session_cmd.is_empty() { |
| 37 | 48 | // Default: try gar-session.sh, fall back to shell |
@@ -45,25 +56,17 @@ impl UserSession { |
| 45 | 56 | (session_cmd[0].clone(), session_cmd[1..].to_vec()) |
| 46 | 57 | }; |
| 47 | 58 | |
| 48 | | - // Set up XAUTHORITY path (even though we use -auth /dev/null, some apps expect it) |
| 49 | | - let xauthority = format!("{}/.Xauthority", home); |
| 50 | | - |
| 51 | 59 | let mut cmd = Command::new(&cmd_path); |
| 52 | 60 | cmd.args(&cmd_args) |
| 53 | 61 | .env_clear() |
| 54 | | - .env("DISPLAY", display) |
| 55 | | - .env("XAUTHORITY", &xauthority) |
| 56 | 62 | .env("HOME", &home) |
| 57 | 63 | .env("USER", username) |
| 58 | 64 | .env("LOGNAME", username) |
| 59 | 65 | .env("SHELL", &shell) |
| 60 | 66 | .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") |
| 62 | 67 | .env("XDG_VTNR", vt.to_string()) |
| 63 | 68 | .env("XDG_SEAT", "seat0") |
| 64 | 69 | .env("XDG_SESSION_CLASS", "user") |
| 65 | | - .env("XDG_SESSION_DESKTOP", "gar") |
| 66 | | - .env("XDG_CURRENT_DESKTOP", "gar") |
| 67 | 70 | .env("DBUS_SESSION_BUS_ADDRESS", format!("unix:path=/run/user/{}/bus", uid.as_raw())) |
| 68 | 71 | .env("XDG_RUNTIME_DIR", format!("/run/user/{}", uid.as_raw())) |
| 69 | 72 | .env("XDG_DATA_DIRS", "/usr/local/share:/usr/share") |
@@ -71,7 +74,25 @@ impl UserSession { |
| 71 | 74 | .stdout(Stdio::inherit()) |
| 72 | 75 | .stderr(Stdio::inherit()); |
| 73 | 76 | |
| 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 | + |
| 74 | 94 | // Set up process to run as the user |
| 95 | + let tty_path = format!("/dev/tty{}", vt); |
| 75 | 96 | unsafe { |
| 76 | 97 | let home_for_closure = home.clone(); |
| 77 | 98 | cmd.pre_exec(move || { |
@@ -85,6 +106,24 @@ impl UserSession { |
| 85 | 106 | // Change to home directory |
| 86 | 107 | std::env::set_current_dir(&home_for_closure)?; |
| 87 | 108 | |
| 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 | + |
| 88 | 127 | Ok(()) |
| 89 | 128 | }); |
| 90 | 129 | } |
@@ -94,6 +133,7 @@ impl UserSession { |
| 94 | 133 | tracing::info!( |
| 95 | 134 | username, |
| 96 | 135 | session = cmd_path, |
| 136 | + session_type, |
| 97 | 137 | pid = process.id(), |
| 98 | 138 | "Started user session" |
| 99 | 139 | ); |