mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-23 20:14:05 +00:00
Compare commits
3 commits
677745ba09
...
071e9389e1
Author | SHA1 | Date | |
---|---|---|---|
071e9389e1 | |||
453948155a | |||
8fc71c9586 |
|
@ -142,4 +142,7 @@ StatementMacros:
|
|||
- QT_REQUIRE_VERSION
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
---
|
||||
Language: ObjC
|
||||
BasedOnStyle: WebKit
|
||||
...
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#define __SECURITYHI__
|
||||
#include <Carbon/Carbon.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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,85 +25,72 @@
|
|||
/// 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
|
||||
{
|
||||
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) {
|
||||
TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if (!inputSourceRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFDataRef uchrDataRef = (CFDataRef)TISGetInputSourceProperty(inputSourceRef, kTISPropertyUnicodeKeyLayoutData);
|
||||
|
||||
const UCKeyboardLayout* UCKeyboardLayoutPtr;
|
||||
|
||||
if (uchrDataRef) {
|
||||
UCKeyboardLayoutPtr = (const UCKeyboardLayout*)CFDataGetBytePtr(uchrDataRef);
|
||||
}
|
||||
|
||||
const UCKeyboardLayout * keyboardLayoutPtr = ( const UCKeyboardLayout * )CFDataGetBytePtr( dataRef );
|
||||
if( !keyboardLayoutPtr ) {
|
||||
if (!UCKeyboardLayoutPtr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mapping = ( struct ReverseMapEntry * )calloc( 128 , sizeof(struct ReverseMapEntry) );
|
||||
if( !mapping ) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapEntries = 0;
|
||||
|
||||
for( int i = 0; i < 128; i++ )
|
||||
{
|
||||
for (UInt16 i = 0; i < 128; i++) {
|
||||
UInt32 theDeadKeyState = 0;
|
||||
UniCharCount theLength = 0;
|
||||
if( UCKeyTranslate( keyboardLayoutPtr, i, kUCKeyActionDisplay, 0, LMGetKbdType(),
|
||||
UniChar temp_char_buf;
|
||||
if (UCKeyTranslate(UCKeyboardLayoutPtr, i, kUCKeyActionDown, 0, LMGetKbdType(),
|
||||
kUCKeyTranslateNoDeadKeysBit, &theDeadKeyState, 1, &theLength,
|
||||
&mapping[ mapEntries ].character ) == noErr && theLength > 0 )
|
||||
{
|
||||
if( isprint( mapping[ mapEntries ].character ) )
|
||||
{
|
||||
mapping[ mapEntries++ ].keyCode = i;
|
||||
&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 ) {
|
||||
if (mapping.empty()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for( int i = 0; i < mapEntries; i++ )
|
||||
{
|
||||
if( mapping[ i ].character == key ) {
|
||||
return mapping[ i ].keyCode;
|
||||
}
|
||||
for (auto& m : mapping) {
|
||||
if (m.character == key) {
|
||||
return m.keyCode;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -109,29 +98,29 @@ quint32 qtKeyToNativeKey( quint32 key )
|
|||
|
||||
} // 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 );
|
||||
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 );
|
||||
(void)parent;
|
||||
hotKeyFunction = NewEventHandlerUPP(hotKeyHandler);
|
||||
EventTypeSpec type;
|
||||
type.eventClass = kEventClassKeyboard;
|
||||
type.eventKind = kEventHotKeyPressed;
|
||||
InstallApplicationEventHandler( hotKeyFunction, 1, &type, this, &handlerRef );
|
||||
keyC = nativeKey( 'c' );
|
||||
InstallApplicationEventHandler(hotKeyFunction, 1, &type, this, &handlerRef);
|
||||
keyC = nativeKey('c');
|
||||
}
|
||||
|
||||
HotkeyWrapper::~HotkeyWrapper()
|
||||
{
|
||||
unregister();
|
||||
RemoveEventHandler( handlerRef );
|
||||
RemoveEventHandler(handlerRef);
|
||||
}
|
||||
|
||||
void HotkeyWrapper::waitKey2()
|
||||
|
@ -159,33 +148,27 @@ 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
|
||||
|
||||
if ( hkId == state2waiter.id + 1 ||
|
||||
( hkId == state2waiter.id && state2waiter.key == state2waiter.key2 ) )
|
||||
{
|
||||
emit hotkeyActivated( state2waiter.handle );
|
||||
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 )
|
||||
{
|
||||
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 );
|
||||
UnregisterEventHotKey(hs.hkRef);
|
||||
|
||||
sendCmdC();
|
||||
|
||||
|
@ -193,17 +176,17 @@ void HotkeyWrapper::activated( int hkId )
|
|||
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 );
|
||||
if (hs.key2 == 0) {
|
||||
emit hotkeyActivated(hs.handle);
|
||||
return;
|
||||
}
|
||||
|
||||
state2 = true;
|
||||
state2waiter = hs;
|
||||
QTimer::singleShot( 500, this, SLOT( waitKey2() ) );
|
||||
QTimer::singleShot(500, this, SLOT(waitKey2()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -214,75 +197,73 @@ void HotkeyWrapper::activated( int hkId )
|
|||
|
||||
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);
|
||||
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 ) {
|
||||
if (!key) {
|
||||
return false; // We don't monitor empty combinations
|
||||
}
|
||||
}
|
||||
|
||||
quint32 vk = nativeKey( key );
|
||||
quint32 vk = nativeKey(key);
|
||||
|
||||
if( vk == 0 ) {
|
||||
if (vk == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
quint32 vk2 = key2 ? nativeKey( key2 ) : 0;
|
||||
quint32 vk2 = key2 ? nativeKey(key2) : 0;
|
||||
|
||||
static int nextId = 1;
|
||||
if( nextId > 0xBFFF - 1 ) {
|
||||
if (nextId > 0xBFFF - 1) {
|
||||
nextId = 1;
|
||||
}
|
||||
}
|
||||
|
||||
quint32 mod = 0;
|
||||
if( modifier & Qt::CTRL ) {
|
||||
if (modifier & Qt::CTRL) {
|
||||
mod |= cmdKey;
|
||||
}
|
||||
if( modifier & Qt::ALT ) {
|
||||
}
|
||||
if (modifier & Qt::ALT) {
|
||||
mod |= optionKey;
|
||||
}
|
||||
if( modifier & Qt::SHIFT ) {
|
||||
}
|
||||
if (modifier & Qt::SHIFT) {
|
||||
mod |= shiftKey;
|
||||
}
|
||||
if( modifier & Qt::META ) {
|
||||
}
|
||||
if (modifier & Qt::META) {
|
||||
mod |= controlKey;
|
||||
}
|
||||
}
|
||||
|
||||
hotkeys.append( HotkeyStruct( vk, vk2, mod, handle, nextId ) );
|
||||
HotkeyStruct &hk = hotkeys.last();
|
||||
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 ) {
|
||||
OSStatus ret = RegisterEventHotKey(vk, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef);
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( vk2 && vk2 != vk )
|
||||
{
|
||||
if (vk2 && vk2 != vk) {
|
||||
hotKeyID.id = nextId + 1;
|
||||
ret = RegisterEventHotKey( vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2 );
|
||||
ret = RegisterEventHotKey(vk2, mod, hotKeyID, GetApplicationEventTarget(), 0, &hk.hkRef2);
|
||||
}
|
||||
|
||||
nextId += 2;
|
||||
|
@ -290,65 +271,97 @@ bool HotkeyWrapper::setGlobalKey( int key, int key2, Qt::KeyboardModifiers modif
|
|||
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 );
|
||||
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;
|
||||
|
|
|
@ -1,33 +1,24 @@
|
|||
#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();
|
||||
}
|
||||
static_cast<MacMouseOver*>(refcon)->mouseMoved();
|
||||
return event;
|
||||
}
|
||||
|
||||
static CGPoint carbonScreenPointFromCocoaScreenPoint( NSPoint cocoaPoint )
|
||||
static CGPoint carbonScreenPointFromCocoaScreenPoint(NSPoint cocoaPoint)
|
||||
{
|
||||
NSScreen *foundScreen = nil;
|
||||
NSScreen* foundScreen = nil;
|
||||
CGPoint thePoint;
|
||||
|
||||
for (NSScreen *screen in [NSScreen screens]) {
|
||||
for (NSScreen* screen in [NSScreen screens]) {
|
||||
if (NSPointInRect(cocoaPoint, [screen frame])) {
|
||||
foundScreen = screen;
|
||||
}
|
||||
|
@ -36,28 +27,27 @@ static CGPoint carbonScreenPointFromCocoaScreenPoint( NSPoint cocoaPoint )
|
|||
if (foundScreen) {
|
||||
CGFloat screenHeight = [foundScreen frame].size.height;
|
||||
thePoint = CGPointMake(cocoaPoint.x, screenHeight - cocoaPoint.y - 1);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
thePoint = CGPointMake(0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
return thePoint;
|
||||
}
|
||||
|
||||
MacMouseOver & MacMouseOver::instance()
|
||||
MacMouseOver& MacMouseOver::instance()
|
||||
{
|
||||
static MacMouseOver 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();
|
||||
}
|
||||
|
@ -66,30 +56,30 @@ MacMouseOver::~MacMouseOver()
|
|||
{
|
||||
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 ) {
|
||||
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;
|
||||
|
@ -97,276 +87,258 @@ QString MacMouseOver::CFStringRefToQString( CFStringRef str )
|
|||
|
||||
void MacMouseOver::mouseMoved()
|
||||
{
|
||||
mouseTimer.start( mouseOverInterval );
|
||||
mouseTimer.start(mouseOverInterval);
|
||||
}
|
||||
|
||||
void MacMouseOver::enableMouseOver()
|
||||
{
|
||||
mouseTimer.stop();
|
||||
if( !isAXAPIEnabled() ) {
|
||||
if (!isAXAPIEnabled()) {
|
||||
return;
|
||||
}
|
||||
if( !tapRef ) {
|
||||
tapRef = CGEventTapCreate( kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
|
||||
}
|
||||
if (!tapRef) {
|
||||
tapRef = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGHeadInsertEventTap,
|
||||
kCGEventTapOptionListenOnly,
|
||||
CGEventMaskBit( kCGEventMouseMoved ),
|
||||
eventCallback, this );
|
||||
}
|
||||
if( !tapRef ) {
|
||||
CGEventMaskBit(kCGEventMouseMoved),
|
||||
eventCallback, this);
|
||||
}
|
||||
if (!tapRef) {
|
||||
return;
|
||||
}
|
||||
if( !loop ) {
|
||||
loop = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, tapRef, 0 );
|
||||
}
|
||||
if( loop ) {
|
||||
CFRunLoopAddSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes );
|
||||
}
|
||||
}
|
||||
if (!loop) {
|
||||
loop = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tapRef, 0);
|
||||
}
|
||||
if (loop) {
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
|
||||
}
|
||||
}
|
||||
|
||||
void MacMouseOver::disableMouseOver()
|
||||
{
|
||||
mouseTimer.stop();
|
||||
if( loop ) {
|
||||
CFRunLoopRemoveSource( CFRunLoopGetMain(), loop, kCFRunLoopCommonModes );
|
||||
}
|
||||
if (loop) {
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), loop, kCFRunLoopCommonModes);
|
||||
}
|
||||
}
|
||||
|
||||
void MacMouseOver::timerShot()
|
||||
{
|
||||
if( mouseMutex.tryLock( 0 ) ) {
|
||||
if (mouseMutex.tryLock(0)) {
|
||||
mouseMutex.unlock();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if( !pPref ) {
|
||||
}
|
||||
if (!pPref) {
|
||||
return;
|
||||
}
|
||||
if( !pPref->enableScanPopupModifiers || checkModifiersPressed( pPref->scanPopupModifiers ) ) {
|
||||
}
|
||||
if (!pPref->enableScanPopupModifiers || checkModifiersPressed(pPref->scanPopupModifiers)) {
|
||||
handlePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacMouseOver::handlePosition()
|
||||
{
|
||||
QMutexLocker _( &mouseMutex );
|
||||
QMutexLocker _(&mouseMutex);
|
||||
|
||||
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;
|
||||
|
||||
AXUIElementRef elem = 0;
|
||||
AXError err = AXUIElementCopyElementAtPosition( elementSystemWide, pt.x, pt.y, &elem );
|
||||
AXError err = AXUIElementCopyElementAtPosition(elementSystemWide, pt.x, pt.y, &elem);
|
||||
|
||||
if( err != kAXErrorSuccess ) {
|
||||
if (err != kAXErrorSuccess) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for( ; ; )
|
||||
{
|
||||
CFTypeRef parameter = AXValueCreate( kAXValueTypeCGPoint, &pt );
|
||||
for (;;) {
|
||||
CFTypeRef parameter = AXValueCreate(kAXValueTypeCGPoint, &pt);
|
||||
CFTypeRef rangeValue;
|
||||
err = AXUIElementCopyParameterizedAttributeNames( elem, &names );
|
||||
if( err != kAXErrorSuccess ) {
|
||||
err = AXUIElementCopyParameterizedAttributeNames(elem, &names);
|
||||
if (err != kAXErrorSuccess) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int numOfAttributes = CFArrayGetCount( names );
|
||||
if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXRangeForPosition" ) ) )
|
||||
{
|
||||
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 ) {
|
||||
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 )
|
||||
{
|
||||
CFRange decodedRange = CFRangeMake(0, 0);
|
||||
bool b = AXValueGetValue((AXValueRef)rangeValue, kAXValueTypeCFRange, &decodedRange);
|
||||
CFRelease(rangeValue);
|
||||
if (b) {
|
||||
int fromPos = decodedRange.location - 127;
|
||||
if( fromPos < 0 ) {
|
||||
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 ) {
|
||||
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 );
|
||||
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 );
|
||||
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 ) {
|
||||
if (err != kAXErrorSuccess) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QString s = CFStringRefToQString( stringValue );
|
||||
CFRelease( stringValue );
|
||||
QString s = CFStringRefToQString(stringValue);
|
||||
CFRelease(stringValue);
|
||||
|
||||
if( s[ 0 ].isLetterOrNumber() || s[ 0 ] == '-' ) {
|
||||
if (s[0].isLetterOrNumber() || s[0] == '-') {
|
||||
strToTranslate += s;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleRetrievedString( strToTranslate, wordPos );
|
||||
handleRetrievedString(strToTranslate, wordPos);
|
||||
}
|
||||
}
|
||||
else if( CFArrayContainsValue( names, CFRangeMake( 0, numOfAttributes ), CFSTR( "AXTextMarkerForPosition" ) ) )
|
||||
{
|
||||
} 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 ) {
|
||||
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 ) {
|
||||
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 );
|
||||
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() ) {
|
||||
if (wordSeq.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// locate the word inside the sequence
|
||||
|
||||
QString word;
|
||||
|
||||
if ( wordSeq[ wordSeqPos ].isSpace() )
|
||||
{
|
||||
if (wordSeq[wordSeqPos].isSpace()) {
|
||||
// Currently we ignore such cases
|
||||
return;
|
||||
}
|
||||
else
|
||||
if ( !wordSeq[ wordSeqPos ].isLetterOrNumber() )
|
||||
{
|
||||
} 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() ) {
|
||||
for (; begin; --begin) {
|
||||
if (!wordSeq[begin - 1].isLetterOrNumber()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int end = wordSeqPos;
|
||||
|
||||
while( ++end < wordSeq.size() ) {
|
||||
if ( !wordSeq[ end ].isLetterOrNumber() ) {
|
||||
while (++end < wordSeq.size()) {
|
||||
if (!wordSeq[end].isLetterOrNumber()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( end - begin == 1 )
|
||||
{
|
||||
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
|
||||
{
|
||||
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() ) {
|
||||
for (; begin; --begin) {
|
||||
if (!wordSeq[begin - 1].isLetterOrNumber()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int end = wordSeqPos;
|
||||
|
||||
while( ++end < wordSeq.size() )
|
||||
{
|
||||
if ( !wordSeq[ end ].isLetterOrNumber() ) {
|
||||
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.
|
||||
|
||||
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() );
|
||||
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+
|
||||
if (NSFoundationVersionNumber >= 1000) { // MacOS 10.9+
|
||||
return AXIsProcessTrusted();
|
||||
}
|
||||
}
|
||||
|
||||
return AXAPIEnabled();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue