v0.2.3: Run without console window on Windows

- Add windows_subsystem = "windows" to prevent console window on double-click
- Use AttachConsole(ATTACH_PARENT_PROCESS) so CLI subcommands still work from a terminal
- Show MessageBoxW error dialog if daemon fails to start
- Remove hide_console() (now unnecessary)
- Fix unused import warning in ipc.rs (warn → tracing::warn! inline)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-15 05:46:53 +01:00
parent e3f823045c
commit 82181545a6
5 changed files with 45 additions and 22 deletions
Generated
+1 -1
View File
@@ -2224,7 +2224,7 @@ dependencies = [
[[package]]
name = "mouth"
version = "0.2.1"
version = "0.2.3"
dependencies = [
"anyhow",
"arboard",
+1 -1
View File
@@ -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"
-16
View File
@@ -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);
}
}
}
+2 -2
View File
@@ -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<SharedState>) -> Result<()> {
}
}
Err(e) => {
warn!("Failed to serialize status: {e}");
tracing::warn!("Failed to serialize status: {e}");
}
}
}
+41 -2
View File
@@ -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<u16> = "Mouth".encode_utf16().chain(Some(0)).collect();
let msg: Vec<u16> = message.encode_utf16().chain(Some(0)).collect();
unsafe {
MessageBoxW(
std::ptr::null_mut(),
msg.as_ptr(),
title.as_ptr(),
MB_OK | MB_ICONERROR,
);
}
}