mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-23 20:14:05 +00:00
Compare commits
5 commits
98619384a7
...
2181a73924
Author | SHA1 | Date | |
---|---|---|---|
2181a73924 | |||
5b70a7e081 | |||
15b918eb6a | |||
27cbb7351b | |||
c787a08d2f |
|
@ -149,6 +149,7 @@ Preferences::Preferences():
|
||||||
doubleClickTranslates( true ),
|
doubleClickTranslates( true ),
|
||||||
selectWordBySingleClick( false ),
|
selectWordBySingleClick( false ),
|
||||||
autoScrollToTargetArticle( true ),
|
autoScrollToTargetArticle( true ),
|
||||||
|
targetArticleAtFirst( false ),
|
||||||
escKeyHidesMainWindow( false ),
|
escKeyHidesMainWindow( false ),
|
||||||
alwaysOnTop( false ),
|
alwaysOnTop( false ),
|
||||||
searchInDock( false ),
|
searchInDock( false ),
|
||||||
|
@ -877,6 +878,11 @@ Class load()
|
||||||
( preferences.namedItem( "autoScrollToTargetArticle" ).toElement().text() == "1" );
|
( 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() ) {
|
if ( !preferences.namedItem( "escKeyHidesMainWindow" ).isNull() ) {
|
||||||
c.preferences.escKeyHidesMainWindow =
|
c.preferences.escKeyHidesMainWindow =
|
||||||
( preferences.namedItem( "escKeyHidesMainWindow" ).toElement().text() == "1" );
|
( preferences.namedItem( "escKeyHidesMainWindow" ).toElement().text() == "1" );
|
||||||
|
@ -1814,6 +1820,10 @@ void save( Class const & c )
|
||||||
opt.appendChild( dd.createTextNode( c.preferences.autoScrollToTargetArticle ? "1" : "0" ) );
|
opt.appendChild( dd.createTextNode( c.preferences.autoScrollToTargetArticle ? "1" : "0" ) );
|
||||||
preferences.appendChild( opt );
|
preferences.appendChild( opt );
|
||||||
|
|
||||||
|
opt = dd.createElement( "targetArticleAtFirst" );
|
||||||
|
opt.appendChild( dd.createTextNode( c.preferences.targetArticleAtFirst ? "1" : "0" ) );
|
||||||
|
preferences.appendChild( opt );
|
||||||
|
|
||||||
opt = dd.createElement( "escKeyHidesMainWindow" );
|
opt = dd.createElement( "escKeyHidesMainWindow" );
|
||||||
opt.appendChild( dd.createTextNode( c.preferences.escKeyHidesMainWindow ? "1" : "0" ) );
|
opt.appendChild( dd.createTextNode( c.preferences.escKeyHidesMainWindow ? "1" : "0" ) );
|
||||||
preferences.appendChild( opt );
|
preferences.appendChild( opt );
|
||||||
|
|
|
@ -297,6 +297,7 @@ struct Preferences
|
||||||
bool doubleClickTranslates;
|
bool doubleClickTranslates;
|
||||||
bool selectWordBySingleClick;
|
bool selectWordBySingleClick;
|
||||||
bool autoScrollToTargetArticle;
|
bool autoScrollToTargetArticle;
|
||||||
|
bool targetArticleAtFirst;
|
||||||
bool escKeyHidesMainWindow;
|
bool escKeyHidesMainWindow;
|
||||||
bool alwaysOnTop;
|
bool alwaysOnTop;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -185,6 +185,7 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
|
||||||
ui.doubleClickTranslates->setChecked( p.doubleClickTranslates );
|
ui.doubleClickTranslates->setChecked( p.doubleClickTranslates );
|
||||||
ui.selectBySingleClick->setChecked( p.selectWordBySingleClick );
|
ui.selectBySingleClick->setChecked( p.selectWordBySingleClick );
|
||||||
ui.autoScrollToTargetArticle->setChecked( p.autoScrollToTargetArticle );
|
ui.autoScrollToTargetArticle->setChecked( p.autoScrollToTargetArticle );
|
||||||
|
ui.targetArticleAtFirst->setChecked( p.targetArticleAtFirst );
|
||||||
ui.escKeyHidesMainWindow->setChecked( p.escKeyHidesMainWindow );
|
ui.escKeyHidesMainWindow->setChecked( p.escKeyHidesMainWindow );
|
||||||
|
|
||||||
ui.darkMode->addItem( tr( "On" ), QVariant::fromValue( Config::Dark::On ) );
|
ui.darkMode->addItem( tr( "On" ), QVariant::fromValue( Config::Dark::On ) );
|
||||||
|
@ -441,6 +442,7 @@ Config::Preferences Preferences::getPreferences()
|
||||||
p.doubleClickTranslates = ui.doubleClickTranslates->isChecked();
|
p.doubleClickTranslates = ui.doubleClickTranslates->isChecked();
|
||||||
p.selectWordBySingleClick = ui.selectBySingleClick->isChecked();
|
p.selectWordBySingleClick = ui.selectBySingleClick->isChecked();
|
||||||
p.autoScrollToTargetArticle = ui.autoScrollToTargetArticle->isChecked();
|
p.autoScrollToTargetArticle = ui.autoScrollToTargetArticle->isChecked();
|
||||||
|
p.targetArticleAtFirst = ui.targetArticleAtFirst->isChecked();
|
||||||
p.escKeyHidesMainWindow = ui.escKeyHidesMainWindow->isChecked();
|
p.escKeyHidesMainWindow = ui.escKeyHidesMainWindow->isChecked();
|
||||||
|
|
||||||
p.darkMode = ui.darkMode->currentData().value< Config::Dark >();
|
p.darkMode = ui.darkMode->currentData().value< Config::Dark >();
|
||||||
|
|
|
@ -169,6 +169,16 @@ however, the article from the topmost dictionary is shown.</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="1" column="0">
|
||||||
<widget class="QGroupBox" name="enableTrayIcon">
|
<widget class="QGroupBox" name="enableTrayIcon">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
|
|
309
src/unix/x11hotkeywrapper.cc
Normal file
309
src/unix/x11hotkeywrapper.cc
Normal 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
|
Loading…
Reference in a new issue