Compare commits

...

5 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
autofix-ci[bot] 15b918eb6a
[autofix.ci] apply automated fixes 2024-11-08 01:47:29 +00:00
xiaoyifang 27cbb7351b opt: add option about 2024-11-06 13:35:22 +08:00
xiaoyifang c787a08d2f opt: add option about 2024-11-06 12:07:23 +08:00
7 changed files with 338 additions and 332 deletions

View file

@ -149,6 +149,7 @@ Preferences::Preferences():
doubleClickTranslates( true ),
selectWordBySingleClick( false ),
autoScrollToTargetArticle( true ),
targetArticleAtFirst( false ),
escKeyHidesMainWindow( false ),
alwaysOnTop( false ),
searchInDock( false ),
@ -877,6 +878,11 @@ Class load()
( preferences.namedItem( "autoScrollToTargetArticle" ).toElement().text() == "1" );
}
if ( !preferences.namedItem( "targetArticleAtFirst" ).isNull() ) {
c.preferences.targetArticleAtFirst =
( preferences.namedItem( "targetArticleAtFirst" ).toElement().text() == "1" );
}
if ( !preferences.namedItem( "escKeyHidesMainWindow" ).isNull() ) {
c.preferences.escKeyHidesMainWindow =
( preferences.namedItem( "escKeyHidesMainWindow" ).toElement().text() == "1" );
@ -1814,6 +1820,10 @@ void save( Class const & c )
opt.appendChild( dd.createTextNode( c.preferences.autoScrollToTargetArticle ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "targetArticleAtFirst" );
opt.appendChild( dd.createTextNode( c.preferences.targetArticleAtFirst ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "escKeyHidesMainWindow" );
opt.appendChild( dd.createTextNode( c.preferences.escKeyHidesMainWindow ? "1" : "0" ) );
preferences.appendChild( opt );

View file

@ -297,6 +297,7 @@ struct Preferences
bool doubleClickTranslates;
bool selectWordBySingleClick;
bool autoScrollToTargetArticle;
bool targetArticleAtFirst;
bool escKeyHidesMainWindow;
bool alwaysOnTop;

View file

@ -250,329 +250,4 @@ bool HotkeyWrapper::checkState( quint32 vk, quint32 mod )
state2 = 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

View file

@ -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 <QGuiApplication>
#include <QThread>
#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,

View file

@ -185,6 +185,7 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
ui.doubleClickTranslates->setChecked( p.doubleClickTranslates );
ui.selectBySingleClick->setChecked( p.selectWordBySingleClick );
ui.autoScrollToTargetArticle->setChecked( p.autoScrollToTargetArticle );
ui.targetArticleAtFirst->setChecked( p.targetArticleAtFirst );
ui.escKeyHidesMainWindow->setChecked( p.escKeyHidesMainWindow );
ui.darkMode->addItem( tr( "On" ), QVariant::fromValue( Config::Dark::On ) );
@ -441,6 +442,7 @@ Config::Preferences Preferences::getPreferences()
p.doubleClickTranslates = ui.doubleClickTranslates->isChecked();
p.selectWordBySingleClick = ui.selectBySingleClick->isChecked();
p.autoScrollToTargetArticle = ui.autoScrollToTargetArticle->isChecked();
p.targetArticleAtFirst = ui.targetArticleAtFirst->isChecked();
p.escKeyHidesMainWindow = ui.escKeyHidesMainWindow->isChecked();
p.darkMode = ui.darkMode->currentData().value< Config::Dark >();

View file

@ -169,6 +169,16 @@ however, the article from the topmost dictionary is shown.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="targetArticleAtFirst">
<property name="text">
<string>Place the target article at the first place.</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="enableTrayIcon">
<property name="toolTip">

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