Win-specific: Add global hotkeys handling via low-level keyboard hook

This commit is contained in:
Abs62 2019-01-31 17:59:24 +03:00
parent eb6a7c83a6
commit 68fc27b7d7
8 changed files with 362 additions and 11 deletions

2
.gitignore vendored
View file

@ -18,6 +18,8 @@ Makefile.Release
goldendict.pro.user
goldendict.pro.user.*
hotkeys.pro.user
hotkeys.pro.user.*
object_script.goldendict.Debug
object_script.goldendict.Release

View file

@ -511,7 +511,8 @@ win32 {
sapi.hh \
sphelper.hh \
speechclient.hh \
speechhlp.hh
speechhlp.hh \
hotkeys.h
}
mac {

211
hotkeys.c Normal file
View file

@ -0,0 +1,211 @@
#include <windows.h>
#include "hotkeys.h"
#include "stdio.h"
static LRESULT CALLBACK lowLevelKeyboardProc(int, WPARAM, LPARAM);
// Max number of hotkeys
#define MAX_HOTKEYS 5
// Max time interval between first and second part of hotkey (ms)
#define MAX_KEYS_TIME_INTERVAL 500
static HINSTANCE hInstance;
static HWND hGDWindow;
static HHOOK hKbdHook;
typedef struct HotkeyStruct
{
DWORD key1;
DWORD key2;
DWORD mods;
DWORD lasttime;
} HotkeyStruct;
static HotkeyStruct hotkeys[ MAX_HOTKEYS ];
__declspec (dllexport) void removeHook()
{
if( hKbdHook )
{
UnhookWindowsHookEx( hKbdHook );
hKbdHook = 0;
}
}
__declspec (dllexport) BOOL setHook( HWND hwnd )
{
hGDWindow = hwnd;
removeHook();
hKbdHook = SetWindowsHookEx( WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0 );
return hKbdHook != 0;
}
__declspec (dllexport) BOOL setHotkeys( DWORD key1, DWORD key2, DWORD modifiers, int nom )
{
if( nom < 0 || nom >= MAX_HOTKEYS )
return FALSE;
hotkeys[ nom ].key1 = key1;
hotkeys[ nom ].key2 = key2;
hotkeys[ nom ].mods = modifiers;
hotkeys[ nom ].lasttime = 0;
return TRUE;
}
__declspec (dllexport) void clearHotkeys()
{
int i;
for( i = 0; i < MAX_HOTKEYS; i++ )
memset( hotkeys + i, 0, sizeof( HotkeyStruct ) );
}
static BOOL isModifiersPressed( DWORD modifiers )
{
int n = GetAsyncKeyState( VK_MENU ) & 0x8000;
if( ( ( modifiers & MOD_ALT ) && n == 0 )
|| ( ( modifiers & MOD_ALT ) == 0 && n ) )
return FALSE;
n = GetAsyncKeyState( VK_SHIFT ) & 0x8000;
if( ( ( modifiers & MOD_SHIFT ) && n == 0 )
|| ( ( modifiers & MOD_SHIFT ) == 0 && n ) )
return FALSE;
n = GetAsyncKeyState( VK_CONTROL ) & 0x8000;
if( ( ( modifiers & MOD_CONTROL ) && n == 0 )
|| ( ( modifiers & MOD_CONTROL ) == 0 && n ) )
return FALSE;
n = ( GetAsyncKeyState( VK_LWIN ) & 0x8000 ) | ( GetAsyncKeyState( VK_RWIN ) & 0x8000 );
if( ( ( modifiers & MOD_WIN ) && n == 0 )
|| ( ( modifiers & MOD_WIN ) == 0 && n ) )
return FALSE;
return TRUE;
}
static LRESULT CALLBACK lowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int i;
PKBDLLHOOKSTRUCT p;
BOOL stop = FALSE;
if ( nCode < 0 ) return CallNextHookEx( hKbdHook, nCode, wParam, lParam );
if( nCode == HC_ACTION )
{
p = (PKBDLLHOOKSTRUCT)lParam;
for( ; ; )
{
// Check hotkeys
if( wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN )
{
DWORD new_time = GetTickCount();
// Check if key is second part of hotkey
for( i = 0; i < MAX_HOTKEYS; i++ )
{
if( hotkeys[ i ].key1 == 0 )
break;
if( hotkeys[ i ].key2 == 0 || hotkeys[ i ].lasttime == 0 )
continue;
if( hotkeys[ i ].key2 == p->vkCode && isModifiersPressed( hotkeys[ i ].mods ) )
{
if( new_time - hotkeys[ i ].lasttime <= MAX_KEYS_TIME_INTERVAL )
{
// Hotkey completed
// Clear all flags for first part
int j;
for( j = 0; j < MAX_HOTKEYS; j++ )
hotkeys[ j ].lasttime = 0;
PostMessage( hGDWindow, GD_HOTKEY_MESSAGE, (WPARAM)i, 0 );
stop = TRUE;
break;
}
else
{
// Interval exceeded, reset time
hotkeys[ i ].lasttime = new_time;
continue;
}
}
else
{
// Key is not second part, clear flag
hotkeys[ i ].lasttime = 0;
}
}
if( stop )
break;
// Check if key is first part of hotkey
for( i = 0; i < MAX_HOTKEYS; i++ )
{
if( hotkeys[ i ].key1 == 0 )
break;
if( hotkeys[ i ].key1 == p->vkCode && isModifiersPressed( hotkeys[ i ].mods ) )
{
// Match found
if( hotkeys[ i ].key2 == 0 )
{
// No second part, hotkey completed
// Clear all flags for first part
int j;
for( j = 0; j < MAX_HOTKEYS; j++ )
hotkeys[ j ].lasttime = 0;
PostMessage( hGDWindow, GD_HOTKEY_MESSAGE, (WPARAM)i, 0 );
stop = TRUE;
break;
}
else
{
// First part detected, need wait for second part
hotkeys[ i ].lasttime = new_time;
break;
}
}
}
}
break;
}
}
LRESULT result = CallNextHookEx(hKbdHook, nCode, wParam, lParam);
return ( stop ? 1 : result );
}
BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
DWORD reason /* Reason this function is being called. */ ,
LPVOID reserved /* Not used. */ )
{
(void) reserved;
switch (reason)
{
case DLL_PROCESS_ATTACH:
hInstance = hInst;
break;
case DLL_PROCESS_DETACH:
removeHook();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
/* Returns TRUE on success, FALSE on failure */
return TRUE;
}

14
hotkeys.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef __HOTKEYS_H_INCLUDED__
#define __HOTKEYS_H_INCLUDED__
#include <windows.h>
// Message if hotkey completed; WPARAM - hotkey number
#define GD_HOTKEY_MESSAGE ( WM_APP + 1 )
typedef BOOL ( *setHookProc )( HWND hwnd );
typedef void ( *removeHookProc )();
typedef BOOL ( *setHotkeysProc )( DWORD, DWORD, DWORD, int );
typedef void ( *clearHotkeysProc )();
#endif // __HOTKEYS_H_INCLUDED__

22
hotkeys.pro Normal file
View file

@ -0,0 +1,22 @@
#-------------------------------------------------
#
# DLL for global hotkeys handling
# Should be build for Windows only
# Place GdHotkey.dll beside GoldenDict.exe
#
#-------------------------------------------------
win32 {
QT -= core gui
TARGET = GdHotkeys
TEMPLATE = lib
DEFINES += HOTKEYS_LIBRARY
SOURCES += \
hotkeys.c
HEADERS += \
hotkeys.h \
}

View file

@ -9,7 +9,7 @@
#include <QWidget>
#include <QMainWindow>
#ifdef Q_OS_WIN32
#ifdef Q_OS_WIN
#include "mainwindow.hh"
#endif
@ -107,6 +107,27 @@ HotkeyWrapper::HotkeyWrapper(QObject *parent) : QThread( parent ),
{
#ifdef Q_OS_WIN
hwnd=(HWND)((static_cast<QMainWindow*>(parent))->winId());
dllHandler.hDLLHandle = LoadLibraryA( "GdHotkeys.dll" );
if( dllHandler.hDLLHandle )
{
dllHandler.setHook = ( setHookProc )GetProcAddress( dllHandler.hDLLHandle, "setHook" );
dllHandler.removeHook = ( removeHookProc )GetProcAddress( dllHandler.hDLLHandle, "removeHook" );
dllHandler.setHotkeys = ( setHotkeysProc )GetProcAddress( dllHandler.hDLLHandle, "setHotkeys" );
dllHandler.clearHotkeys = ( clearHotkeysProc )GetProcAddress( dllHandler.hDLLHandle, "clearHotkeys" );
if( !dllHandler.setHook || !dllHandler.removeHook || !dllHandler.setHotkeys || !dllHandler.clearHotkeys )
{
FreeLibrary( dllHandler.hDLLHandle );
dllHandler.hDLLHandle = 0;
}
}
if( dllHandler.hDLLHandle )
gdWarning( "Handle global hotkeys via GdHotkeys.dll" );
else
gdWarning( "Handle global hotkeys via RegisterHotkey()" );
#else
init();
#endif
@ -116,6 +137,8 @@ HotkeyWrapper::HotkeyWrapper(QObject *parent) : QThread( parent ),
HotkeyWrapper::~HotkeyWrapper()
{
unregister();
if( dllHandler.hDLLHandle )
FreeLibrary( dllHandler.hDLLHandle );
}
void HotkeyWrapper::waitKey2()
@ -305,6 +328,14 @@ bool HotkeyWrapper::setGlobalKey( int key, int key2,
hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, id ) );
if( dllHandler.hDLLHandle )
{
dllHandler.removeHook();
dllHandler.setHotkeys( vk, vk2, mod, hotkeys.size() - 1 );
dllHandler.setHook( hwnd );
return true;
}
if (!RegisterHotKey(hwnd, id++, mod, vk))
return false;
@ -320,6 +351,14 @@ bool HotkeyWrapper::winEvent ( MSG * message, long * result )
if (message->message == WM_HOTKEY)
return checkState( (message->lParam >> 16), (message->lParam & 0xffff) );
if( message->message == GD_HOTKEY_MESSAGE )
{
int n = (int)message->wParam;
if( n < hotkeys.size() && n >= 0 )
emit hotkeyActivated( hotkeys.at( n ).handle );
return true;
}
return false;
}
@ -416,14 +455,22 @@ quint32 HotkeyWrapper::nativeKey(int key)
void HotkeyWrapper::unregister()
{
for (int i = 0; i < hotkeys.count(); i++)
if( dllHandler.hDLLHandle )
{
HotkeyStruct const & hk = hotkeys.at( i );
dllHandler.removeHook();
dllHandler.clearHotkeys();
}
else
{
for (int i = 0; i < hotkeys.count(); i++)
{
HotkeyStruct const & hk = hotkeys.at( i );
UnregisterHotKey( hwnd, hk.id );
UnregisterHotKey( hwnd, hk.id );
if ( hk.key2 && hk.key2 != hk.key )
UnregisterHotKey( hwnd, hk.id+1 );
if ( hk.key2 && hk.key2 != hk.key )
UnregisterHotKey( hwnd, hk.id+1 );
}
}
(static_cast<QHotkeyApplication*>(qApp))->unregisterWrapper(this);
@ -436,7 +483,7 @@ bool QHotkeyApplication::nativeEventFilter( const QByteArray & /*eventType*/, vo
{
MSG * msg = reinterpret_cast< MSG * >( message );
if ( msg->message == WM_HOTKEY )
if ( msg->message == WM_HOTKEY || msg->message == GD_HOTKEY_MESSAGE )
{
for (int i = 0; i < hotkeyWrappers.size(); i++)
{
@ -458,7 +505,7 @@ bool QHotkeyApplication::nativeEventFilter( const QByteArray & /*eventType*/, vo
bool QHotkeyApplication::winEventFilter ( MSG * message, long * result )
{
if (message->message == WM_HOTKEY)
if (message->message == WM_HOTKEY || message->message == GD_HOTKEY_MESSAGE)
{
for (int i = 0; i < hotkeyWrappers.size(); i++)
{

View file

@ -27,6 +27,10 @@
#include "qtsingleapplication.h"
#include "qt4x5.hh"
#ifdef Q_OS_WIN32
#include "hotkeys.h"
#endif
//////////////////////////////////////////////////////////////////////////
struct HotkeyStruct
@ -67,6 +71,9 @@ public:
/// Unregisters everything
void unregister();
bool handleViaDLL()
{ return dllHandler.hDLLHandle != 0; }
signals:
void hotkeyActivated( int );
@ -95,6 +102,17 @@ private:
virtual bool winEvent ( MSG * message, long * result );
HWND hwnd;
struct DLL_HANDLER
{
HMODULE hDLLHandle;
setHookProc setHook;
removeHookProc removeHook;
setHotkeysProc setHotkeys;
clearHotkeysProc clearHotkeys;
};
DLL_HANDLER dllHandler;
#elif defined(Q_OS_MAC)
public:

View file

@ -2827,6 +2827,24 @@ void MainWindow::toggleMainWindow( bool onlyShow )
if ( !isVisible() )
{
show();
#ifdef Q_OS_WIN32
if( hotkeyWrapper->handleViaDLL() )
{
// Some dances with tambourine
HWND wId = (HWND) winId();
DWORD pId = GetWindowThreadProcessId( wId, NULL );
DWORD fpId = GetWindowThreadProcessId( GetForegroundWindow(), NULL );
//Attach Thread to get the Input - i am now allowed to set the Foreground window!
AttachThreadInput( fpId, pId, true );
SetActiveWindow( wId );
SetForegroundWindow( wId );
SetFocus( wId );
AttachThreadInput( fpId, pId, false );
}
#endif
qApp->setActiveWindow( this );
activateWindow();
raise();
@ -2846,8 +2864,25 @@ void MainWindow::toggleMainWindow( bool onlyShow )
else
if ( !isActiveWindow() )
{
activateWindow();
qApp->setActiveWindow( this );
#ifdef Q_OS_WIN32
if( hotkeyWrapper->handleViaDLL() )
{
// Some dances with tambourine
HWND wId = (HWND) winId();
DWORD pId = GetWindowThreadProcessId( wId, NULL );
DWORD fpId = GetWindowThreadProcessId( GetForegroundWindow(), NULL );
//Attach Thread to get the Input - i am now allowed to set the Foreground window!
AttachThreadInput( fpId, pId, true );
SetActiveWindow( wId );
SetForegroundWindow( wId );
SetFocus( wId );
AttachThreadInput( fpId, pId, false );
}
#endif
raise();
activateWindow();
shown = true;
}
else
@ -2944,7 +2979,8 @@ void MainWindow::installHotKeys()
}
connect( hotkeyWrapper.get(), SIGNAL( hotkeyActivated( int ) ),
this, SLOT( hotKeyActivated( int ) ) );
this, SLOT( hotKeyActivated( int ) ),
hotkeyWrapper->handleViaDLL() ? Qt::QueuedConnection : Qt::AutoConnection );
}
}