diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index bf4df8c1..1c157bb1 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -80,4 +80,4 @@ libc = "0.2" libc = "0.2" [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.60", features = ["Win32_Foundation"] } +windows = { version = "0.60", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging"] } diff --git a/crates/tui/src/tui/notifications.rs b/crates/tui/src/tui/notifications.rs index df7992ed..e6583a16 100644 --- a/crates/tui/src/tui/notifications.rs +++ b/crates/tui/src/tui/notifications.rs @@ -5,6 +5,9 @@ //! tmux DCS passthrough so OSC 9 reaches the outer terminal even when //! running inside a tmux session. +#[cfg(target_os = "windows")] +use windows::Win32::UI::WindowsAndMessaging::MessageBeep; + use std::io::{self, Write}; use std::time::Duration; @@ -42,6 +45,19 @@ impl Method { } } +/// Emit a Windows system beep via `MessageBeep(MB_OK)`. +/// +/// Writing BEL (`\\x07`) to the terminal is silent on most Windows +/// terminals (Windows Terminal, Conhost, etc.), so we call the Win32 +/// API directly to produce the standard notification sound. +#[cfg(target_os = "windows")] +fn windows_bell() { + // MB_OK = 0x00000000 — plays the default system sound. + unsafe { + MessageBeep(0x00000000); + } +} + /// Resolve `Auto` to a concrete method by inspecting `$TERM_PROGRAM`. /// /// Known OSC-9 capable programs: `iTerm.app`, `Ghostty`, `WezTerm` @@ -118,6 +134,14 @@ pub fn notify_done_to( // Best-effort: ignore write errors (e.g. stdout closed). let _ = sink.write_all(&bytes); let _ = sink.flush(); + + // On Windows, writing BEL (`\x07`) to the terminal is silent in most + // terminals (Windows Terminal, Conhost, etc.). Call MessageBeep to + // produce an actual notification sound via the system audio scheme. + #[cfg(target_os = "windows")] + if effective == Method::Bel { + windows_bell(); + } } /// Emit a turn-complete notification to **stdout** if `elapsed >= threshold`.