Compare commits

...

8 commits

Author SHA1 Message Date
xiaoyifang 2fc85c98ff
Merge 15b918eb6a into 453948155a 2024-11-12 19:00:48 -05:00
shenleban tongying 453948155a
clean: simplify macOS hotkey mapping code
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 12:33:28 +00:00
shenleban tongying 8fc71c9586
dev: .mm / obj-c files -> clang-format and simplify includes
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 10:19:59 +00:00
shenleban tongying 8fd5b37335 clean: remove unused DEF_EX
Some checks are pending
SonarCloud / Build and analyze (push) Waiting to run
2024-11-12 01:29:12 -05:00
shenleban tongying 4758f9e972 refactor: change DEF_EX macro's underlying implementation to template 2024-11-12 00:30:02 -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
15 changed files with 581 additions and 568 deletions

View file

@ -142,4 +142,7 @@ StatementMacros:
- QT_REQUIRE_VERSION
UseCRLF: false
UseTab: Never
---
Language: ObjC
BasedOnStyle: WebKit
...

View file

@ -17,7 +17,6 @@ Checks: >
portability-*,
readability-*,
-bugprone-easily-swappable-parameters,
-bugprone-reserved-identifier,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
@ -44,5 +43,11 @@ CheckOptions:
value: 1
- key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors
value: 1
- key: modernize-avoid-c-arrays.AllowStringArrays
value: 1
- key: cppcoreguidelines-avoid-c-arrays.AllowStringArrays
value: 1
- key: hicpp-avoid-c-arrays.AllowStringArrays
value: 1
...

View file

@ -4,22 +4,17 @@
#pragma once
#include <string>
#include <fmt/format.h>
// clang-format off
/// A way to declare an exception class fast
/// Do like this:
/// DEF_EX( exErrorInFoo, "An error in foo encountered", std::exception )
/// DEF_EX( exFooNotFound, "Foo was not found", exErrorInFoo )
#define DEF_EX( exName, exDescription, exParent ) \
class exName: public exParent \
{ \
public: \
virtual const char * what() const noexcept \
{ \
return ( exDescription ); \
} \
virtual ~exName() noexcept {} \
};
#define DEF_EX( exName, exDescription, exParent ) \
constexpr static char ExStr_## exName[] = exDescription; \
using exName = defineEx< exParent, ExStr_## exName >;
/// Same as DEF_EX, but takes a runtime string argument, which gets concatenated
/// with the description.
@ -29,20 +24,36 @@
/// throw exCantOpen( "example.txt" );
///
/// what() would return "can't open file example.txt"
///
#define DEF_EX_STR( exName, exDescription, exParent ) \
constexpr static char ExStr_## exName[] = exDescription; \
using exName = defineExStr< exParent, ExStr_## exName >;
#define DEF_EX_STR( exName, exDescription, exParent ) \
class exName: public exParent \
{ \
std::string value; \
\
public: \
explicit exName( std::string const & value_ ): \
value( std::string( exDescription ) + " " + value_ ) \
{ \
} \
virtual const char * what() const noexcept \
{ \
return value.c_str(); \
} \
virtual ~exName() noexcept {} \
};
// clang-format on
template< typename ParentEx, const char * description >
class defineEx: public ParentEx
{
public:
virtual const char * what() const noexcept
{
return description;
}
};
template< typename ParentEx, const char * description >
class defineExStr: public ParentEx
{
public:
explicit defineExStr( std::string const & message_ ):
message( fmt::format( "{} {}", description, message_ ) )
{
}
virtual const char * what() const noexcept
{
return message.c_str();
}
private:
std::string message;
};

View file

@ -201,6 +201,7 @@ Preferences::Preferences():
doubleClickTranslates( true ),
selectWordBySingleClick( false ),
autoScrollToTargetArticle( true ),
targetArticleAtFirst( false ),
escKeyHidesMainWindow( false ),
alwaysOnTop( false ),
searchInDock( false ),
@ -929,6 +930,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" );
@ -1866,6 +1872,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

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

View file

