diff --git a/src/hotkeywrapper.cc b/src/hotkeywrapper.cc index 67a2097a..5f09b02d 100644 --- a/src/hotkeywrapper.cc +++ b/src/hotkeywrapper.cc @@ -250,329 +250,4 @@ bool HotkeyWrapper::checkState( quint32 vk, quint32 mod ) state2 = false; return false; } - - - #ifndef Q_OS_WIN - - ////////////////////////////////////////////////////////////////////////// - - #include - -void HotkeyWrapper::init() -{ - keyToUngrab = grabbedKeys.end(); - - #if QT_VERSION < 0x060000 - Display * displayID = QX11Info::display(); - #else - QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); - 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 ) ) - qDebug( "Failed to enable record context" ); -} - - -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( QKeySequence const & seq, int handle ) -{ - Config::HotKey hk( seq ); - return setGlobalKey( hk.key1, hk.key2, hk.modifiers, handle ); -} - -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 { - -using X11ErrorHandler = int ( * )( 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< QNativeInterface::QX11Application >(); - 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< QNativeInterface::QX11Application >(); - 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< QNativeInterface::QX11Application >(); - Display * displayID = x11AppInfo->display(); - #endif - X11GrabUngrabErrorHandler errorHandler; - XGrabKey( displayID, keyCode, modifiers, DefaultRootWindow( displayID ), True, GrabModeAsync, GrabModeAsync ); - - if ( errorHandler.isError() ) { - qWarning( "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< QNativeInterface::QX11Application >(); - Display * displayID = x11AppInfo->display(); - #endif - X11GrabUngrabErrorHandler errorHandler; - XUngrabKey( displayID, i->first, i->second, XDefaultRootWindow( displayID ) ); - - grabbedKeys.erase( i ); - - if ( errorHandler.isError() ) { - qWarning( "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< QNativeInterface::QX11Application >(); - 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< QNativeInterface::QX11Application >(); - 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< QHotkeyApplication * >( qApp ) )->unregisterWrapper( this ); -} - - #endif #endif diff --git a/src/hotkeywrapper.hh b/src/hotkeywrapper.hh index 344bb29e..5897831d 100644 --- a/src/hotkeywrapper.hh +++ b/src/hotkeywrapper.hh @@ -1,15 +1,14 @@ #pragma once /// @file -/// Handling global hotkeys and some tricks -/// Part of this header are implmented in -/// + `winhotkeywrapper.cc` -/// + `machotkeywrapper.hh` -/// +/// Handling global hotkeys and some trick +/// Part of this header is implemented in +/// + `winhotkeywrapper` +/// + `machotkeywrapper` +/// + `x11hotkeywrapper` #include #include - #include "config.hh" #include "ex.hh" #include "qtsingleapplication.h" @@ -124,7 +123,7 @@ private: /// Called by recordEventCallback() void handleRecordEvent( XRecordInterceptData * ); - void run(); // QThread + void run() override; // QThread // We do one-time init of those, translating keysyms to keycodes KeyCode lShiftCode, rShiftCode, lCtrlCode, rCtrlCode, lAltCode, rAltCode, cCode, insertCode, kpInsertCode, lMetaCode, diff --git a/src/unix/x11hotkeywrapper.cc b/src/unix/x11hotkeywrapper.cc new file mode 100644 index 00000000..2c2f8931 --- /dev/null +++ b/src/unix/x11hotkeywrapper.cc @@ -0,0 +1,309 @@ +#ifdef HAVE_X11 + #include "hotkeywrapper.hh" + #include + +// +/// Previously impletended with XGrabKey +/// Then reimplemented with X Record Extension Library +/// + +void HotkeyWrapper::init() +{ + keyToUngrab = grabbedKeys.end(); + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + Display * displayID = x11AppInfo->display(); + + // 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 ) ) + qDebug( "Failed to enable record context" ); +} + + +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( QKeySequence const & seq, int handle ) +{ + Config::HotKey hk( seq ); + return setGlobalKey( hk.key1, hk.key2, hk.modifiers, handle ); +} + +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 { + +using X11ErrorHandler = int ( * )( 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() + { + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + + Display * displayID = x11AppInfo->display(); + + XFlush( displayID ); + (void)XSetErrorHandler( previousErrorHandler_ ); + } + + bool isError() const + { + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + + Display * displayID = x11AppInfo->display(); + + 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 ) { + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + + Display * displayID = x11AppInfo->display(); + + X11GrabUngrabErrorHandler errorHandler; + XGrabKey( displayID, keyCode, modifiers, DefaultRootWindow( displayID ), True, GrabModeAsync, GrabModeAsync ); + + if ( errorHandler.isError() ) { + qWarning( "Possible hotkeys conflict. Check your hotkeys options." ); + ungrabKey( result.first ); + } + } + + return result.first; +} + +void HotkeyWrapper::ungrabKey( GrabbedKeys::iterator i ) +{ + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + + Display * displayID = x11AppInfo->display(); + X11GrabUngrabErrorHandler errorHandler; + XUngrabKey( displayID, i->first, i->second, XDefaultRootWindow( displayID ) ); + + grabbedKeys.erase( i ); + + if ( errorHandler.isError() ) { + qWarning( "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; + } + + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + + Display * display = x11AppInfo->display(); + return XKeysymToKeycode( display, XStringToKeysym( keySymName.toLatin1().data() ) ); +} + +void HotkeyWrapper::unregister() +{ + QNativeInterface::QX11Application * x11AppInfo = qApp->nativeInterface< QNativeInterface::QX11Application >(); + Display * display = x11AppInfo->display(); + + XRecordDisableContext( display, recordContext ); + XSync( display, False ); + + wait(); + + XRecordFreeContext( display, recordContext ); + XFree( recordRange ); + XCloseDisplay( dataDisplay ); + + while ( grabbedKeys.size() ) + ungrabKey( grabbedKeys.begin() ); + + ( static_cast< QHotkeyApplication * >( qApp ) )->unregisterWrapper( this ); +} + +#endif