diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index 76d12df..4a738df 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -1,7 +1,7 @@ #[cfg(all(target_os = "macos", target_arch = "aarch64"))] use crate::apple_intelligence; use crate::audio_feedback::{play_feedback_sound, play_feedback_sound_blocking, SoundType}; -use crate::audio_toolkit::is_microphone_access_denied; +use crate::audio_toolkit::{is_microphone_access_denied, is_no_input_device_error}; use crate::managers::audio::AudioRecordingManager; use crate::managers::history::HistoryManager; use crate::managers::transcription::TranscriptionManager; @@ -436,6 +436,8 @@ impl ShortcutAction for TranscribeAction { if let Some(err) = recording_error { let error_type = if is_microphone_access_denied(&err) { "microphone_permission_denied" + } else if is_no_input_device_error(&err) { + "no_input_device" } else { "unknown" }; diff --git a/src-tauri/src/audio_toolkit/audio/mod.rs b/src-tauri/src/audio_toolkit/audio/mod.rs index a25360a..f973dae 100644 --- a/src-tauri/src/audio_toolkit/audio/mod.rs +++ b/src-tauri/src/audio_toolkit/audio/mod.rs @@ -6,7 +6,7 @@ mod utils; mod visualizer; pub use device::{list_input_devices, list_output_devices, CpalDeviceInfo}; -pub use recorder::{is_microphone_access_denied, AudioRecorder}; +pub use recorder::{is_microphone_access_denied, is_no_input_device_error, AudioRecorder}; pub use resampler::FrameResampler; pub use utils::{read_wav_samples, save_wav_file, verify_wav_file}; pub use visualizer::AudioVisualiser; diff --git a/src-tauri/src/audio_toolkit/audio/recorder.rs b/src-tauri/src/audio_toolkit/audio/recorder.rs index 01a033d..ef94a98 100644 --- a/src-tauri/src/audio_toolkit/audio/recorder.rs +++ b/src-tauri/src/audio_toolkit/audio/recorder.rs @@ -342,9 +342,16 @@ pub fn is_microphone_access_denied(error_message: &str) -> bool { || normalized.contains("0x80070005") } +pub fn is_no_input_device_error(error_message: &str) -> bool { + let normalized = error_message.to_lowercase(); + normalized.contains("no input device found") + || (normalized.contains("failed to fetch preferred config") + && normalized.contains("coreaudio")) +} + #[cfg(test)] mod tests { - use super::is_microphone_access_denied; + use super::{is_microphone_access_denied, is_no_input_device_error}; #[test] fn detects_access_is_denied() { @@ -365,6 +372,24 @@ mod tests { fn does_not_match_unrelated_errors() { assert!(!is_microphone_access_denied("device not found")); } + + #[test] + fn detects_no_input_device() { + assert!(is_no_input_device_error("No input device found")); + } + + #[test] + fn detects_coreaudio_config_error() { + assert!(is_no_input_device_error( + "Failed to fetch preferred config: A backend-specific error has occurred: An unknown error unknown to the coreaudio-rs API occurred" + )); + } + + #[test] + fn does_not_match_other_errors_for_no_device() { + assert!(!is_no_input_device_error("permission denied")); + assert!(!is_no_input_device_error("device not found")); + } } fn run_consumer( diff --git a/src-tauri/src/audio_toolkit/mod.rs b/src-tauri/src/audio_toolkit/mod.rs index a94f27c..304be5f 100644 --- a/src-tauri/src/audio_toolkit/mod.rs +++ b/src-tauri/src/audio_toolkit/mod.rs @@ -5,8 +5,9 @@ pub mod utils; pub mod vad; pub use audio::{ - is_microphone_access_denied, list_input_devices, list_output_devices, read_wav_samples, - save_wav_file, verify_wav_file, AudioRecorder, CpalDeviceInfo, + is_microphone_access_denied, is_no_input_device_error, list_input_devices, + list_output_devices, read_wav_samples, save_wav_file, verify_wav_file, AudioRecorder, + CpalDeviceInfo, }; pub use text::{apply_custom_words, filter_transcription_output}; pub use utils::get_cpal_host; diff --git a/src-tauri/src/managers/audio.rs b/src-tauri/src/managers/audio.rs index 3b1ac73..b3378b7 100644 --- a/src-tauri/src/managers/audio.rs +++ b/src-tauri/src/managers/audio.rs @@ -297,6 +297,18 @@ impl AudioRecordingManager { let settings = get_settings(&self.app_handle); let selected_device = self.get_effective_microphone_device(&settings); + // Pre-flight check: if no device was selected/configured AND no devices + // exist at all, fail early with a clear error instead of letting cpal + // produce a cryptic backend-specific message. + if selected_device.is_none() { + let has_any_device = list_input_devices() + .map(|devices| !devices.is_empty()) + .unwrap_or(false); + if !has_any_device { + return Err(anyhow::anyhow!("No input device found")); + } + } + if let Some(rec) = recorder_opt.as_mut() { rec.open(selected_device) .map_err(|e| anyhow::anyhow!("Failed to open recorder: {}", e))?; diff --git a/src/App.tsx b/src/App.tsx index e09ade6..ea378e5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -107,6 +107,10 @@ function App() { defaultValue: t("errors.micPermissionDenied.generic"), }); toast.error(t("errors.micPermissionDeniedTitle"), { description }); + } else if (error_type === "no_input_device") { + toast.error(t("errors.noInputDeviceTitle"), { + description: t("errors.noInputDevice"), + }); } else { toast.error( t("errors.recordingFailed", { error: detail ?? "Unknown error" }), diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index a228f1d..55ce0fe 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -581,6 +581,8 @@ "macos": "امنح الوصول إلى الميكروفون في إعدادات النظام ← الخصوصية والأمان ← الميكروفون.", "linux": "امنح الوصول إلى الميكروفون في إعدادات الصوت أو الخصوصية في نظامك." }, + "noInputDeviceTitle": "لم يتم العثور على ميكروفون", + "noInputDevice": "لم يتم اكتشاف أي جهاز إدخال صوتي. يرجى توصيل ميكروفون أو سماعة رأس والمحاولة مرة أخرى.", "recordingFailed": "فشل في بدء التسجيل: {{error}}", "modelLoadFailed": "فشل في تحميل النموذج: {{model}}", "modelLoadFailedUnknown": "نموذج غير معروف" diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 877ccdb..0dd34f3 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -581,6 +581,8 @@ "macos": "Povolte přístup k mikrofonu v Nastavení systému → Soukromí a zabezpečení → Mikrofon.", "linux": "Povolte přístup k mikrofonu v nastavení zvuku nebo soukromí vašeho systému." }, + "noInputDeviceTitle": "Mikrofon nebyl nalezen", + "noInputDevice": "Nebylo detekováno žádné zvukové vstupní zařízení. Připojte mikrofon nebo sluchátka a zkuste to znovu.", "recordingFailed": "Nepodařilo se spustit nahrávání: {{error}}", "modelLoadFailed": "Nepodařilo se načíst model: {{model}}", "modelLoadFailedUnknown": "neznámý model" diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index d476002..249c274 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -581,6 +581,8 @@ "macos": "Erteilen Sie den Mikrofonzugriff in Systemeinstellungen → Datenschutz & Sicherheit → Mikrofon.", "linux": "Erteilen Sie den Mikrofonzugriff in den Sound- oder Datenschutzeinstellungen Ihres Systems." }, + "noInputDeviceTitle": "Kein Mikrofon gefunden", + "noInputDevice": "Es wurde kein Audio-Eingabegerät erkannt. Bitte schließen Sie ein Mikrofon oder Headset an und versuchen Sie es erneut.", "recordingFailed": "Aufnahme konnte nicht gestartet werden: {{error}}", "modelLoadFailed": "Modell konnte nicht geladen werden: {{model}}", "modelLoadFailedUnknown": "unbekanntes Modell" diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index f7cf056..583bdbb 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -581,6 +581,8 @@ "macos": "Grant microphone access in System Settings → Privacy & Security → Microphone.", "linux": "Grant microphone access in your system's sound or privacy settings." }, + "noInputDeviceTitle": "No Microphone Found", + "noInputDevice": "No audio input device was detected. Please connect a microphone or headset and try again.", "recordingFailed": "Failed to start recording: {{error}}", "modelLoadFailed": "Failed to load model: {{model}}", "modelLoadFailedUnknown": "unknown model" diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index fb3644f..dcfdef8 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -581,6 +581,8 @@ "macos": "Conceda el acceso al micrófono en Ajustes del Sistema → Privacidad y seguridad → Micrófono.", "linux": "Conceda el acceso al micrófono en la configuración de sonido o privacidad de su sistema." }, + "noInputDeviceTitle": "No se encontró micrófono", + "noInputDevice": "No se detectó ningún dispositivo de entrada de audio. Conecta un micrófono o auriculares e inténtalo de nuevo.", "recordingFailed": "Error al iniciar la grabación: {{error}}", "modelLoadFailed": "Error al cargar el modelo: {{model}}", "modelLoadFailedUnknown": "modelo desconocido" diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 0deb8e2..7953c44 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -581,6 +581,8 @@ "macos": "Accordez l'accès au microphone dans Réglages du système → Confidentialité et sécurité → Microphone.", "linux": "Accordez l'accès au microphone dans les paramètres de son ou de confidentialité de votre système." }, + "noInputDeviceTitle": "Aucun microphone trouvé", + "noInputDevice": "Aucun périphérique d'entrée audio n'a été détecté. Veuillez connecter un microphone ou un casque et réessayer.", "recordingFailed": "Échec du démarrage de l'enregistrement : {{error}}", "modelLoadFailed": "Échec du chargement du modèle : {{model}}", "modelLoadFailedUnknown": "modèle inconnu" diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index c7cf6a8..259d0b3 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -581,6 +581,8 @@ "macos": "Concedi l'accesso al microfono in Impostazioni di Sistema → Privacy e sicurezza → Microfono.", "linux": "Concedi l'accesso al microfono nelle impostazioni audio o sulla privacy del tuo sistema." }, + "noInputDeviceTitle": "Nessun microfono trovato", + "noInputDevice": "Non è stato rilevato alcun dispositivo di ingresso audio. Collega un microfono o delle cuffie e riprova.", "recordingFailed": "Impossibile avviare la registrazione: {{error}}", "modelLoadFailed": "Impossibile caricare il modello: {{model}}", "modelLoadFailedUnknown": "modello sconosciuto" diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index 5fdc8c3..815baee 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -581,6 +581,8 @@ "macos": "システム設定 → プライバシーとセキュリティ → マイクでマイクへのアクセスを許可してください。", "linux": "システムのサウンドまたはプライバシー設定でマイクへのアクセスを許可してください。" }, + "noInputDeviceTitle": "マイクが見つかりません", + "noInputDevice": "オーディオ入力デバイスが検出されませんでした。マイクまたはヘッドセットを接続してから再試行してください。", "recordingFailed": "録音の開始に失敗しました: {{error}}", "modelLoadFailed": "モデルの読み込みに失敗しました: {{model}}", "modelLoadFailedUnknown": "不明なモデル" diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index e1c8ccb..e76569e 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -581,6 +581,8 @@ "macos": "시스템 설정 → 개인 정보 보호 및 보안 → 마이크에서 마이크 접근을 허용하세요.", "linux": "시스템의 소리 또는 개인 정보 설정에서 마이크 접근을 허용하세요." }, + "noInputDeviceTitle": "마이크를 찾을 수 없음", + "noInputDevice": "오디오 입력 장치가 감지되지 않았습니다. 마이크 또는 헤드셋을 연결한 후 다시 시도해 주세요.", "recordingFailed": "녹음을 시작하지 못했습니다: {{error}}", "modelLoadFailed": "모델을 불러오지 못했습니다: {{model}}", "modelLoadFailedUnknown": "알 수 없는 모델" diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 36c2c96..7b6ae7c 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -581,6 +581,8 @@ "macos": "Przyznaj dostęp do mikrofonu w Ustawienia systemowe → Prywatność i ochrona → Mikrofon.", "linux": "Przyznaj dostęp do mikrofonu w ustawieniach dźwięku lub prywatności systemu." }, + "noInputDeviceTitle": "Nie znaleziono mikrofonu", + "noInputDevice": "Nie wykryto żadnego urządzenia wejściowego audio. Podłącz mikrofon lub słuchawki i spróbuj ponownie.", "recordingFailed": "Nie udało się rozpocząć nagrywania: {{error}}", "modelLoadFailed": "Nie udało się załadować modelu: {{model}}", "modelLoadFailedUnknown": "nieznany model" diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index 3d62a17..c092dfe 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -581,6 +581,8 @@ "macos": "Conceda o acesso ao microfone em Ajustes do Sistema → Privacidade e Segurança → Microfone.", "linux": "Conceda o acesso ao microfone nas configurações de som ou privacidade do seu sistema." }, + "noInputDeviceTitle": "Nenhum microfone encontrado", + "noInputDevice": "Nenhum dispositivo de entrada de áudio foi detectado. Conecte um microfone ou fone de ouvido e tente novamente.", "recordingFailed": "Falha ao iniciar a gravação: {{error}}", "modelLoadFailed": "Falha ao carregar o modelo: {{model}}", "modelLoadFailedUnknown": "modelo desconhecido" diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index cb88f67..cdfc56a 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -581,6 +581,8 @@ "macos": "Предоставьте доступ к микрофону в Системные настройки → Конфиденциальность и безопасность → Микрофон.", "linux": "Предоставьте доступ к микрофону в настройках звука или конфиденциальности вашей системы." }, + "noInputDeviceTitle": "Микрофон не найден", + "noInputDevice": "Аудиоустройство ввода не обнаружено. Подключите микрофон или гарнитуру и попробуйте снова.", "recordingFailed": "Не удалось начать запись: {{error}}", "modelLoadFailed": "Не удалось загрузить модель: {{model}}", "modelLoadFailedUnknown": "неизвестная модель" diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index 4574b6c..a005f35 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -581,6 +581,8 @@ "macos": "Sistem Ayarları → Gizlilik ve Güvenlik → Mikrofon bölümünden mikrofon erişimini verin.", "linux": "Sisteminizin ses veya gizlilik ayarlarından mikrofon erişimini verin." }, + "noInputDeviceTitle": "Mikrofon bulunamadı", + "noInputDevice": "Ses giriş aygıtı algılanamadı. Lütfen bir mikrofon veya kulaklık bağlayın ve tekrar deneyin.", "recordingFailed": "Kayıt başlatılamadı: {{error}}", "modelLoadFailed": "Model yüklenemedi: {{model}}", "modelLoadFailedUnknown": "bilinmeyen model" diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index 1aa0eb0..e031f53 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -581,6 +581,8 @@ "macos": "Надайте доступ до мікрофона в Системні налаштування → Конфіденційність і безпека → Мікрофон.", "linux": "Надайте доступ до мікрофона в налаштуваннях звуку або конфіденційності вашої системи." }, + "noInputDeviceTitle": "Мікрофон не знайдено", + "noInputDevice": "Не виявлено жодного пристрою введення звуку. Підключіть мікрофон або гарнітуру та спробуйте знову.", "recordingFailed": "Не вдалося розпочати запис: {{error}}", "modelLoadFailed": "Не вдалося завантажити модель: {{model}}", "modelLoadFailedUnknown": "невідома модель" diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index e406b44..bacbb7b 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -581,6 +581,8 @@ "macos": "Cấp quyền truy cập micrô trong Cài đặt hệ thống → Quyền riêng tư & Bảo mật → Micrô.", "linux": "Cấp quyền truy cập micrô trong cài đặt âm thanh hoặc quyền riêng tư của hệ thống." }, + "noInputDeviceTitle": "Không tìm thấy micrô", + "noInputDevice": "Không phát hiện thiết bị đầu vào âm thanh nào. Vui lòng kết nối micrô hoặc tai nghe và thử lại.", "recordingFailed": "Không thể bắt đầu ghi âm: {{error}}", "modelLoadFailed": "Không thể tải mô hình: {{model}}", "modelLoadFailedUnknown": "mô hình không xác định" diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index 6f27da2..f5c1421 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -581,6 +581,8 @@ "macos": "在系統設定 → 隱私與安全性 → 麥克風中授予麥克風存取權限。", "linux": "在系統的聲音或隱私設定中授予麥克風存取權限。" }, + "noInputDeviceTitle": "找不到麥克風", + "noInputDevice": "未偵測到音訊輸入裝置。請連接麥克風或耳機後重試。", "recordingFailed": "錄音啟動失敗: {{error}}", "modelLoadFailed": "載入模型失敗: {{model}}", "modelLoadFailedUnknown": "未知模型" diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index de6efc8..3958e23 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -581,6 +581,8 @@ "macos": "在系统设置 → 隐私与安全性 → 麦克风中授予麦克风访问权限。", "linux": "在系统的声音或隐私设置中授予麦克风访问权限。" }, + "noInputDeviceTitle": "未找到麦克风", + "noInputDevice": "未检测到音频输入设备。请连接麦克风或耳机后重试。", "recordingFailed": "录音启动失败: {{error}}", "modelLoadFailed": "加载模型失败: {{model}}", "modelLoadFailedUnknown": "未知模型"