impress_voice_input/src/core/win_hotkey.cpp
impressionyang 8c2e787a25 feat: 添加 Windows 交叉编译支持与 ONNX Runtime MinGW 兼容方案
- 新增 C API shim (ort_api_shim.h) 解决 MinGW 与 ONNX Runtime 的 SAL 注解/_stdcall 兼容性问题
- 新增轻量级 C++ 包装器 (ort_minimal) 替代 onnxruntime_cxx_api.h
- cmake/dependencies.cmake 支持 Windows/ Linux 平台自动识别依赖路径
- 修复音频采集 paNonInterleaved bug(指针被误解析为 float 导致 RMS=inf)
- 修复 Windows 热键和 UI 相关代码
- 添加 MinGW 交叉编译工具链配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:50:16 +08:00

227 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "win_hotkey.h"
#include "utils/logger.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <QAbstractNativeEventFilter>
#include <QGuiApplication>
#include <QThread>
#include <QWidget>
#endif
static const char* const kTag = "CapsLockVoiceHotkey";
namespace impress {
struct CapsLockVoiceHotkey::Impl {
#ifdef Q_OS_WIN
int hotkeyId = 0;
QTimer* longPressTimer = nullptr;
QTimer* keyUpDebounce = nullptr;
bool isHolding = false;
bool longPressFired = false;
bool pollThreadRunning = false;
void* nativeEventFilter = nullptr;
QWidget* hiddenWindow = nullptr;
static constexpr int kLongPressMs = 1000;
#endif
};
#ifdef Q_OS_WIN
/** Native event filter to catch WM_HOTKEY */
class HotkeyNativeEventFilter : public QAbstractNativeEventFilter {
public:
explicit HotkeyNativeEventFilter(CapsLockVoiceHotkey* hotkey)
: hotkey_(hotkey) {}
bool nativeEventFilter(const QByteArray& eventType, void* message,
qintptr* /*result*/) override {
if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") {
auto* msg = static_cast<MSG*>(message);
if (msg->message == WM_HOTKEY) {
if (hotkey_) {
hotkey_->onHotkeyEvent(static_cast<int>(msg->wParam));
}
return true;
}
}
return false;
}
private:
CapsLockVoiceHotkey* hotkey_ = nullptr;
};
#endif
CapsLockVoiceHotkey::CapsLockVoiceHotkey(QObject* parent)
: QObject(parent)
, impl_(std::make_unique<Impl>())
{}
CapsLockVoiceHotkey::~CapsLockVoiceHotkey() {
stop();
}
bool CapsLockVoiceHotkey::start() {
if (active_) return true;
#ifdef Q_OS_WIN
// 创建隐藏窗口用于接收 WM_HOTKEY 消息
if (!impl_->hiddenWindow) {
impl_->hiddenWindow = new QWidget();
impl_->hiddenWindow->setObjectName("HotkeyReceiver");
impl_->hiddenWindow->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
impl_->hiddenWindow->resize(0, 0);
}
// 确保窗口已创建show 会创建原生句柄)
impl_->hiddenWindow->show();
HWND hwnd = reinterpret_cast<HWND>(impl_->hiddenWindow->winId());
if (!hwnd) {
emit error("无法创建窗口句柄");
return false;
}
// 注册 CapsLock (VK_CAPITAL = 0x14) 全局快捷键
// MOD_NOREPEAT 防止按住时重复触发
const int vkCapsLock = 0x14;
impl_->hotkeyId = GlobalAddAtom(L"ImpressVoiceHotkey");
BOOL ok = RegisterHotKey(hwnd, impl_->hotkeyId, MOD_NOREPEAT, vkCapsLock);
if (!ok) {
DWORD err = GetLastError();
emit error(QString("注册 CapsLock 快捷键失败 (错误码: %1)").arg(err));
LOG_ERROR(kTag, QString("RegisterHotKey failed: %1").arg(err));
return false;
}
// 安装原生事件过滤器
auto* filter = new HotkeyNativeEventFilter(this);
QGuiApplication::instance()->installNativeEventFilter(filter);
impl_->nativeEventFilter = filter;
// 长按定时器
impl_->longPressTimer = new QTimer(this);
impl_->longPressTimer->setSingleShot(true);
impl_->longPressTimer->setInterval(Impl::kLongPressMs);
connect(impl_->longPressTimer, &QTimer::timeout, this, [this]() {
if (impl_->isHolding && !impl_->longPressFired) {
impl_->longPressFired = true;
recording_ = true;
emit recordingStarted();
LOG_DEBUG(kTag, "长按触发,开始录音");
}
});
// 松开后延迟重置,避免 CapsLock 状态闪烁
impl_->keyUpDebounce = new QTimer(this);
impl_->keyUpDebounce->setSingleShot(true);
impl_->keyUpDebounce->setInterval(200);
connect(impl_->keyUpDebounce, &QTimer::timeout, this, [this]() {
impl_->isHolding = false;
impl_->longPressFired = false;
});
active_ = true;
emit ready();
LOG_INFO(kTag, "CapsLock 快捷键已注册");
return true;
#else
emit error("CapsLockVoiceHotkey 仅支持 Windows 平台");
return false;
#endif
}
void CapsLockVoiceHotkey::stop() {
if (!active_) return;
#ifdef Q_OS_WIN
impl_->pollThreadRunning = false;
if (impl_->longPressTimer) {
impl_->longPressTimer->stop();
}
if (impl_->keyUpDebounce) {
impl_->keyUpDebounce->stop();
}
// 移除原生事件过滤器
if (impl_->nativeEventFilter) {
auto* filter = static_cast<HotkeyNativeEventFilter*>(impl_->nativeEventFilter);
QGuiApplication::instance()->removeNativeEventFilter(filter);
delete filter;
impl_->nativeEventFilter = nullptr;
}
// 注销快捷键
if (impl_->hiddenWindow) {
HWND hwnd = reinterpret_cast<HWND>(impl_->hiddenWindow->winId());
if (hwnd && impl_->hotkeyId) {
UnregisterHotKey(hwnd, impl_->hotkeyId);
GlobalDeleteAtom(impl_->hotkeyId);
impl_->hotkeyId = 0;
}
impl_->hiddenWindow->hide();
}
active_ = false;
recording_ = false;
impl_->isHolding = false;
impl_->longPressFired = false;
LOG_INFO(kTag, "CapsLock 快捷键已停止");
#endif
}
#ifdef Q_OS_WIN
void CapsLockVoiceHotkey::onHotkeyEvent(int /*hotkeyId*/) {
if (!active_) return;
// Windows 只在按键按下时触发 WM_HOTKEY
// 我们通过 GetAsyncKeyState 轮询检测松开
impl_->isHolding = true;
impl_->longPressFired = false;
// 启动长按定时器
if (impl_->longPressTimer) {
impl_->longPressTimer->start();
}
// 启动轮询线程检测松开
if (impl_->pollThreadRunning) return;
impl_->pollThreadRunning = true;
QThread::create([this]() {
const int vkCapsLock = 0x14;
while (impl_->pollThreadRunning && impl_->isHolding) {
SHORT state = GetAsyncKeyState(vkCapsLock);
if (!(state & 0x8000)) {
// 按键松开
impl_->isHolding = false;
if (impl_->longPressTimer) {
impl_->longPressTimer->stop();
}
QMetaObject::invokeMethod(this, [this]() {
if (impl_->longPressFired) {
// 长按 → 停止录音
recording_ = false;
emit recordingStopped();
LOG_DEBUG(kTag, "长按结束,停止录音");
} else {
// 短按 → 不处理(让系统处理 CapsLock
LOG_DEBUG(kTag, "短按,不拦截 CapsLock");
}
impl_->longPressFired = false;
if (impl_->keyUpDebounce) {
impl_->keyUpDebounce->start();
}
}, Qt::QueuedConnection);
break;
}
QThread::msleep(50);
}
impl_->pollThreadRunning = false;
})->start();
}
#endif
} // namespace impress