diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05dcbc9..687acf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,7 +146,7 @@ From a real Linux TTY/VT, the same dev-session command launches the full native cargo run -- --dev --no-watch-apps ``` -In dev mode, the supervisor runs its compositor child as `cargo run -p hearthspace-compositor --features udev -- --no-shell`. The compositor chooses `udev` automatically when neither `WAYLAND_DISPLAY` nor `DISPLAY` is present; inside GNOME or another graphical host session it still chooses the nested winit backend unless `--tty` is passed directly to the compositor. +In dev mode, the supervisor runs its compositor child as `cargo run -p hearthspace-compositor --features udev -- --no-shell --tty` from real TTY sessions and as `cargo run -p hearthspace-compositor --features udev -- --no-shell --winit` inside GNOME or another graphical host session. The explicit backend flag prevents stale host display variables from changing the intended session mode. Use direct compositor commands when you want to test only the compositor backend: diff --git a/crates/hearthspace-session/src/main.rs b/crates/hearthspace-session/src/main.rs index 4ed835a..5aa6a8f 100644 --- a/crates/hearthspace-session/src/main.rs +++ b/crates/hearthspace-session/src/main.rs @@ -89,9 +89,16 @@ struct ProcessSpec { program: String, args: Vec, envs: Vec<(String, String)>, + env_removals: Vec, current_dir: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CompositorBackend { + NativeTty, + NestedWinit, +} + struct ManagedProcess { spec: ProcessSpec, child: Child, @@ -138,14 +145,16 @@ fn main() -> Result<(), Box> { let shutdown_requested = install_signal_handler()?; let mut supervisor = Supervisor::new(mode, paths, session_env, shutdown_requested); - supervisor.start_settingsd(if args.no_settingsd_restart { 0 } else { 3 })?; - supervisor.wait_for_settingsd_ready()?; - supervisor.start_compositor()?; - supervisor.wait_for_compositor_ready()?; - supervisor.start_shell(if args.no_shell_restart { 0 } else { 3 })?; - supervisor.start_app_watcher_if_available(&args)?; + let monitor_result = (|| { + supervisor.start_settingsd(if args.no_settingsd_restart { 0 } else { 3 })?; + supervisor.wait_for_settingsd_ready()?; + supervisor.start_compositor()?; + supervisor.wait_for_compositor_ready()?; + supervisor.start_shell(if args.no_shell_restart { 0 } else { 3 })?; + supervisor.start_app_watcher_if_available(&args)?; - let monitor_result = supervisor.monitor_until_exit(); + supervisor.monitor_until_exit() + })(); supervisor.shutdown(); monitor_result } @@ -342,11 +351,17 @@ impl Supervisor { } fn start_compositor(&mut self) -> Result<(), Box> { - let spec = self.binary_spec( + let backend = select_compositor_backend(); + let backend_flag = match backend { + CompositorBackend::NativeTty => "--tty", + CompositorBackend::NestedWinit => "--winit", + }; + let mut spec = self.binary_spec( "hearthspace-compositor", - &[hearthspace_common::NO_SHELL_FLAG], + &[hearthspace_common::NO_SHELL_FLAG, backend_flag], false, ); + configure_compositor_environment(&mut spec, backend); self.compositor = Some(ManagedProcess::spawn(spec, 0)?); Ok(()) } @@ -519,6 +534,7 @@ impl Supervisor { program: "cargo".to_string(), args, envs, + env_removals: Vec::new(), current_dir, } } @@ -527,6 +543,7 @@ impl Supervisor { program: binary.to_string(), args: binary_args.iter().map(|arg| (*arg).to_string()).collect(), envs, + env_removals: Vec::new(), current_dir, }, } @@ -562,6 +579,7 @@ impl Supervisor { "build -p hearthspace-settings".to_string(), ], envs, + env_removals: Vec::new(), current_dir, }); } @@ -583,6 +601,7 @@ impl Supervisor { "hearthspace-settings".to_string(), ], envs, + env_removals: Vec::new(), current_dir, }); } @@ -590,6 +609,53 @@ impl Supervisor { } } +fn select_compositor_backend() -> CompositorBackend { + if env::var("XDG_SESSION_TYPE").is_ok_and(|session_type| session_type == "tty") { + return CompositorBackend::NativeTty; + } + + if host_wayland_display_path().is_some() || env::var_os("DISPLAY").is_some() { + CompositorBackend::NestedWinit + } else { + CompositorBackend::NativeTty + } +} + +fn configure_compositor_environment(spec: &mut ProcessSpec, backend: CompositorBackend) { + spec.env_removals.extend([ + "WAYLAND_DISPLAY".to_string(), + "WAYLAND_SOCKET".to_string(), + "DISPLAY".to_string(), + ]); + + if backend == CompositorBackend::NestedWinit { + if let Some(path) = host_wayland_display_path() { + spec.envs + .push((hearthspace_common::WAYLAND_DISPLAY_ENV.to_string(), path)); + } + if let Some(display) = env::var_os("DISPLAY") { + spec.envs.push(( + "DISPLAY".to_string(), + display.to_string_lossy().into_owned(), + )); + } + } +} + +fn host_wayland_display_path() -> Option { + let display = env::var_os("WAYLAND_DISPLAY")?; + let display_path = PathBuf::from(&display); + let socket_path = if display_path.is_absolute() { + display_path + } else { + PathBuf::from(env::var_os("XDG_RUNTIME_DIR")?).join(display_path) + }; + + socket_path + .exists() + .then(|| socket_path.to_string_lossy().into_owned()) +} + fn dev_cargo_args(binary: &str, binary_args: &[&str]) -> Vec { let mut args = vec!["run".to_string()]; if cargo_package_exists(binary) { @@ -636,6 +702,9 @@ impl ManagedProcess { fn spawn_child(spec: &ProcessSpec) -> Result> { info!(name = spec.name, program = %spec.program, args = ?spec.args, "starting process"); let mut command = Command::new(&spec.program); + for key in &spec.env_removals { + command.env_remove(key); + } command .args(&spec.args) .envs(spec.envs.iter().map(|(key, value)| (key, value)))