fix: 长按 1s 录音 + 3s 复位 CapsLock 灯 + 防抖保护

- 分离两个定时器:longPressTimer(1s) 启动录音,capsResetTimer(3s) 复位灯
- 1s 时只确认录音状态,不操作 CapsLock 灯
- 3s 时才模拟 CapsLock 按键复位灯,避免手指抖动导致状态闪烁
- 松开时自动取消 3s 定时器,避免松开后再复位

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alvin Young 2026-06-11 14:38:30 +08:00
parent d4db8756b5
commit 13d4aae725
2 changed files with 26 additions and 10 deletions

View File

@ -39,19 +39,29 @@ VoiceInputService::VoiceInputService(ConfigManager* configManager,
, impl_(std::make_unique<Impl>())
{
impl_->sttEngine = sttEngine;
// 1s 定时器:启动录音
longPressTimer_ = new QTimer(this);
longPressTimer_->setSingleShot(true);
connect(longPressTimer_, &QTimer::timeout, this, [this]() {
// 长按超时仍未松开 → 确认为长按录音
if (!longPressDetected_) {
longPressDetected_ = true;
capsResetDone_ = true;
// 立即复位 CapsLock不等用户松开
simulateCapsLock();
emit statusChanged("正在录音...");
}
});
// 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);
@ -111,6 +121,7 @@ void VoiceInputService::stop() {
if (!running_) return;
longPressTimer_->stop();
capsResetTimer_->stop();
cooldownTimer_->stop();
if (impl_->audioCapture) {
@ -146,10 +157,12 @@ void VoiceInputService::onHotkeyActivated() {
LOG_DEBUG(kTag, "快捷键激活(按下)");
recording_ = true;
longPressDetected_ = false;
capsResetDone_ = false;
audioBuffer_.clear();
// 启动长按定时器
// 启动 1s 录音确认定时器 + 3s CapsLock 灯复位定时器
longPressTimer_->start(longPressThreshold_);
capsResetTimer_->start(capsResetDelayMs_);
// 开始音频采集(后台预采集)
int deviceIndex = configManager_->get("audio.input_device").toInt();
@ -164,6 +177,7 @@ void VoiceInputService::onHotkeyDeactivated() {
LOG_DEBUG(kTag, "快捷键停用(松开)");
recording_ = false;
longPressTimer_->stop();
capsResetTimer_->stop();
// 停止音频采集
if (impl_->audioCapture && impl_->audioCapture->isRunning()) {
@ -176,7 +190,7 @@ void VoiceInputService::onHotkeyDeactivated() {
simulateCapsLock();
emit statusChanged("短按:切换 CapsLock");
} else {
// 长按 → CapsLock 已在长按阈值时复位,松开后直接开始识别
// 长按 → CapsLock 已在 3s 定时器时复位,松开后直接开始识别
stopRecordingAndTranscribe();
}

View File

@ -69,14 +69,16 @@ private:
bool longPressDetected_ = false;
bool capsResetDone_ = false; // CapsLock 复位后忽略重复 Activated
bool cooldownActive_ = false; // 松开后的冷却期,防止立即重新触发
int longPressThreshold_ = 1000;
int releaseCooldownMs_ = 1000; // 松开后冷却时间
int longPressThreshold_ = 1000; // 1s 启动录音
int capsResetDelayMs_ = 3000; // 3s 后复位 CapsLock 灯
int releaseCooldownMs_ = 1000; // 松开后冷却时间
std::vector<float> audioBuffer_;
int audioSampleRate_ = 16000;
QTimer* longPressTimer_ = nullptr;
QTimer* cooldownTimer_ = nullptr;
QTimer* longPressTimer_ = nullptr; // 1s 启动录音
QTimer* capsResetTimer_ = nullptr; // 3s 复位 CapsLock 灯
QTimer* cooldownTimer_ = nullptr; // 松开后冷却
void startRecording();
void stopRecordingAndTranscribe();