@ -46,10 +46,6 @@ using BtreeIndexing::IndexInfo;
namespace {
DEF_EX_STR( exNotAardFile, "Not an AARD file", Dictionary::Ex )
DEF_EX_STR( exWordIsTooLarge, "Enountered a word that is too large:", Dictionary::Ex )
DEF_EX_STR( exSuddenEndOfFile, "Sudden end of file", Dictionary::Ex )
#pragma pack( push, 1 )
/// AAR file header

View file

@ -169,10 +169,6 @@ void addEntryToIndex( string & word,
indexedWords.addWord( Utf8::decode( word ), articleOffset );
}
DEF_EX( exFailedToDecompressArticle, "Failed to decompress article's body", Dictionary::Ex )
DEF_EX( exChunkIndexOutOfRange, "Chunk index is out of range", Dictionary::Ex )
class BglDictionary: public BtreeIndexing::BtreeDictionary
{
QMutex idxMutex;

View file

@ -301,7 +301,6 @@ namespace {
////////////////// GLS Dictionary
using Dictionary::exCantReadFile;
DEF_EX( exUserAbort, "User abort", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
enum {

View file

@ -64,14 +64,11 @@ DEF_EX( exNotAnIfoFile, "Not an .ifo file", Dictionary::Ex )
DEF_EX_STR( exBadFieldInIfo, "Bad field in .ifo file encountered:", Dictionary::Ex )
DEF_EX_STR( exNoIdxFile, "No corresponding .idx file was found for", Dictionary::Ex )
DEF_EX_STR( exNoDictFile, "No corresponding .dict file was found for", Dictionary::Ex )
DEF_EX_STR( exNoSynFile, "No corresponding .syn file was found for", Dictionary::Ex )
DEF_EX( ex64BitsNotSupported, "64-bit indices are not presently supported, sorry", Dictionary::Ex )
DEF_EX( exDicttypeNotSupported, "Dictionaries with dicttypes are not supported, sorry", Dictionary::Ex )
using Dictionary::exCantReadFile;
DEF_EX_STR( exWordIsTooLarge, "Enountered a word that is too large:", Dictionary::Ex )
DEF_EX_STR( exSuddenEndOfFile, "Sudden end of file", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
DEF_EX_STR( exIncorrectOffset, "Incorrect offset encountered in file", Dictionary::Ex )

View file

@ -81,7 +81,6 @@ namespace {
using Dictionary::exCantReadFile;
DEF_EX_STR( exNotXdxfFile, "The file is not an XDXF file:", Dictionary::Ex )
DEF_EX( exCorruptedIndex, "The index file is corrupted", Dictionary::Ex )
DEF_EX_STR( exDictzipError, "DICTZIP error", Dictionary::Ex )
enum {

View file

@ -30,8 +30,7 @@
#endif
#ifdef Q_OS_MAC
#define __SECURITYHI__
#include <Carbon/Carbon.h>
#import <Carbon/Carbon.h>
#endif
//////////////////////////////////////////////////////////////////////////

View file

@ -2,11 +2,13 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "hotkeywrapper.hh"
#include <QTimer>
#include <QMessageBox>
#include <QPushButton>
#include <QObject>
#include <QPushButton>
#include <QTimer>
#include <memory>
#include <vector>
#import <Appkit/Appkit.h>
@ -23,120 +25,107 @@
/// https://github.com/sindresorhus/KeyboardShortcuts/blob/9369a045a72a5296150879781321aecd228171db/readme.md?plain=1#L207
///
namespace MacKeyMapping
{
namespace MacKeyMapping {
// Convert Qt key codes to Mac OS X native codes
struct ReverseMapEntry
{
UniChar character;
UInt16 keyCode;
struct ReverseMapEntry {
UniChar character;
UInt16 keyCode;
};
static struct ReverseMapEntry * mapping;
static int mapEntries = 0;
static std::vector<ReverseMapEntry> mapping;
/// References:
/// * https://github.com/libsdl-org/SDL/blob/fc12cc6dfd859a4e01376162a58f12208e539ac6/src/video/cocoa/SDL_cocoakeyboard.m#L345
/// * https://github.com/qt/qtbase/blob/922369844fcb75386237bca3eef59edd5093f58d/src/gui/platform/darwin/qapplekeymapper.mm#L449
///
/// Known possible flaws 1) UCKeyTranslate doesn't handle modifiers at all 2) Handling keyboard switching
void createMapping()
{
if( mapping == NULL )
{
TISInputSourceRef inputSourceRef = TISCopyInputSourceForLanguage( CFSTR( "en" ) );
if ( !inputSourceRef ) {
inputSourceRef = TISCopyCurrentKeyboardInputSource();
}
if ( !inputSourceRef ) {
return;
}
if (mapping.empty()) {
mapping.reserve(128);
CFDataRef dataRef = ( CFDataRef )TISGetInputSourceProperty( inputSourceRef,
kTISPropertyUnicodeKeyLayoutData );
// this method returns null under macos Japanese input method(and also Chinese), which causes cmd+C+C not to be registered as a hotkey
if( !dataRef )
{
// solve the null value under Japanese keyboard
inputSourceRef = TISCopyCurrentKeyboardLayoutInputSource();
dataRef = static_cast<CFDataRef>((TISGetInputSourceProperty(inputSourceRef, kTISPropertyUnicodeKeyLayoutData)));
if (!dataRef) {
return;
TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardLayoutInputSource();
if (!inputSourceRef) {
return;
}
}
const UCKeyboardLayout * keyboardLayoutPtr = ( const UCKeyboardLayout * )CFDataGetBytePtr( dataRef );
if( !keyboardLayoutPtr ) {
return;
}
CFDataRef uchrDataRef = (CFDataRef)TISGetInputSourceProperty(inputSourceRef, kTISPropertyUnicodeKeyLayoutData);
mapping = ( struct ReverseMapEntry * )calloc( 128 , sizeof(struct ReverseMapEntry) );
if( !mapping ) {
return;
}
const UCKeyboardLayout* UCKeyboardLayoutPtr;
mapEntries = 0;
for( int i = 0; i < 128; i++ )
{
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
if( UCKeyTranslate( keyboardLayoutPtr, i, kUCKeyActionDisplay, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
&mapping[ mapEntries ].character ) == noErr && theLength > 0 )
{
if( isprint( mapping[ mapEntries ].character ) )
{
mapping[ mapEntries++ ].keyCode = i;
if (uchrDataRef) {
UCKeyboardLayoutPtr = (const UCKeyboardLayout*)CFDataGetBytePtr(uchrDataRef);
}
}
if (!UCKeyboardLayoutPtr) {
return;
}
for (UInt16 i = 0; i < 128; i++) {
UInt32 theDeadKeyState = 0;
UniCharCount theLength = 0;
UniChar temp_char_buf;
if (UCKeyTranslate(UCKeyboardLayoutPtr, i, kUCKeyActionDown, 0, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
&temp_char_buf)
== noErr
&& theLength > 0) {
if (isprint(temp_char_buf)) {
mapping.emplace_back(ReverseMapEntry { temp_char_buf, i });
}
}
}
mapping.shrink_to_fit();
}
}
}
quint32 qtKeyToNativeKey( quint32 key )
quint32 qtKeyToNativeKey(UniChar key)
{
createMapping();
if( mapping == NULL ) {
createMapping();
if (mapping.empty()) {
return 0;
}
for (auto& m : mapping) {
if (m.character == key) {
return m.keyCode;
}
}
return 0;
}
for( int i = 0; i < mapEntries; i++ )
{
if( mapping[ i ].character == key ) {
return mapping[ i ].keyCode;
}
}
return 0;
}
} // namespace MacKeyMapping
static pascal OSStatus hotKeyHandler( EventHandlerCallRef /* nextHandler */, EventRef theEvent, void * userData )
static pascal OSStatus hotKeyHandler(EventHandlerCallRef /* nextHandler */, EventRef theEvent, void* userData)
{
EventHotKeyID hkID;
GetEventParameter( theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID );
static_cast< HotkeyWrapper * >( userData )->activated( hkID.id );
return noErr;
EventHotKeyID hkID;
GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID);
static_cast<HotkeyWrapper*>(userData)->activated(hkID.id);
return noErr;
}
HotkeyWrapper::HotkeyWrapper( QObject *parent )
HotkeyWrapper::HotkeyWrapper(QObject* parent)
{
(void) parent;
hotKeyFunction = NewEventHandlerUPP( hotKeyHandler );
EventTypeSpec type;
type.eventClass = kEventClassKeyboard;
type.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler( hotKeyFunction, 1, &type, this, &handlerRef );
keyC = nativeKey( 'c' );
(void)parent;
hotKeyFunction = NewEventHandlerUPP(hotKeyHandler);
EventTypeSpec type;
type.eventClass = kEventClassKeyboard;
type.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(hotKeyFunction, 1, &type, this, &handlerRef);
keyC = nativeKey('c');
}
HotkeyWrapper::~HotkeyWrapper()
{
unregister();
RemoveEventHandler( handlerRef );
unregister();
RemoveEventHandler(handlerRef);
}
void HotkeyWrapper::waitKey2()
{
state2 = false;
state2 = false;
}
void checkAndRequestAccessibilityPermission()
{
@ -159,196 +148,220 @@ void checkAndRequestAccessibilityPermission()
}
}
void HotkeyWrapper::activated( int hkId )
void HotkeyWrapper::activated(int hkId)
{
if ( state2 )
{ // wait for 2nd key
if (state2) { // wait for 2nd key
waitKey2(); // Cancel the 2nd-key wait stage
waitKey2(); // Cancel the 2nd-key wait stage
if ( hkId == state2waiter.id + 1 ||
( hkId == state2waiter.id && state2waiter.key == state2waiter.key2 ) )
{
emit hotkeyActivated( state2waiter.handle );
return;
if (hkId == state2waiter.id + 1 || (hkId == state2waiter.id && state2waiter.key == state2waiter.key2)) {
emit hotkeyActivated(state2waiter.handle);
return;
}
}
}
for ( int i = 0; i < hotkeys.count(); i++ )
{
HotkeyStruct &hs = hotkeys[ i ];
if( hkId == hs.id )
{
if( hs.key == keyC && hs.modifier == cmdKey )
{
checkAndRequestAccessibilityPermission();
for (int i = 0; i < hotkeys.count(); i++) {
HotkeyStruct& hs = hotkeys[i];
if (hkId == hs.id) {
if (hs.key == keyC && hs.modifier == cmdKey) {
checkAndRequestAccessibilityPermission();
// If that was a copy-to-clipboard shortcut, re-emit it back so it could
// reach its original destination so it could be acted upon.
UnregisterEventHotKey( hs.hkRef );
// If that was a copy-to-clipboard shortcut, re-emit it back so it could
// reach its original destination so it could be acted upon.
UnregisterEventHotKey(hs.hkRef);
sendCmdC();
sendCmdC();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = hs.id;
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = hs.id;
RegisterEventHotKey( hs.key, hs.modifier, hotKeyID, GetApplicationEventTarget(), 0, &hs.hkRef );
}
RegisterEventHotKey(hs.key, hs.modifier, hotKeyID, GetApplicationEventTarget(), 0, &hs.hkRef);
}
if ( hs.key2 == 0 ) {
emit hotkeyActivated( hs.handle );
return;
}
if (hs.key2 == 0) {
emit hotkeyActivated(hs.handle);
return;
}
state2 = true;
state2waiter = hs;
QTimer::singleShot( 500, this, SLOT( waitKey2() ) );
return;
state2 = true;
state2waiter = hs;
QTimer::singleShot(500, this, SLOT(waitKey2()));
return;
}
}
}
state2 = false;
return;
state2 = false;
return;
}
void HotkeyWrapper::unregister()
{
for ( int i = 0; i < hotkeys.count(); i++ )
{
HotkeyStruct const & hk = hotkeys.at( i );
for (int i = 0; i < hotkeys.count(); i++) {
HotkeyStruct const& hk = hotkeys.at(i);
UnregisterEventHotKey( hk.hkRef );
UnregisterEventHotKey(hk.hkRef);
if ( hk.key2 && hk.key2 != hk.key ) {
UnregisterEventHotKey( hk.hkRef2 );
}
}
if (hk.key2 && hk.key2 != hk.key) {
UnregisterEventHotKey(hk.hkRef2);
}
}
(static_cast< QHotkeyApplication * >( qApp ))->unregisterWrapper( this );
(static_cast<QHotkeyApplication*>(qApp))->unregisterWrapper(this);
}
bool HotkeyWrapper::setGlobalKey( QKeySequence const & seq, int handle )
bool HotkeyWrapper::setGlobalKey(QKeySequence const& seq, int handle)
{
Config::HotKey hk(seq);
return setGlobalKey(hk.key1,hk.key2,hk.modifiers,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 )
bool HotkeyWrapper::setGlobalKey(int key, int key2, Qt::KeyboardModifiers modifier, int handle)
{
if ( !key ) {
return false; // We don't monitor empty combinations
if (!key) {
return false; // We don't monitor empty combinations
}
quint32 vk = nativeKey(key);
if (vk == 0) {
return false;
}
quint32 vk2 = key2 ? nativeKey(key2) : 0;
static int nextId = 1;
if (nextId > 0xBFFF - 1) {
nextId = 1;
}
quint32 mod = 0;
if (modifier & Qt::CTRL) {
mod |= cmdKey;
}
if (modifier & Qt::ALT) {
mod |= optionKey;
}
if (modifier & Qt::SHIFT) {
mod |= shiftKey;
}
if (modifier & Qt::META) {
mod |= controlKey;
}
hotkeys.append(HotkeyStruct(vk, vk2, mod, handle, nextId));
HotkeyStruct& hk = hotkeys.last();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = nextId;
OSStatus ret = RegisterEventHotKey(vk, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef);
if (ret != 0) {
return false;
}
if (vk2 && vk2 != vk) {
hotKeyID.id = nextId + 1;
ret = RegisterEventHotKey(vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2);
}
nextId += 2;
return ret == 0;
}
quint32 vk = nativeKey( key );
if( vk == 0 ) {
return false;
}
quint32 vk2 = key2 ? nativeKey( key2 ) : 0;
static int nextId = 1;
if( nextId > 0xBFFF - 1 ) {
nextId = 1;
}
quint32 mod = 0;
if( modifier & Qt::CTRL ) {
mod |= cmdKey;
}
if( modifier & Qt::ALT ) {
mod |= optionKey;
}
if( modifier & Qt::SHIFT ) {
mod |= shiftKey;
}
if( modifier & Qt::META ) {
mod |= controlKey;
}
hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, nextId ) );
HotkeyStruct &hk = hotkeys.last();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = nextId;
OSStatus ret = RegisterEventHotKey( vk, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef );
if ( ret != 0 ) {
return false;
}
if ( vk2 && vk2 != vk )
{
hotKeyID.id = nextId + 1;
ret = RegisterEventHotKey( vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2 );
}
nextId += 2;
return ret == 0;
}
quint32 HotkeyWrapper::nativeKey( int key )
quint32 HotkeyWrapper::nativeKey(int key)
{
switch( key ) {
case Qt::Key_Escape: return 0x35;
case Qt::Key_Tab: return 0x30;
case Qt::Key_Backspace: return 0x33;
case Qt::Key_Return: return 0x24;
case Qt::Key_Enter: return 0x4c;
case Qt::Key_Delete: return 0x75;
case Qt::Key_Clear: return 0x47;
case Qt::Key_Home: return 0x73;
case Qt::Key_End: return 0x77;
case Qt::Key_Left: return 0x7b;
case Qt::Key_Up: return 0x7e;
case Qt::Key_Right: return 0x7c;
case Qt::Key_Down: return 0x7d;
case Qt::Key_PageUp: return 0x74;
case Qt::Key_PageDown: return 0x79;
case Qt::Key_CapsLock: return 0x57;
case Qt::Key_F1: return 0x7a;
case Qt::Key_F2: return 0x78;
case Qt::Key_F3: return 0x63;
case Qt::Key_F4: return 0x76;
case Qt::Key_F5: return 0x60;
case Qt::Key_F6: return 0x61;
case Qt::Key_F7: return 0x62;
case Qt::Key_F8: return 0x64;
case Qt::Key_F9: return 0x65;
case Qt::Key_F10: return 0x6d;
case Qt::Key_F11: return 0x67;
case Qt::Key_F12: return 0x6f;
case Qt::Key_F13: return 0x69;
case Qt::Key_F14: return 0x6b;
case Qt::Key_F15: return 0x71;
case Qt::Key_Help: return 0x72;
switch (key) {
case Qt::Key_Escape:
return 0x35;
case Qt::Key_Tab:
return 0x30;
case Qt::Key_Backspace:
return 0x33;
case Qt::Key_Return:
return 0x24;
case Qt::Key_Enter:
return 0x4c;
case Qt::Key_Delete:
return 0x75;
case Qt::Key_Clear:
return 0x47;
case Qt::Key_Home:
return 0x73;
case Qt::Key_End:
return 0x77;
case Qt::Key_Left:
return 0x7b;
case Qt::Key_Up:
return 0x7e;
case Qt::Key_Right:
return 0x7c;
case Qt::Key_Down:
return 0x7d;
case Qt::Key_PageUp:
return 0x74;
case Qt::Key_PageDown:
return 0x79;
case Qt::Key_CapsLock:
return 0x57;
case Qt::Key_F1:
return 0x7a;
case Qt::Key_F2:
return 0x78;
case Qt::Key_F3:
return 0x63;
case Qt::Key_F4:
return 0x76;
case Qt::Key_F5:
return 0x60;
case Qt::Key_F6:
return 0x61;
case Qt::Key_F7:
return 0x62;
case Qt::Key_F8:
return 0x64;
case Qt::Key_F9:
return 0x65;
case Qt::Key_F10:
return 0x6d;
case Qt::Key_F11:
return 0x67;
case Qt::Key_F12:
return 0x6f;
case Qt::Key_F13:
return 0x69;
case Qt::Key_F14:
return 0x6b;
case Qt::Key_F15:
return 0x71;
case Qt::Key_Help:
return 0x72;
default:;
}
return MacKeyMapping::qtKeyToNativeKey( QChar( key ).toLower().toLatin1() );
}
return MacKeyMapping::qtKeyToNativeKey(QChar(key).toLower().unicode());
}
void HotkeyWrapper::sendCmdC()
{
CGEventFlags flags = kCGEventFlagMaskCommand;
CGEventRef ev;
CGEventSourceRef source = CGEventSourceCreate( kCGEventSourceStateCombinedSessionState );
CGEventFlags flags = kCGEventFlagMaskCommand;
CGEventRef ev;
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
//press down
ev = CGEventCreateKeyboardEvent( source, keyC, true );
CGEventSetFlags( ev, CGEventFlags( flags | CGEventGetFlags( ev ) ) ); //combine flags
CGEventPost( kCGAnnotatedSessionEventTap, ev );
CFRelease( ev );
// press down
ev = CGEventCreateKeyboardEvent(source, keyC, true);
CGEventSetFlags(ev, CGEventFlags(flags | CGEventGetFlags(ev))); // combine flags
CGEventPost(kCGAnnotatedSessionEventTap, ev);
CFRelease(ev);
//press up
ev = CGEventCreateKeyboardEvent( source, keyC, false );
CGEventSetFlags( ev, CGEventFlags( flags | CGEventGetFlags( ev ) ) ); //combine flags
CGEventPost( kCGAnnotatedSessionEventTap, ev );
CFRelease( ev );
// press up
ev = CGEventCreateKeyboardEvent(source, keyC, false);
CGEventSetFlags(ev, CGEventFlags(flags | CGEventGetFlags(ev))); // combine flags
CGEventPost(kCGAnnotatedSessionEventTap, ev);
CFRelease(ev);
CFRelease( source );
CFRelease(source);
}
EventHandlerUPP HotkeyWrapper::hotKeyFunction = NULL;

View file

@ -1,372 +1,344 @@
#include "macmouseover.hh"
#include <AppKit/NSTouch.h>
#include <AppKit/NSEvent.h>
#include <AppKit/NSScreen.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/Foundation.h>
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_11_AND_LATER
#define kAXValueTypeCGPoint kAXValueCGPointType
#define kAXValueTypeCFRange kAXValueCFRangeType
#endif
#import <AppKit/AppKit.h>
const int mouseOverInterval = 300;
CGEventRef eventCallback( CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon )
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon)
{
(void) proxy;
if( type != kCGEventMouseMoved ) {
(void)proxy;
if (type != kCGEventMouseMoved) {
return event;
}
static_cast<MacMouseOver*>(refcon)->mouseMoved();
return event;
}
static_cast< MacMouseOver * >( refcon )->mouseMoved();
return event;
}
static CGPoint carbonScreenPointFromCocoaScreenPoint( NSPoint cocoaPoint )
static CGPoint carbonScreenPointFromCocoaScreenPoint(NSPoint cocoaPoint)
{
NSScreen *foundScreen = nil;
CGPoint thePoint;
NSScreen* foundScreen = nil;
CGPoint thePoint;
for (NSScreen *screen in [NSScreen screens]) {
if (NSPointInRect(cocoaPoint, [screen frame])) {
foundScreen = screen;
for (NSScreen* screen in [NSScreen screens]) {
if (NSPointInRect(cocoaPoint, [screen frame])) {
foundScreen = screen;
}
}
}
if (foundScreen) {
CGFloat screenHeight = [foundScreen frame].size.height;
thePoint = CGPointMake(cocoaPoint.x, screenHeight - cocoaPoint.y - 1);
}
else {
thePoint = CGPointMake(0.0, 0.0);
if (foundScreen) {
CGFloat screenHeight = [foundScreen frame].size.height;
thePoint = CGPointMake(cocoaPoint.x, screenHeight - cocoaPoint.y - 1);
} else {
thePoint = CGPointMake(0.0, 0.0);
}
return thePoint;
}
return thePoint;
}
MacMouseOver & MacMouseOver::instance()
MacMouseOver& MacMouseOver::instance()
{
static MacMouseOver m;
static MacMouseOver m;
return m;
return m;
}
MacMouseOver::MacMouseOver() :
pPref(NULL)
, tapRef( 0 )
, loop( 0 )
MacMouseOver::MacMouseOver()
: pPref(NULL)
, tapRef(0)
, loop(0)
{
mouseTimer.setSingleShot( true );
connect( &mouseTimer, SIGNAL( timeout() ), this, SLOT( timerShot() ) );
mouseTimer.setSingleShot(true);
connect(&mouseTimer, SIGNAL(timeout()), this, SLOT(timerShot()));
elementSystemWide = AXUIElementCreateSystemWide();
elementSystemWide = AXUIElementCreateSystemWide();
}
MacMouseOver::~MacMouseOver()
{
disableMouseOver();
disableMouseOver();
if( tapRef ) {
CFRelease( tapRef );
if (tapRef) {
CFRelease(tapRef);
}
if (loop) {
CFRelease(loop);
}
if (elementSystemWide) {
CFRelease(elementSystemWide);
}
}
if( loop ) {
CFRelease( loop );
}
if( elementSystemWide ) {
CFRelease( elementSystemWide );
}
}
QString MacMouseOver::CFStringRefToQString( CFStringRef str )
QString MacMouseOver::CFStringRefToQString(CFStringRef str)
{
int length = CFStringGetLength( str );
if( length == 0 ) {
return QString();
}
int length = CFStringGetLength(str);
if (length == 0) {
return QString();
}
UniChar *chars = new UniChar[ length ];
CFStringGetCharacters( str, CFRangeMake( 0, length ), chars );
UniChar* chars = new UniChar[length];
CFStringGetCharacters(str, CFRangeMake(0, length), chars);
QString result = QString::fromUtf16( (char16_t*)chars, length );
QString result = QString::fromUtf16((char16_t*)chars, length);
delete[] chars;
return result;
delete[] chars;
return result;
}
void MacMouseOver::mouseMoved()
{
mouseTimer.start( mouseOverInterval );
mouseTimer.start(mouseOverInterval);
}
void MacMouseOver::enableMouseOver()
{
mouseTimer.stop();
if( !isAXAPIEnabled() ) {
return;
}
if( !tapRef ) {
tapRef = CGEventTapCreate( kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly,
CGEventMaskBit( kCGEventMouseMoved ),
eventCallback, this );
}
if( !tapRef ) {
return;
}
if( !loop ) {
loop = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, tapRef, 0 );
}
if( loop ) {
CFRunLoopAddSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes );
}
mouseTimer.stop();
if (!isAXAPIEnabled()) {
return;
}
if (!tapRef) {
tapRef = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly,
CGEventMaskBit(kCGEventMouseMoved),
eventCallback, this);
}
if (!tapRef) {
return;
}
if (!loop) {
loop = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tapRef, 0);
}
if (loop) {
CFRunLoopAddSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
}
}
void MacMouseOver::disableMouseOver()
{
mouseTimer.stop();
if( loop ) {
CFRunLoopRemoveSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes );
}
mouseTimer.stop();
if (loop) {
CFRunLoopRemoveSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
}
}
void MacMouseOver::timerShot()
{
if( mouseMutex.tryLock( 0 ) ) {
mouseMutex.unlock();
} else {
return;
}
if( !pPref ) {
return;
}
if( !pPref->enableScanPopupModifiers || checkModifiersPressed( pPref->scanPopupModifiers ) ) {
handlePosition();
}
if (mouseMutex.tryLock(0)) {
mouseMutex.unlock();
} else {
return;
}
if (!pPref) {
return;
}
if (!pPref->enableScanPopupModifiers || checkModifiersPressed(pPref->scanPopupModifiers)) {
handlePosition();
}
}
void MacMouseOver::handlePosition()
{
QMutexLocker _( &mouseMutex );
QMutexLocker _(&mouseMutex);
QString strToTranslate;
QString strToTranslate;
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
CGPoint pt = carbonScreenPointFromCocoaScreenPoint( [NSEvent mouseLocation] );
[ pool drain ];
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
CGPoint pt = carbonScreenPointFromCocoaScreenPoint([NSEvent mouseLocation]);
[pool drain];
CFArrayRef names = 0;
CFArrayRef names = 0;
AXUIElementRef elem = 0;
AXError err = AXUIElementCopyElementAtPosition( elementSystemWide, pt.x, pt.y, &elem );
AXUIElementRef elem = 0;
AXError err = AXUIElementCopyElementAtPosition(elementSystemWide, pt.x, pt.y, &elem);
if( err != kAXErrorSuccess ) {
return;
}
if (err != kAXErrorSuccess) {
return;
}
for( ; ; )
{
CFTypeRef parameter = AXValueCreate( kAXValueTypeCGPoint, &pt );
CFTypeRef rangeValue;
err = AXUIElementCopyParameterizedAttributeNames( elem, &names );
if( err != kAXErrorSuccess ) {
break;
}
int numOfAttributes = CFArrayGetCount( names );
if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXRangeForPosition" ) ) )
{
// Standard interface
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXRangeForPositionParameterizedAttribute,
parameter, ( CFTypeRef * )&rangeValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
break;
}
CFStringRef stringValue;
CFRange decodedRange = CFRangeMake( 0, 0 );
bool b = AXValueGetValue( (AXValueRef)rangeValue, kAXValueTypeCFRange, &decodedRange );
CFRelease( rangeValue );
if( b )
{
int fromPos = decodedRange.location - 127;
if( fromPos < 0 ) {
fromPos = 0;
}
int wordPos = decodedRange.location - fromPos; // Cursor position in result string
CFRange range = CFRangeMake( fromPos, wordPos + 1 );
parameter = AXValueCreate( kAXValueTypeCFRange, &range );
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef *)&stringValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
break;
}
strToTranslate = CFStringRefToQString( stringValue );
CFRelease( stringValue );
// Read string further
for( int i = 1; i < 128; i++ )
{
range = CFRangeMake( decodedRange.location + i, 1 );
parameter = AXValueCreate( kAXValueTypeCFRange, &range );
err = AXUIElementCopyParameterizedAttributeValue( elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef *)&stringValue );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
for (;;) {
CFTypeRef parameter = AXValueCreate(kAXValueTypeCGPoint, &pt);
CFTypeRef rangeValue;
err = AXUIElementCopyParameterizedAttributeNames(elem, &names);
if (err != kAXErrorSuccess) {
break;
}
QString s = CFStringRefToQString( stringValue );
CFRelease( stringValue );
if( s[ 0 ].isLetterOrNumber() || s[ 0 ] == '-' ) {
strToTranslate += s;
} else {
break;
}
}
handleRetrievedString( strToTranslate, wordPos );
}
}
else if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXTextMarkerForPosition" ) ) )
{
// Safari interface
CFTypeRef marker, range;
CFStringRef str;
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXTextMarkerForPosition" ),
parameter, ( CFTypeRef * )&marker );
CFRelease( parameter );
if( err != kAXErrorSuccess ) {
int numOfAttributes = CFArrayGetCount(names);
if (CFArrayContainsValue(names, CFRangeMake(0, numOfAttributes), CFSTR("AXRangeForPosition"))) {
// Standard interface
err = AXUIElementCopyParameterizedAttributeValue(elem, kAXRangeForPositionParameterizedAttribute,
parameter, (CFTypeRef*)&rangeValue);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
CFStringRef stringValue;
CFRange decodedRange = CFRangeMake(0, 0);
bool b = AXValueGetValue((AXValueRef)rangeValue, kAXValueTypeCFRange, &decodedRange);
CFRelease(rangeValue);
if (b) {
int fromPos = decodedRange.location - 127;
if (fromPos < 0) {
fromPos = 0;
}
int wordPos = decodedRange.location - fromPos; // Cursor position in result string
CFRange range = CFRangeMake(fromPos, wordPos + 1);
parameter = AXValueCreate(kAXValueTypeCFRange, &range);
err = AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef*)&stringValue);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
strToTranslate = CFStringRefToQString(stringValue);
CFRelease(stringValue);
// Read string further
for (int i = 1; i < 128; i++) {
range = CFRangeMake(decodedRange.location + i, 1);
parameter = AXValueCreate(kAXValueTypeCFRange, &range);
err = AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute,
parameter, (CFTypeRef*)&stringValue);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
QString s = CFStringRefToQString(stringValue);
CFRelease(stringValue);
if (s[0].isLetterOrNumber() || s[0] == '-') {
strToTranslate += s;
} else {
break;
}
}
handleRetrievedString(strToTranslate, wordPos);
}
} else if (CFArrayContainsValue(names, CFRangeMake(0, numOfAttributes), CFSTR("AXTextMarkerForPosition"))) {
// Safari interface
CFTypeRef marker, range;
CFStringRef str;
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXTextMarkerForPosition"),
parameter, (CFTypeRef*)&marker);
CFRelease(parameter);
if (err != kAXErrorSuccess) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXLeftWordTextMarkerRangeForTextMarker"),
marker, (CFTypeRef*)&range);
CFRelease(marker);
if (err != kAXErrorSuccess) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue(elem, CFSTR("AXStringForTextMarkerRange"),
range, (CFTypeRef*)&str);
CFRelease(range);
if (err == kAXErrorSuccess) {
strToTranslate = CFStringRefToQString(str);
CFRelease(str);
handleRetrievedString(strToTranslate, 0);
}
}
break;
}
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXLeftWordTextMarkerRangeForTextMarker" ),
marker, ( CFTypeRef * )&range );
CFRelease( marker );
if( err != kAXErrorSuccess ) {
break;
}
err = AXUIElementCopyParameterizedAttributeValue( elem, CFSTR( "AXStringForTextMarkerRange" ),
range, ( CFTypeRef * )&str );
CFRelease( range );
if( err == kAXErrorSuccess )
{
strToTranslate = CFStringRefToQString( str );
CFRelease( str );
handleRetrievedString( strToTranslate, 0 );
}
}
break;
}
if( elem ) {
CFRelease( elem );
}
if( names ) {
CFRelease( names );
}
if (elem) {
CFRelease(elem);
}
if (names) {
CFRelease(names);
}
}
void MacMouseOver::handleRetrievedString( QString & wordSeq, int wordSeqPos )
void MacMouseOver::handleRetrievedString(QString& wordSeq, int wordSeqPos)
{
if( wordSeq.isEmpty() ) {
return;
}
// locate the word inside the sequence
QString word;
if ( wordSeq[ wordSeqPos ].isSpace() )
{
// Currently we ignore such cases
return;
}
else
if ( !wordSeq[ wordSeqPos ].isLetterOrNumber() )
{
// Special case: the cursor points to something which doesn't look like a
// middle of the word -- assume that it's something that joins two words
// together.
int begin = wordSeqPos;
for( ; begin; --begin ) {
if ( !wordSeq[ begin - 1 ].isLetterOrNumber() ) {
break;
}
}
int end = wordSeqPos;
while( ++end < wordSeq.size() ) {
if ( !wordSeq[ end ].isLetterOrNumber() ) {
break;
}
}
if ( end - begin == 1 )
{
// Well, turns out it was just a single non-letter char, discard it
return;
if (wordSeq.isEmpty()) {
return;
}
word = wordSeq.mid( begin, end - begin );
}
else
{
// Cursor points to a letter -- cut the word it points to
// locate the word inside the sequence
int begin = wordSeqPos;
QString word;
for( ; begin; --begin ) {
if ( !wordSeq[ begin - 1 ].isLetterOrNumber() ) {
break;
}
}
if (wordSeq[wordSeqPos].isSpace()) {
// Currently we ignore such cases
return;
} else if (!wordSeq[wordSeqPos].isLetterOrNumber()) {
// Special case: the cursor points to something which doesn't look like a
// middle of the word -- assume that it's something that joins two words
// together.
int end = wordSeqPos;
int begin = wordSeqPos;
while( ++end < wordSeq.size() )
{
if ( !wordSeq[ end ].isLetterOrNumber() ) {
break;
}
for (; begin; --begin) {
if (!wordSeq[begin - 1].isLetterOrNumber()) {
break;
}
}
int end = wordSeqPos;
while (++end < wordSeq.size()) {
if (!wordSeq[end].isLetterOrNumber()) {
break;
}
}
if (end - begin == 1) {
// Well, turns out it was just a single non-letter char, discard it
return;
}
word = wordSeq.mid(begin, end - begin);
} else {
// Cursor points to a letter -- cut the word it points to
int begin = wordSeqPos;
for (; begin; --begin) {
if (!wordSeq[begin - 1].isLetterOrNumber()) {
break;
}
}
int end = wordSeqPos;
while (++end < wordSeq.size()) {
if (!wordSeq[end].isLetterOrNumber()) {
break;
}
}
word = wordSeq.mid(begin, end - begin);
}
word = wordSeq.mid( begin, end - begin );
}
// See if we have an RTL char. Reverse the whole string if we do.
// See if we have an RTL char. Reverse the whole string if we do.
for( int x = 0; x < word.size(); ++x )
{
QChar::Direction d = word[ x ].direction();
for (int x = 0; x < word.size(); ++x) {
QChar::Direction d = word[x].direction();
if ( d == QChar::DirR || d == QChar::DirAL ||
d == QChar::DirRLE || d == QChar::DirRLO )
{
std::reverse( word.begin(), word.end() );
break;
if (d == QChar::DirR || d == QChar::DirAL || d == QChar::DirRLE || d == QChar::DirRLO) {
std::reverse(word.begin(), word.end());
break;
}
}
}
emit instance().hovered( word, false );
emit instance().hovered(word, false);
}
bool MacMouseOver::isAXAPIEnabled()
{
if( NSFoundationVersionNumber >= 1000 ) { // MacOS 10.9+
return AXIsProcessTrusted();
}
if (NSFoundationVersionNumber >= 1000) { // MacOS 10.9+
return AXIsProcessTrusted();
}
return AXAPIEnabled();
return AXAPIEnabled();
}

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">