goldendict-ng/machotkeywrapper.mm
2022-12-30 10:46:44 +08:00

299 lines
8.1 KiB
Plaintext

/* This file is (c) 2013 Abs62
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "hotkeywrapper.hh"
#include <ApplicationServices/ApplicationServices.h>
namespace MacKeyMapping
{
// Convert Qt key codes to Mac OS X native codes
struct ReverseMapEntry
{
UniChar character;
UInt16 keyCode;
};
static struct ReverseMapEntry * mapping;
static int mapEntries = 0;
void createMapping()
{
if( mapping == NULL )
{
TISInputSourceRef inputSourceRef = TISCopyInputSourceForLanguage( CFSTR( "en" ) );
if ( !inputSourceRef )
inputSourceRef = TISCopyCurrentKeyboardInputSource();
if ( !inputSourceRef )
return;
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;
}
}
const UCKeyboardLayout * keyboardLayoutPtr = ( const UCKeyboardLayout * )CFDataGetBytePtr( dataRef );
if( !keyboardLayoutPtr )
return;
mapping = ( struct ReverseMapEntry * )calloc( 128 , sizeof(struct ReverseMapEntry) );
if( !mapping )
return;
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;
}
}
}
}
}
quint32 qtKeyToNativeKey( quint32 key )
{
createMapping();
if( mapping == NULL )
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 )
{
EventHotKeyID hkID;
GetEventParameter( theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(EventHotKeyID), NULL, &hkID );
static_cast< HotkeyWrapper * >( userData )->activated( hkID.id );
return noErr;
}
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' );
}
HotkeyWrapper::~HotkeyWrapper()
{
unregister();
RemoveEventHandler( handlerRef );
}
void HotkeyWrapper::waitKey2()
{
state2 = false;
}
void HotkeyWrapper::activated( int hkId )
{
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 );
return;
}
}
for ( int i = 0; i < hotkeys.count(); i++ )
{
HotkeyStruct &hs = hotkeys[ i ];
if( hkId == hs.id )
{
if( hs.key == keyC && hs.modifier == cmdKey )
{
// 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();
EventHotKeyID hotKeyID;
hotKeyID.signature = 'GDHK';
hotKeyID.id = hs.id;
RegisterEventHotKey( hs.key, hs.modifier, hotKeyID, GetApplicationEventTarget(), 0, &hs.hkRef );
}
if ( hs.key2 == 0 ) {
emit hotkeyActivated( hs.handle );
return;
}
state2 = true;
state2waiter = hs;
QTimer::singleShot( 500, this, SLOT( waitKey2() ) );
return;
}
}
state2 = false;
return;
}
void HotkeyWrapper::unregister()
{
for ( int i = 0; i < hotkeys.count(); i++ )
{
HotkeyStruct const & hk = hotkeys.at( i );
UnregisterEventHotKey( hk.hkRef );
if ( hk.key2 && hk.key2 != hk.key )
UnregisterEventHotKey( hk.hkRef2 );
}
(static_cast< QHotkeyApplication * >( qApp ))->unregisterWrapper( this );
}
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
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 )
{
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() );
}
void HotkeyWrapper::sendCmdC()
{
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 up
ev = CGEventCreateKeyboardEvent( source, keyC, false );
CGEventSetFlags( ev, CGEventFlags( flags | CGEventGetFlags( ev ) ) ); //combine flags
CGEventPost( kCGAnnotatedSessionEventTap, ev );
CFRelease( ev );
CFRelease( source );
}
EventHandlerUPP HotkeyWrapper::hotKeyFunction = NULL;