diff --git a/Cargo.lock b/Cargo.lock index ab95aad..33bdc7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2224,7 +2224,7 @@ dependencies = [ [[package]] name = "mouth" -version = "0.2.1" +version = "0.2.3" dependencies = [ "anyhow", "arboard", diff --git a/Cargo.toml b/Cargo.toml index 7ce3a84..ffe6df2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mouth" -version = "0.2.2" +version = "0.2.3" edition = "2024" description = "Offline speech-to-text with global hotkey and paste" license-file = "LICENSE" diff --git a/src/cli/run_cmd.rs b/src/cli/run_cmd.rs index 4ff480f..d203bc0 100644 --- a/src/cli/run_cmd.rs +++ b/src/cli/run_cmd.rs @@ -22,10 +22,6 @@ pub fn run() -> Result<()> { std::process::exit(1); } - // Hide Windows console window - #[cfg(windows)] - hide_console(); - info!("Mouth v{} starting", env!("CARGO_PKG_VERSION")); info!("Mode: {:?}", config.mode); info!("Hotkey: {}", config.hotkey); @@ -146,15 +142,3 @@ pub fn run() -> Result<()> { ipc::cleanup(); Ok(()) } - -#[cfg(windows)] -fn hide_console() { - use windows_sys::Win32::System::Console::GetConsoleWindow; - use windows_sys::Win32::UI::WindowsAndMessaging::{ShowWindow, SW_HIDE}; - unsafe { - let console = GetConsoleWindow(); - if !console.is_null() { - ShowWindow(console, SW_HIDE); - } - } -} diff --git a/src/ipc.rs b/src/ipc.rs index f2132d8..e444932 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::io::{Read, Write}; use std::sync::Arc; -use tracing::{debug, info, warn}; +use tracing::{debug, info}; use crate::shared_state::SharedState; @@ -116,7 +116,7 @@ fn unix_listener(path: &str, shared_state: Arc) -> Result<()> { } } Err(e) => { - warn!("Failed to serialize status: {e}"); + tracing::warn!("Failed to serialize status: {e}"); } } } diff --git a/src/main.rs b/src/main.rs index 4ec3990..624f845 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(windows, windows_subsystem = "windows")] + mod audio_feedback; mod cli; mod config; @@ -48,7 +50,16 @@ enum Commands { Status, } -fn main() -> anyhow::Result<()> { +fn main() { + // Attach to parent console when launched from a terminal so CLI subcommands + // can write output. Silently fails when double-clicked (no parent console). + #[cfg(windows)] + unsafe { + windows_sys::Win32::System::Console::AttachConsole( + windows_sys::Win32::System::Console::ATTACH_PARENT_PROCESS, + ); + } + tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() @@ -57,8 +68,9 @@ fn main() -> anyhow::Result<()> { .init(); let cli = Cli::parse(); + let is_daemon = matches!(cli.command, None | Some(Commands::Run)); - match cli.command { + let result = match cli.command { None | Some(Commands::Run) => cli::run_cmd::run(), Some(Commands::Config { show, reset }) => { @@ -80,5 +92,32 @@ fn main() -> anyhow::Result<()> { } Some(Commands::Status) => cli::status_cmd::status(), + }; + + if let Err(e) = result { + if is_daemon { + #[cfg(windows)] + show_error_dialog(&format!("Mouth failed to start:\n\n{e:#}")); + #[cfg(not(windows))] + eprintln!("Error: {e:#}"); + } else { + eprintln!("Error: {e:#}"); + } + std::process::exit(1); + } +} + +#[cfg(windows)] +fn show_error_dialog(message: &str) { + use windows_sys::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_ICONERROR, MB_OK}; + let title: Vec = "Mouth".encode_utf16().chain(Some(0)).collect(); + let msg: Vec = message.encode_utf16().chain(Some(0)).collect(); + unsafe { + MessageBoxW( + std::ptr::null_mut(), + msg.as_ptr(), + title.as_ptr(), + MB_OK | MB_ICONERROR, + ); } }