goldendict-ng/macmouseover.mm

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();
}