Compare commits

..

2 commits

Author SHA1 Message Date
xiaoyifang 2181a73924
Merge 15b918eb6a into 5b70a7e081 2024-11-22 09:23:20 +08:00
shenleban tongying 5b70a7e081
refactor: move X11 global hotkey out of hotkeywrapper.cc
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-21 13:03:27 -05:00
3 changed files with 315 additions and 332 deletions

View file

@ -250,329 +250,4 @@ bool HotkeyWrapper::checkState( quint32 vk, quint32 mod )
state2 = false; state2 = false;
return false; return false;
} }
#ifndef Q_OS_WIN
//////////////////////////////////////////////////////////////////////////
#include <X11/keysym.h>
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 #endif

View file

@ -1,15 +1,14 @@
#pragma once #pragma once
/// @file /// @file
/// Handling global hotkeys and some tricks /// Handling global hotkeys and some trick
/// Part of this header are implmented in /// Part of this header is implemented in
/// + `winhotkeywrapper.cc` /// + `winhotkeywrapper`
/// + `machotkeywrapper.hh` /// + `machotkeywrapper`
/// /// + `x11hotkeywrapper`
#include <QGuiApplication> #include <QGuiApplication>
#include <QThread> #include <QThread>
#include "config.hh" #include "config.hh"
#include "ex.hh" #include "ex.hh"
#include "qtsingleapplication.h" #include "qtsingleapplication.h"
@ -124,7 +123,7 @@ private:
/// Called by recordEventCallback() /// Called by recordEventCallback()
void handleRecordEvent( XRecordInterceptData * ); void handleRecordEvent( XRecordInterceptData * );
void run(); // QThread void run() override; // QThread
// We do one-time init of those, translating keysyms to keycodes // We do one-time init of those, translating keysyms to keycodes
KeyCode lShiftCode, rShiftCode, lCtrlCode, rCtrlCode, lAltCode, rAltCode, cCode, insertCode, kpInsertCode, lMetaCode, KeyCode lShiftCode, rShiftCode, lCtrlCode, rCtrlCode, lAltCode, rAltCode, cCode, insertCode, kpInsertCode, lMetaCode,

View file

@ -0,0 +1,309 @@
#ifdef HAVE_X11
#include "hotkeywrapper.hh"
#include <X11/keysym.h>
//
/// 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