Qt 中沒有實現好的跨平臺全局熱鍵,需要自己調用系統 API 來實現。
監聽系統原生事件
Qt 作為跨平臺的框架,實現的功能是在每個平臺都共有的部分,而有的功能可能由于某些原因不能跨平臺就沒有,這時候就需要調用系統原生的 API,比如全局熱鍵的功能、電池休眠事件監聽等。
窗口的原生事件
窗口的原生事件可以通過重載QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)
來實現。在函數中返回 true 會停止傳遞,如果返回 false 會繼續由 Qt 處理事件。當窗口有句柄的時候,觸發了平臺原生的事件就會調用此函數。由 eventType 標識本機平臺事件,這些事件的參數在 message 中傳遞。不同平臺的 eventType 、message 如下:
Platform | Event Type Identifier | Message Type | Result Type |
---|---|---|---|
Windows | "windows_generic_MSG" | MSG * | LRESULT |
macOS | "NSEvent" | NSEvent * | |
XCB | "xcb_generic_event_t" | xcb_generic_event_t * |
下面舉個例子,比如當前窗口在做一些操作,筆記本合上蓋子進入休眠,要監聽電池相關的事件:
bool MainWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG") {
MSG* msg = static_cast<MSG*>(message);
if (msg->message == WM_POWERBROADCAST) {
if(msg->wParam == PBT_APMSUSPEND) {
qDebug("系統休眠");
}
else if(msg->wParam == PBT_APMRESUMESUSPEND || msg->wParam == PBT_APMRESUMEAUTOMATIC) {
qDebug("系統喚醒");
}
}
}
return QWidget::nativeEvent(eventType, message, result);
}
應用級原生事件
像全局熱鍵這樣的事件是應用級別的,事件的監聽就要放在 app 上。通過繼承QAbstractNativeEventFilter
來實現自己的原生事件過濾器并安裝到 app 上即可。當有原生事件觸發后就會調用事件過濾器的virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *)
函數。
要處理熱鍵的話重寫函數如下:
bool MyWinEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *)
{
if(eventType == "windows_generic_MSG") {
MSG *msg = static_cast<MSG *>(message);
if(msg->message == WM_HOTKEY) {
//判斷是否為指定熱鍵按下
}
}
return false;
}
熱鍵的注冊
上面處理好了事件發生后的步驟,還需要告訴系統哪些算是熱鍵。注冊用到的原生 API 有:
BOOL RegisterHotKey(
HWND hWnd, //響應該熱鍵的窗口句柄, 由于是應用級的,可以不傳
Int id, //該熱鍵的唯一標識
UINT fsModifiers, //該熱鍵的輔助按鍵,Ctrl、Alt、Shift
UINT vk //該熱鍵的鍵值
);
BOOL UnregisterHotKey(
HWND hWnd, //響應該熱鍵的窗口句柄,同不傳
Int id //該熱鍵的唯一標識
);
注冊時按鍵的虛擬碼可以去官網查詢。
注冊按鍵的函數一般都是字符串的形式,可以自己寫個轉換函數,也可以使用 Qt 的QKeySequence
先得到熱鍵的 Qt 的鍵盤碼,再映射到 windwos 的虛擬碼。
具體實現
具體代碼就不貼了,在 Github 上有個封裝好各個平臺的全局熱鍵的倉庫,QHotkey
windows 相關實現在QHotkey/QHotkey/qhotkey_win.cpp
中,里面就是調用 RegisterHotKey
來實現的。