mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-23 20:14:05 +00:00
338 lines
8.9 KiB
Plaintext
338 lines
8.9 KiB
Plaintext
#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
|
|
|
|
const int mouseOverInterval = 300;
|
|
|
|
CGEventRef eventCallback( CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon )
|
|
{
|
|
(void) proxy;
|
|
if( type != kCGEventMouseMoved )
|
|
return event;
|
|
static_cast< MacMouseOver * >( refcon )->mouseMoved();
|
|
return event;
|
|
}
|
|
|
|
static CGPoint carbonScreenPointFromCocoaScreenPoint( NSPoint cocoaPoint )
|
|
{
|
|
NSScreen *foundScreen = nil;
|
|
CGPoint thePoint;
|
|
|
|
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);
|
|
|
|
return thePoint;
|
|
}
|
|
|
|
MacMouseOver & MacMouseOver::instance()
|
|
{
|
|
static MacMouseOver m;
|
|
|
|
return m;
|
|
}
|
|
|
|
MacMouseOver::MacMouseOver() :
|
|
pPref(NULL)
|
|
, tapRef( 0 )
|
|
, loop( 0 )
|
|
{
|
|
mouseTimer.setSingleShot( true );
|
|
connect( &mouseTimer, SIGNAL( timeout() ), this, SLOT( timerShot() ) );
|
|
|
|
elementSystemWide = AXUIElementCreateSystemWide();
|
|
}
|
|
|
|
MacMouseOver::~MacMouseOver()
|
|
{
|
|
disableMouseOver();
|
|
|
|
if( tapRef )
|
|
CFRelease( tapRef );
|
|
|
|
if( loop )
|
|
CFRelease( loop );
|
|
|
|
if( elementSystemWide )
|
|
CFRelease( elementSystemWide );
|
|
}
|
|
|
|
QString MacMouseOver::CFStringRefToQString( CFStringRef str )
|
|
{
|
|
int length = CFStringGetLength( str );
|
|
if( length == 0 )
|
|
return QString();
|
|
|
|
UniChar *chars = new UniChar[ length ];
|
|
CFStringGetCharacters( str, CFRangeMake( 0, length ), chars );
|
|
|
|
QString result = QString::fromUtf16( chars, length );
|
|
|
|
delete[] chars;
|
|
return result;
|
|
}
|
|
|
|
void MacMouseOver::mouseMoved()
|
|
{
|
|
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 );
|
|
}
|
|
|
|
void MacMouseOver::disableMouseOver()
|
|
{
|
|
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();
|
|
}
|
|
|
|
void MacMouseOver::handlePosition()
|
|
{
|
|
Mutex::Lock _( mouseMutex );
|
|
|
|
QString strToTranslate;
|
|
|
|
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 );
|
|
|
|
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 )
|
|
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;
|
|
}
|
|
if( elem )
|
|
CFRelease( elem );
|
|
if( names )
|
|
CFRelease( names );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
// 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();
|
|
|
|
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 );
|
|
}
|
|
|
|
bool MacMouseOver::isAXAPIEnabled()
|
|
{
|
|
if( NSFoundationVersionNumber >= 1000 ) // MacOS 10.9+
|
|
return AXIsProcessTrusted();
|
|
|
|
return AXAPIEnabled();
|
|
}
|