diff --git a/src/core/voice_input_service.cpp b/src/core/voice_input_service.cpp index b2943ef..edfad7d 100644 --- a/src/core/voice_input_service.cpp +++ b/src/core/voice_input_service.cpp @@ -40,34 +40,25 @@ VoiceInputService::VoiceInputService(ConfigManager* configManager, { impl_->sttEngine = sttEngine; - // 1s 定时器:启动录音 + // 长按确认定时器(1s) longPressTimer_ = new QTimer(this); longPressTimer_->setSingleShot(true); connect(longPressTimer_, &QTimer::timeout, this, [this]() { - if (!longPressDetected_) { - longPressDetected_ = true; + if (state_ == PreRecording) { + state_ = Recording; emit statusChanged("正在录音..."); + LOG_DEBUG(kTag, "状态转换: PreRecording → Recording (长按确认)"); } }); - // 3s 定时器:复位 CapsLock 灯(防抖:只在未按下状态下执行) - capsResetTimer_ = new QTimer(this); - capsResetTimer_->setSingleShot(true); - connect(capsResetTimer_, &QTimer::timeout, this, [this]() { - // 如果已经松开(recording_ = false),不执行 - if (recording_) { - capsResetDone_ = true; - simulateCapsLock(); - LOG_DEBUG(kTag, "CapsLock 灯已复位(防抖保护中)"); - } - }); - - // 松开后的冷却定时器 + // 松开后冷却定时器 cooldownTimer_ = new QTimer(this); cooldownTimer_->setSingleShot(true); connect(cooldownTimer_, &QTimer::timeout, this, [this]() { - cooldownActive_ = false; - LOG_DEBUG(kTag, "冷却期结束,恢复 CapsLock 检测"); + if (state_ == Cooldown) { + state_ = Idle; + LOG_DEBUG(kTag, "状态转换: Cooldown → Idle (冷却结束)"); + } }); } @@ -112,6 +103,7 @@ bool VoiceInputService::start() { } running_ = true; + state_ = Idle; emit statusChanged("语音输入已启动(等待授权...)"); LOG_INFO(kTag, "语音输入服务已启动"); return true; @@ -121,7 +113,6 @@ void VoiceInputService::stop() { if (!running_) return; longPressTimer_->stop(); - capsResetTimer_->stop(); cooldownTimer_->stop(); if (impl_->audioCapture) { @@ -133,74 +124,84 @@ void VoiceInputService::stop() { running_ = false; recording_ = false; - longPressDetected_ = false; - capsResetDone_ = false; - cooldownActive_ = false; + state_ = Idle; audioBuffer_.clear(); LOG_INFO(kTag, "语音输入服务已停止"); } void VoiceInputService::onHotkeyActivated() { - // CapsLock 已复位,用户仍按住键 → 忽略重复触发 - if (capsResetDone_) { - LOG_DEBUG(kTag, "忽略重复的 Activated(CapsLock 已复位,等待松开)"); + // Recording 状态:屏蔽所有重复 Activated(防抖核心) + if (state_ == Recording) { + LOG_DEBUG(kTag, "忽略 Activated (Recording 状态屏蔽)"); return; } - // 冷却期内 → 忽略 - if (cooldownActive_) { - LOG_DEBUG(kTag, "忽略 Activated(冷却期内)"); + // Cooldown 状态:冷却期内忽略 + if (state_ == Cooldown) { + LOG_DEBUG(kTag, "忽略 Activated (冷却期内)"); return; } - LOG_DEBUG(kTag, "快捷键激活(按下)"); - recording_ = true; - longPressDetected_ = false; - capsResetDone_ = false; - audioBuffer_.clear(); + // Idle 状态 → 开始预录音 + if (state_ == Idle) { + state_ = PreRecording; + recording_ = true; + audioBuffer_.clear(); - // 启动 1s 录音确认定时器 + 3s CapsLock 灯复位定时器 - longPressTimer_->start(longPressThreshold_); - capsResetTimer_->start(capsResetDelayMs_); + longPressTimer_->start(longPressThreshold_); - // 开始音频采集(后台预采集) - int deviceIndex = configManager_->get("audio.input_device").toInt(); - int sampleRate = configManager_->get("stt.sample_rate").toInt(); - int bufferSizeMs = configManager_->get("audio.buffer_size_ms").toInt(); - impl_->audioCapture->start(deviceIndex, sampleRate, bufferSizeMs); + int deviceIndex = configManager_->get("audio.input_device").toInt(); + int sampleRate = configManager_->get("stt.sample_rate").toInt(); + int bufferSizeMs = configManager_->get("audio.buffer_size_ms").toInt(); + impl_->audioCapture->start(deviceIndex, sampleRate, bufferSizeMs); - emit statusChanged("等待长按确认..."); + LOG_DEBUG(kTag, "状态转换: Idle → PreRecording"); + emit statusChanged("等待长按确认..."); + return; + } + + // PreRecording 状态收到重复 Activated → 忽略(防抖) + if (state_ == PreRecording) { + LOG_DEBUG(kTag, "忽略重复 Activated (PreRecording 防抖)"); + return; + } } void VoiceInputService::onHotkeyDeactivated() { - LOG_DEBUG(kTag, "快捷键停用(松开)"); + LOG_DEBUG(kTag, QString("Deactivated (state=%1)").arg(recording_ ? "recording" : "idle")); + + // Cooldown 状态的 Deactivated → 忽略 + if (state_ == Cooldown) { + LOG_DEBUG(kTag, "忽略 Deactivated (Cooldown 状态屏蔽)"); + return; + } + recording_ = false; longPressTimer_->stop(); - capsResetTimer_->stop(); // 停止音频采集 if (impl_->audioCapture && impl_->audioCapture->isRunning()) { impl_->audioCapture->stop(); } - if (!longPressDetected_) { - // 短按 → 模拟 CapsLock 按键 + if (state_ == PreRecording) { + // 短按 → 模拟 CapsLock 切换大小写 + state_ = Idle; LOG_DEBUG(kTag, "短按,模拟 CapsLock"); simulateCapsLock(); emit statusChanged("短按:切换 CapsLock"); - } else { - // 长按 → CapsLock 已在 3s 定时器时复位,松开后直接开始识别 + } else if (state_ == Recording) { + // 长按后松开 → 停止录音并转写 + state_ = Idle; + LOG_DEBUG(kTag, "状态转换: Recording → Idle (松开转写)"); stopRecordingAndTranscribe(); } - longPressDetected_ = false; - capsResetDone_ = false; - - // 启动冷却期,1s 内忽略新的 Activated - cooldownActive_ = true; + // 启动冷却期 + state_ = Cooldown; cooldownTimer_->start(releaseCooldownMs_); - LOG_DEBUG(kTag, QString("冷却期启动 (%1ms)").arg(releaseCooldownMs_)); + LOG_DEBUG(kTag, QString("状态转换: → Cooldown (%1ms)").arg(releaseCooldownMs_)); } void VoiceInputService::onAudioData(const std::vector& samples, int sampleRate) { @@ -259,7 +260,6 @@ void VoiceInputService::onRecognitionComplete(const QString& text) { void VoiceInputService::simulateCapsLock() { if (impl_->injector && impl_->injector->isInitialized()) { - // XK_Caps_Lock = 0xffe5,使用 simulateKeysym 自动转换为 keycode impl_->injector->simulateKeysym(0xffe5); LOG_DEBUG(kTag, "模拟 CapsLock 按键已注入"); } else { diff --git a/src/core/voice_input_service.h b/src/core/voice_input_service.h index 696b09a..c9887b9 100644 --- a/src/core/voice_input_service.h +++ b/src/core/voice_input_service.h @@ -17,12 +17,11 @@ class ConfigManager; /** * @brief CapsLock 语音输入服务 * - * 协调全局快捷键、音频采集、STT 推理和文本注入。 - * 状态机: - * 1. 按下 CapsLock → 开始预录音 - * 2. 长按超过阈值(默认 1s)→ 立即复位 CapsLock,正式录音 - * 3. 松开 CapsLock → 停止录音 → 推理 → 注入文本 - * 4. 短按(< 阈值)→ 注入 CapsLock 按键(切换大小写) + * 状态机(防止 CapsLock 抖动/误触): + * Idle — 空闲,等待按键 + * PreRecording — 按下 CapsLock,预录音,等待长按确认 + * Recording — 长按 1s 确认,正式录音(屏蔽所有 Portal 信号) + * Cooldown — 松开后冷却期,防止立即重新触发 */ class VoiceInputService : public QObject { Q_OBJECT @@ -60,25 +59,23 @@ private slots: void onRecognitionComplete(const QString& text); private: + enum State { Idle, PreRecording, Recording, Cooldown }; + State state_ = Idle; + struct Impl; ConfigManager* configManager_ = nullptr; std::unique_ptr impl_; bool running_ = false; bool recording_ = false; - bool longPressDetected_ = false; - bool capsResetDone_ = false; // CapsLock 复位后忽略重复 Activated - bool cooldownActive_ = false; // 松开后的冷却期,防止立即重新触发 - int longPressThreshold_ = 1000; // 1s 启动录音 - int capsResetDelayMs_ = 3000; // 3s 后复位 CapsLock 灯 - int releaseCooldownMs_ = 1000; // 松开后冷却时间 + int longPressThreshold_ = 1000; + int releaseCooldownMs_ = 1000; std::vector audioBuffer_; int audioSampleRate_ = 16000; - QTimer* longPressTimer_ = nullptr; // 1s 启动录音 - QTimer* capsResetTimer_ = nullptr; // 3s 复位 CapsLock 灯 - QTimer* cooldownTimer_ = nullptr; // 松开后冷却 + QTimer* longPressTimer_ = nullptr; + QTimer* cooldownTimer_ = nullptr; void startRecording(); void stopRecordingAndTranscribe();