From 56907b4115097be9b5164486b21bd80e353e0b28 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 24 Jun 2026 09:08:26 +0300 Subject: [PATCH] pty: propagate the shell's exit status Signed-off-by: NotAShelf Change-Id: I9f33a222a19794b6ad2910fb6029796f6a6a6964 --- src/main.rs | 7 +++---- src/wayland.rs | 23 ++++++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5981dc1..e9e3a15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ struct Cli { fn main() -> ExitCode { init_logging(); match run(Cli::parse()) { - Ok(()) => ExitCode::SUCCESS, + Ok(code) => code, Err(err) => { tracing::error!("{err:#}"); eprintln!("beer: {err:#}"); @@ -45,10 +45,9 @@ fn init_logging() { .init(); } -fn run(cli: Cli) -> anyhow::Result<()> { +fn run(cli: Cli) -> anyhow::Result { if cli.server { - tracing::info!("starting beer server"); - todo!("server mode") + anyhow::bail!("server mode is not implemented yet"); } tracing::info!("starting beer"); diff --git a/src/wayland.rs b/src/wayland.rs index ec570c2..dd4f8f7 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -4,6 +4,8 @@ //! event loop, so the PTY master fd and timers share one loop. use std::os::fd::OwnedFd; +use std::os::unix::process::ExitStatusExt; +use std::process::ExitCode; use std::time::Duration; use anyhow::Context; @@ -45,8 +47,8 @@ const DEFAULT_H: u32 = 600; const FONT_FAMILY: &str = "monospace"; const FONT_SIZE_PX: u32 = 16; -/// Run a single window until it is closed. -pub fn run() -> anyhow::Result<()> { +/// Run a single window until it is closed, returning the shell's exit code. +pub fn run() -> anyhow::Result { let conn = Connection::connect_to_env().context("connect to Wayland compositor")?; let (globals, event_queue) = registry_queue_init(&conn).context("initialize Wayland registry")?; @@ -127,6 +129,7 @@ pub fn run() -> anyhow::Result<()> { height: DEFAULT_H, dirty: false, exit: false, + exit_code: ExitCode::SUCCESS, }; while !app.exit { @@ -138,7 +141,7 @@ pub fn run() -> anyhow::Result<()> { app.dirty = false; } } - Ok(()) + Ok(app.exit_code) } /// Columns and rows that fit a `width`×`height` px window at `metrics`. @@ -180,6 +183,8 @@ struct App { /// The grid changed and the window needs repainting. dirty: bool, exit: bool, + /// Exit code to return, taken from the shell when it exits. + exit_code: ExitCode, } impl App { @@ -213,10 +218,18 @@ impl App { } } - /// The child shell has gone away; reap it and tear the window down. + /// The child shell has gone away; reap it, capture its code, and tear the + /// window down. fn child_exited(&mut self) { match self.pty.wait() { - Ok(status) => tracing::info!("shell exited: {status}"), + Ok(status) => { + tracing::info!("shell exited: {status}"); + // Mirror the shell's status: its code, or 128+signal if killed. + let code = status + .code() + .unwrap_or_else(|| 128 + status.signal().unwrap_or(0)); + self.exit_code = ExitCode::from(code as u8); + } Err(err) => tracing::warn!("reap shell: {err}"), } self.exit = true;