#ifdef __WIN32 // Q_OS_WIN32 isn't available at this point #define _WIN32_WINNT 0x0430 #include #endif #include "hotkeywrapper.hh" #include "gddebug.hh" #include #include #ifdef Q_OS_WIN #include "mainwindow.hh" #endif ////////////////////////////////////////////////////////////////////////// QHotkeyApplication::QHotkeyApplication( int & argc, char ** argv ): QIntermediateApplication( argc, argv ) #ifdef Q_OS_WIN32 , mainWindow( 0 ) #endif { connect( this, &QGuiApplication::commitDataRequest, this, &QHotkeyApplication::hotkeyAppCommitData, Qt::DirectConnection ); connect( this, &QGuiApplication::saveStateRequest, this, &QHotkeyApplication::hotkeyAppSaveState, Qt::DirectConnection ); #if defined( Q_OS_WIN ) installNativeEventFilter( this ); #endif } QHotkeyApplication::QHotkeyApplication( QString const & id, int & argc, char ** argv ): QIntermediateApplication( id, argc, argv ) #ifdef Q_OS_WIN32 , mainWindow( 0 ) #endif { connect( this, &QGuiApplication::commitDataRequest, this, &QHotkeyApplication::hotkeyAppCommitData, Qt::DirectConnection ); connect( this, &QGuiApplication::saveStateRequest, this, &QHotkeyApplication::hotkeyAppSaveState, Qt::DirectConnection ); #if defined( Q_OS_WIN ) installNativeEventFilter( this ); #endif } void QHotkeyApplication::addDataCommiter( DataCommitter & d ) { dataCommitters.append( &d ); } void QHotkeyApplication::removeDataCommiter( DataCommitter & d ) { dataCommitters.removeAll( &d ); } void QHotkeyApplication::hotkeyAppCommitData( QSessionManager & mgr ) { for( int x = 0; x < dataCommitters.size(); ++x ) dataCommitters[ x ]->commitData( mgr ); } void QHotkeyApplication::hotkeyAppSaveState(QSessionManager & mgr ) { mgr.setRestartHint( QSessionManager::RestartNever ); } void QHotkeyApplication::registerWrapper(HotkeyWrapper *wrapper) { if (wrapper && !hotkeyWrappers.contains(wrapper)) hotkeyWrappers.append(wrapper); } void QHotkeyApplication::unregisterWrapper(HotkeyWrapper *wrapper) { if (wrapper && hotkeyWrappers.contains(wrapper)) hotkeyWrappers.removeAll(wrapper); } ////////////////////////////////////////////////////////////////////////// HotkeyStruct::HotkeyStruct( quint32 key_, quint32 key2_, quint32 modifier_, int handle_, int id_ ): key( key_ ), key2( key2_ ), modifier( modifier_ ), handle( handle_ ), id( id_ ) #ifdef Q_OS_MAC , hkRef( 0 ) , hkRef2( 0 ) #endif { } ////////////////////////////////////////////////////////////////////////// #if !defined(Q_OS_MAC) && !defined(Q_WS_QWS) HotkeyWrapper::HotkeyWrapper(QObject *parent) : QThread( parent ), state2(false) { #ifdef Q_OS_WIN hwnd=(HWND)((static_cast(parent))->winId()); gdWarning( "Handle global hotkeys via RegisterHotkey()" ); #else init(); #endif (static_cast(qApp))->registerWrapper(this); } HotkeyWrapper::~HotkeyWrapper() { unregister(); } void HotkeyWrapper::waitKey2() { state2 = false; #ifdef HAVE_X11 if ( keyToUngrab != grabbedKeys.end() ) { ungrabKey( keyToUngrab ); keyToUngrab = grabbedKeys.end(); } #endif } bool HotkeyWrapper::checkState(quint32 vk, quint32 mod) { if ( state2 ) { // wait for 2nd key waitKey2(); // Cancel the 2nd-key wait stage if (state2waiter.key2 == vk && state2waiter.modifier == mod) { emit hotkeyActivated( state2waiter.handle ); return true; } } for (int i = 0; i < hotkeys.count(); i++) { const HotkeyStruct &hs = hotkeys.at(i); if (hs.key == vk && hs.modifier == mod) { #ifdef Q_OS_WIN32 if( hs.key2 != 0 || ( mod == MOD_CONTROL && ( vk == VK_INSERT || vk == 'c' || vk == 'C' ) ) ) { // Pass-through first part of compound hotkey or clipdoard copy command INPUT i[ 10 ]; memset( i, 0, sizeof( i ) ); int nextKeyNom = 0; short emulateModKeys = 0; // If modifier keys aren't pressed it looks like emulation // We then also emulate full sequence if( ( mod & MOD_ALT ) != 0 && ( GetAsyncKeyState( VK_MENU ) & 0x8000 ) == 0 ) { emulateModKeys |= MOD_ALT; i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_MENU; nextKeyNom += 1; } if( ( mod & MOD_CONTROL ) != 0 && ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) == 0 ) { emulateModKeys |= MOD_CONTROL; i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_CONTROL; nextKeyNom += 1; } if( ( mod & MOD_SHIFT ) != 0 && ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) == 0 ) { emulateModKeys |= MOD_SHIFT; i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_SHIFT; nextKeyNom += 1; } if( ( mod & MOD_WIN ) != 0 && ( GetAsyncKeyState( VK_LWIN ) & 0x8000 ) == 0 && ( GetAsyncKeyState( VK_RWIN ) & 0x8000 ) == 0 ) { emulateModKeys |= MOD_WIN; i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_LWIN; nextKeyNom += 1; } i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = vk; nextKeyNom += 1; i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = vk; i[ nextKeyNom ].ki.dwFlags = KEYEVENTF_KEYUP; nextKeyNom += 1; if( emulateModKeys & MOD_WIN ) { i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_LWIN; i[ nextKeyNom ].ki.dwFlags = KEYEVENTF_KEYUP; nextKeyNom += 1; } if( emulateModKeys & MOD_SHIFT ) { i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_SHIFT; i[ nextKeyNom ].ki.dwFlags = KEYEVENTF_KEYUP; nextKeyNom += 1; } if( emulateModKeys & MOD_CONTROL ) { i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_CONTROL; i[ nextKeyNom ].ki.dwFlags = KEYEVENTF_KEYUP; nextKeyNom += 1; } if( emulateModKeys & MOD_ALT ) { i[ nextKeyNom ].type = INPUT_KEYBOARD; i[ nextKeyNom ].ki.wVk = VK_MENU; i[ nextKeyNom ].ki.dwFlags = KEYEVENTF_KEYUP; nextKeyNom += 1; } UnregisterHotKey( hwnd, hs.id ); SendInput( nextKeyNom, i, sizeof( *i ) ); RegisterHotKey(hwnd, hs.id, hs.modifier, hs.key); } #endif if (hs.key2 == 0) { emit hotkeyActivated( hs.handle ); return true; } state2 = true; state2waiter = hs; QTimer::singleShot( 500, this, &HotkeyWrapper::waitKey2 ); #ifdef HAVE_X11 // Grab the second key, unless it's grabbed already // Note that we only grab the clipboard key only if // the sequence didn't begin with it if ( ( isCopyToClipboardKey( hs.key, hs.modifier ) || !isCopyToClipboardKey( hs.key2, hs.modifier ) ) && !isKeyGrabbed( hs.key2, hs.modifier ) ) keyToUngrab = grabKey( hs.key2, hs.modifier ); #endif return true; } } state2 = false; return false; } ////////////////////////////////////////////////////////////////////////// #ifdef Q_OS_WIN void HotkeyWrapper::init() { QWidget *root = qApp->topLevelWidgets().value(0); hwnd = (HWND)root->winId(); } bool HotkeyWrapper::setGlobalKey( int key, int key2, Qt::KeyboardModifiers modifier, int handle ) { if ( !key ) return false; // We don't monitor empty combinations static int id = 0; if( id > 0xBFFF - 1 ) id = 0; quint32 mod = 0; if (modifier & Qt::CTRL) mod |= MOD_CONTROL; if (modifier & Qt::ALT) mod |= MOD_ALT; if (modifier & Qt::SHIFT) mod |= MOD_SHIFT; if (modifier & Qt::META) mod |= MOD_WIN; quint32 vk = nativeKey( key ); quint32 vk2 = key2 ? nativeKey( key2 ) : 0; hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, id ) ); if (!RegisterHotKey(hwnd, id++, mod, vk)) return false; if ( key2 && key2 != key ) return RegisterHotKey(hwnd, id++, mod, vk2); return true; } bool HotkeyWrapper::setGlobalKey( QKeySequence & seq, int handle ) { Config::HotKey hk(seq); return setGlobalKey(hk.key1,hk.key2,hk.modifiers,handle); } #if( QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) ) bool HotkeyWrapper::winEvent ( MSG * message, long * result ) #else bool HotkeyWrapper::winEvent ( MSG * message, qintptr * result ) #endif { (void) result; if (message->message == WM_HOTKEY) return checkState( (message->lParam >> 16), (message->lParam & 0xffff) ); return false; } quint32 HotkeyWrapper::nativeKey(int key) { if (key >= Qt::Key_0 && key <= Qt::Key_9) return VK_NUMPAD0 + ( key - Qt::Key_0 ); if (key >= Qt::Key_A && key <= Qt::Key_Z) return key; switch (key) { case Qt::Key_Space: return VK_SPACE; case Qt::Key_Asterisk: return VK_MULTIPLY; case Qt::Key_Plus: return VK_ADD; case Qt::Key_Comma: return VK_SEPARATOR; case Qt::Key_Minus: return VK_SUBTRACT; case Qt::Key_Slash: return VK_DIVIDE; case Qt::Key_Tab: case Qt::Key_Backtab: return VK_TAB; case Qt::Key_Backspace: return VK_BACK; case Qt::Key_Return: case Qt::Key_Escape: return VK_ESCAPE; case Qt::Key_Enter: return VK_RETURN; case Qt::Key_Insert: return VK_INSERT; case Qt::Key_Delete: return VK_DELETE; case Qt::Key_Pause: return VK_PAUSE; case Qt::Key_Print: return VK_PRINT; case Qt::Key_Clear: return VK_CLEAR; case Qt::Key_Home: return VK_HOME; case Qt::Key_End: return VK_END; case Qt::Key_Up: return VK_UP; case Qt::Key_Down: return VK_DOWN; case Qt::Key_Left: return VK_LEFT; case Qt::Key_Right: return VK_RIGHT; case Qt::Key_PageUp: return VK_PRIOR; case Qt::Key_PageDown: return VK_NEXT; case Qt::Key_F1: return VK_F1; case Qt::Key_F2: return VK_F2; case Qt::Key_F3: return VK_F3; case Qt::Key_F4: return VK_F4; case Qt::Key_F5: return VK_F5; case Qt::Key_F6: return VK_F6; case Qt::Key_F7: return VK_F7; case Qt::Key_F8: return VK_F8; case Qt::Key_F9: return VK_F9; case Qt::Key_F10: return VK_F10; case Qt::Key_F11: return VK_F11; case Qt::Key_F12: return VK_F12; case Qt::Key_F13: return VK_F13; case Qt::Key_F14: return VK_F14; case Qt::Key_F15: return VK_F15; case Qt::Key_F16: return VK_F16; case Qt::Key_F17: return VK_F17; case Qt::Key_F18: return VK_F18; case Qt::Key_F19: return VK_F19; case Qt::Key_F20: return VK_F20; case Qt::Key_F21: return VK_F21; case Qt::Key_F22: return VK_F22; case Qt::Key_F23: return VK_F23; case Qt::Key_F24: return VK_F24; case Qt::Key_Colon: case Qt::Key_Semicolon: return VK_OEM_1; case Qt::Key_Question: return VK_OEM_2; case Qt::Key_AsciiTilde: case Qt::Key_QuoteLeft: return VK_OEM_3; case Qt::Key_BraceLeft: case Qt::Key_BracketLeft: return VK_OEM_4; case Qt::Key_Bar: case Qt::Key_Backslash: return VK_OEM_5; case Qt::Key_BraceRight: case Qt::Key_BracketRight: return VK_OEM_6; case Qt::Key_QuoteDbl: case Qt::Key_Apostrophe: return VK_OEM_7; case Qt::Key_Less: return VK_OEM_COMMA; case Qt::Key_Greater: return VK_OEM_PERIOD; case Qt::Key_Equal: return VK_OEM_PLUS; case Qt::Key_ParenRight: return 0x30; case Qt::Key_Exclam: return 0x31; case Qt::Key_At: return 0x32; case Qt::Key_NumberSign: return 0x33; case Qt::Key_Dollar: return 0x34; case Qt::Key_Percent: return 0x35; case Qt::Key_AsciiCircum: return 0x36; case Qt::Key_Ampersand: return 0x37; case Qt::Key_copyright: return 0x38; case Qt::Key_ParenLeft: return 0x39; case Qt::Key_Underscore: return VK_OEM_MINUS; default:; } return key; } void HotkeyWrapper::unregister() { for( int i = 0; i < hotkeys.count(); i++ ) { HotkeyStruct const & hk = hotkeys.at( i ); UnregisterHotKey( hwnd, hk.id ); if( hk.key2 && hk.key2 != hk.key ) UnregisterHotKey( hwnd, hk.id + 1 ); } (static_cast(qApp))->unregisterWrapper(this); } #if( QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) ) bool QHotkeyApplication::nativeEventFilter( const QByteArray & /*eventType*/, void * message, long * result ) #else bool QHotkeyApplication::nativeEventFilter( const QByteArray & /*eventType*/, void * message, qintptr * result ) #endif { MSG * msg = reinterpret_cast< MSG * >( message ); if( msg->message == WM_HOTKEY ) { for (int i = 0; i < hotkeyWrappers.size(); i++) { if ( hotkeyWrappers.at(i)->winEvent( msg, result ) ) return true; } } return false; } ////////////////////////////////////////////////////////////////////////// #else ////////////////////////////////////////////////////////////////////////// #include void HotkeyWrapper::init() { keyToUngrab = grabbedKeys.end(); #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif // We use RECORD extension instead of XGrabKey. That's because XGrabKey // prevents other clients from getting their input if it's grabbed. Display * display = displayID; lShiftCode = XKeysymToKeycode( display, XK_Shift_L ); rShiftCode = XKeysymToKeycode( display, XK_Shift_R ); lCtrlCode = XKeysymToKeycode( display, XK_Control_L ); rCtrlCode = XKeysymToKeycode( display, XK_Control_R ); lAltCode = XKeysymToKeycode( display, XK_Alt_L ); rAltCode = XKeysymToKeycode( display, XK_Alt_R ); lMetaCode = XKeysymToKeycode( display, XK_Super_L ); rMetaCode = XKeysymToKeycode( display, XK_Super_R ); cCode = XKeysymToKeycode( display, XK_c ); insertCode = XKeysymToKeycode( display, XK_Insert ); kpInsertCode = XKeysymToKeycode( display, XK_KP_Insert ); currentModifiers = 0; // This one will be used to read the recorded content dataDisplay = XOpenDisplay( 0 ); if ( !dataDisplay ) throw exInit(); recordRange = XRecordAllocRange(); if ( !recordRange ) { XCloseDisplay( dataDisplay ); throw exInit(); } recordRange->device_events.first = KeyPress; recordRange->device_events.last = KeyRelease; recordClientSpec = XRecordAllClients; recordContext = XRecordCreateContext( display, 0, &recordClientSpec, 1, &recordRange, 1 ); if ( !recordContext ) { XFree( recordRange ); XCloseDisplay( dataDisplay ); throw exInit(); } // This is required to ensure context was indeed created XSync( display, False ); connect( this, &HotkeyWrapper::keyRecorded, this, &HotkeyWrapper::checkState, Qt::QueuedConnection ); start(); } void HotkeyWrapper::run() // Runs in a separate thread { if ( !XRecordEnableContext( dataDisplay, recordContext, recordEventCallback, (XPointer) this ) ) GD_DPRINTF( "Failed to enable record context\n" ); } void HotkeyWrapper::recordEventCallback( XPointer ptr, XRecordInterceptData * data ) { ((HotkeyWrapper * )ptr)->handleRecordEvent( data ); } void HotkeyWrapper::handleRecordEvent( XRecordInterceptData * data ) { if ( data->category == XRecordFromServer ) { xEvent * event = ( xEvent * ) data->data; if ( event->u.u.type == KeyPress ) { KeyCode key = event->u.u.detail; if ( key == lShiftCode || key == rShiftCode ) currentModifiers |= ShiftMask; else if ( key == lCtrlCode || key == rCtrlCode ) currentModifiers |= ControlMask; else if ( key == lAltCode || key == rAltCode ) currentModifiers |= Mod1Mask; else if ( key == lMetaCode || key == rMetaCode ) currentModifiers |= Mod4Mask; else { // Here we employ a kind of hack translating KP_Insert key // to just Insert. This allows reacting to both Insert keys. if ( key == kpInsertCode ) key = insertCode; emit keyRecorded( key, currentModifiers ); } } else if ( event->u.u.type == KeyRelease ) { KeyCode key = event->u.u.detail; if ( key == lShiftCode || key == rShiftCode ) currentModifiers &= ~ShiftMask; else if ( key == lCtrlCode || key == rCtrlCode ) currentModifiers &= ~ControlMask; else if ( key == lAltCode || key == rAltCode ) currentModifiers &= ~Mod1Mask; else if ( key == lMetaCode || key == rMetaCode ) currentModifiers &= ~Mod4Mask; } } XRecordFreeData( data ); } bool HotkeyWrapper::setGlobalKey( int key, int key2, Qt::KeyboardModifiers modifier, int handle ) { if ( !key ) return false; // We don't monitor empty combinations int vk = nativeKey( key ); int vk2 = key2 ? nativeKey( key2 ) : 0; quint32 mod = 0; if (modifier & Qt::ShiftModifier) mod |= ShiftMask; if (modifier & Qt::ControlModifier) mod |= ControlMask; if (modifier & Qt::AltModifier) mod |= Mod1Mask; if (modifier & Qt::MetaModifier) mod |= Mod4Mask; hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, 0 ) ); if ( !isCopyToClipboardKey( vk, mod ) ) grabKey( vk, mod ); // Make sure it doesn't get caught by other apps return true; } bool HotkeyWrapper::isCopyToClipboardKey( quint32 keyCode, quint32 modifiers ) const { return modifiers == ControlMask && ( keyCode == cCode || keyCode == insertCode || keyCode == kpInsertCode ); } bool HotkeyWrapper::isKeyGrabbed( quint32 keyCode, quint32 modifiers ) const { GrabbedKeys::const_iterator i = grabbedKeys.find( std::make_pair( keyCode, modifiers ) ); return i != grabbedKeys.end(); } namespace { typedef int (*X11ErrorHandler) ( Display * display, XErrorEvent * event ); class X11GrabUngrabErrorHandler { public: static bool error; static int grab_ungrab_error_handler( Display *, XErrorEvent * event ) { qDebug() << "grab_ungrab_error_handler is invoked"; switch ( event->error_code ) { case BadAccess: case BadValue: case BadWindow: if ( event->request_code == 33 /* X_GrabKey */ || event->request_code == 34 /* X_UngrabKey */) { error = true; } } return 0; } X11GrabUngrabErrorHandler() { error = false; previousErrorHandler_ = XSetErrorHandler( grab_ungrab_error_handler ); } ~X11GrabUngrabErrorHandler() { #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif XFlush( displayID ); (void) XSetErrorHandler( previousErrorHandler_ ); } bool isError() const { #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif XFlush( displayID ); return error; } private: X11ErrorHandler previousErrorHandler_; }; bool X11GrabUngrabErrorHandler::error = false; } // anonymous namespace HotkeyWrapper::GrabbedKeys::iterator HotkeyWrapper::grabKey( quint32 keyCode, quint32 modifiers ) { std::pair< GrabbedKeys::iterator, bool > result = grabbedKeys.insert( std::make_pair( keyCode, modifiers ) ); if ( result.second ) { #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif X11GrabUngrabErrorHandler errorHandler; XGrabKey( displayID, keyCode, modifiers, DefaultRootWindow(displayID), True, GrabModeAsync, GrabModeAsync ); if ( errorHandler.isError() ) { gdWarning( "Possible hotkeys conflict. Check your hotkeys options." ); ungrabKey( result.first ); } } return result.first; } void HotkeyWrapper::ungrabKey( GrabbedKeys::iterator i ) { #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif X11GrabUngrabErrorHandler errorHandler; XUngrabKey( displayID, i->first, i->second, XDefaultRootWindow(displayID) ); grabbedKeys.erase( i ); if ( errorHandler.isError() ) { gdWarning( "Cannot ungrab the hotkey" ); } } quint32 HotkeyWrapper::nativeKey(int key) { QString keySymName; switch( key ) { case Qt::Key_Insert: keySymName = "Insert"; break; default: keySymName = QKeySequence( key ).toString(); break; } #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif Display * display = displayID; return XKeysymToKeycode( display, XStringToKeysym( keySymName.toLatin1().data() ) ); } void HotkeyWrapper::unregister() { #if QT_VERSION < 0x060000 Display *displayID = QX11Info::display(); #else QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); Display *displayID = x11AppInfo->display(); #endif Display * display = displayID; XRecordDisableContext( display, recordContext ); XSync( display, False ); wait(); XRecordFreeContext( display, recordContext ); XFree( recordRange ); XCloseDisplay( dataDisplay ); while( grabbedKeys.size() ) ungrabKey( grabbedKeys.begin() ); (static_cast(qApp))->unregisterWrapper(this); } #endif #endif