2 Commits

Author SHA1 Message Date
steve 82181545a6 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>
2026-04-15 05:46:53 +01:00
steve e3f823045c Auto-publish in non-interactive mode in release script
When stdin is not a terminal (e.g. run from a tool or CI), skip the
confirmation prompt and proceed with publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:23:28 +01:00
6 changed files with 52 additions and 23 deletions
Generated
+1 -1
View File
@@ -2224,7 +2224,7 @@ dependencies = [
[[package]] [[package]]
name = "mouth" name = "mouth"
version = "0.2.1" version = "0.2.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arboard", "arboard",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mouth" name = "mouth"
version = "0.2.2" version = "0.2.3"
edition = "2024" edition = "2024"
description = "Offline speech-to-text with global hotkey and paste" description = "Offline speech-to-text with global hotkey and paste"
license-file = "LICENSE" license-file = "LICENSE"
+7 -1
View File
@@ -170,7 +170,13 @@ if [ ${#BUILT[@]} -eq 0 ]; then
fi fi
echo "" echo ""
read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm if [ -t 0 ]; then
read -rp "Publish release ${TAG} to ${FORGE}? [y/N] " confirm
else
# Non-interactive (piped/scripted) — default to yes
confirm="y"
echo "Non-interactive mode: auto-publishing release ${TAG} to ${FORGE}"
fi
if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
echo "Skipped. Artifacts are in ${RELEASE_DIR}/" echo "Skipped. Artifacts are in ${RELEASE_DIR}/"
exit 0 exit 0
-16
View File
@@ -22,10 +22,6 @@ pub fn run() -> Result<()> {
std::process::exit(1); std::process::exit(1);
} }
// Hide Windows console window
#[cfg(windows)]
hide_console();
info!("Mouth v{} starting", env!("CARGO_PKG_VERSION")); info!("Mouth v{} starting", env!("CARGO_PKG_VERSION"));
info!("Mode: {:?}", config.mode); info!("Mode: {:?}", config.mode);
info!("Hotkey: {}", config.hotkey); info!("Hotkey: {}", config.hotkey);
@@ -146,15 +142,3 @@ pub fn run() -> Result<()> {
ipc::cleanup(); ipc::cleanup();
Ok(()) 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 serde::{Deserialize, Serialize};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::sync::Arc; use std::sync::Arc;
use tracing::{debug, info, warn}; use tracing::{debug, info};
use crate::shared_state::SharedState; use crate::shared_state::SharedState;
@@ -116,7 +116,7 @@ fn unix_listener(path: &str, shared_state: Arc<SharedState>) -> Result<()> {
} }
} }
Err(e) => { 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 audio_feedback;
mod cli; mod cli;
mod config; mod config;
@@ -48,7 +50,16 @@ enum Commands {
Status, 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() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()
@@ -57,8 +68,9 @@ fn main() -> anyhow::Result<()> {
.init(); .init();
let cli = Cli::parse(); 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(), None | Some(Commands::Run) => cli::run_cmd::run(),
Some(Commands::Config { show, reset }) => { Some(Commands::Config { show, reset }) => {
@@ -80,5 +92,32 @@ fn main() -> anyhow::Result<()> {
} }
Some(Commands::Status) => cli::status_cmd::status(), 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,
);
} }
} }