mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-05-05 17:55:45 +08:00
[MacOS] Change keyboard implementation to IOKit
This commit is contained in:
committed by
Mat Sutcliffe
parent
cc620698c6
commit
a36ae7b680
@@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
#include "blackinput/keyboard.h"
|
#include "blackinput/keyboard.h"
|
||||||
#include "blackmisc/input/hotkeycombination.h"
|
#include "blackmisc/input/hotkeycombination.h"
|
||||||
|
#include "blackmisc/input/keycodes.h"
|
||||||
|
|
||||||
|
#include <IOKit/hid/IOHIDManager.h>
|
||||||
|
|
||||||
#include <ApplicationServices/ApplicationServices.h>
|
#include <ApplicationServices/ApplicationServices.h>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
@@ -34,9 +38,6 @@ namespace BlackInput
|
|||||||
//! Destructor
|
//! Destructor
|
||||||
virtual ~CKeyboardMacOS() override;
|
virtual ~CKeyboardMacOS() override;
|
||||||
|
|
||||||
//! Process key event
|
|
||||||
virtual void processKeyEvent(CGEventType type, CGEventRef event);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//! \copydoc IKeyboard::init()
|
//! \copydoc IKeyboard::init()
|
||||||
virtual bool init() override;
|
virtual bool init() override;
|
||||||
@@ -46,16 +47,14 @@ namespace BlackInput
|
|||||||
|
|
||||||
//! Constructor
|
//! Constructor
|
||||||
CKeyboardMacOS(QObject *parent = nullptr);
|
CKeyboardMacOS(QObject *parent = nullptr);
|
||||||
BlackMisc::Input::KeyCode convertToKey(int keyCode);
|
|
||||||
|
|
||||||
static CGEventRef myCGEventCallback(CGEventTapProxy proxy,
|
void processKeyEvent(IOHIDValueRef value);
|
||||||
CGEventType type,
|
|
||||||
CGEventRef event,
|
|
||||||
void *refcon);
|
|
||||||
|
|
||||||
|
static BlackMisc::Input::KeyCode convertToKey(quint32 keyCode);
|
||||||
|
static void valueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value);
|
||||||
|
|
||||||
|
IOHIDManagerRef m_hidManager = nullptr;
|
||||||
BlackMisc::Input::CHotkeyCombination m_keyCombination; //!< Current status of pressed keys;
|
BlackMisc::Input::CHotkeyCombination m_keyCombination; //!< Current status of pressed keys;
|
||||||
CFMachPortRef m_eventTap = nullptr;
|
|
||||||
CFRunLoopSourceRef m_sourceRef = nullptr;
|
|
||||||
};
|
};
|
||||||
} // ns
|
} // ns
|
||||||
|
|
||||||
|
|||||||
@@ -8,203 +8,102 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "keyboardmacos.h"
|
#include "keyboardmacos.h"
|
||||||
|
#include "macosinpututils.h"
|
||||||
#include "blackmisc/logmessage.h"
|
#include "blackmisc/logmessage.h"
|
||||||
|
|
||||||
#include <QHash>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#include <AppKit/NSEvent.h>
|
#include <array>
|
||||||
#include <AppKit/NSAlert.h>
|
|
||||||
#include <Foundation/NSString.h>
|
|
||||||
#include <Carbon/Carbon.h>
|
|
||||||
|
|
||||||
|
using namespace BlackMisc;
|
||||||
using namespace BlackMisc::Input;
|
using namespace BlackMisc::Input;
|
||||||
|
|
||||||
namespace BlackInput
|
namespace BlackInput
|
||||||
{
|
{
|
||||||
|
|
||||||
static QHash<int, KeyCode> keyMapping
|
static QHash<quint32, KeyCode> keyMapping
|
||||||
{
|
{
|
||||||
{ kVK_ANSI_0, Key_0 },
|
{ kHIDUsage_Keyboard0, Key_0 },
|
||||||
{ kVK_ANSI_1, Key_1 },
|
{ kHIDUsage_Keyboard0, Key_1 },
|
||||||
{ kVK_ANSI_2, Key_2 },
|
{ kHIDUsage_Keyboard0, Key_2 },
|
||||||
{ kVK_ANSI_3, Key_3 },
|
{ kHIDUsage_Keyboard0, Key_3 },
|
||||||
{ kVK_ANSI_4, Key_4 },
|
{ kHIDUsage_Keyboard4, Key_4 },
|
||||||
{ kVK_ANSI_5, Key_5 },
|
{ kHIDUsage_Keyboard5, Key_5 },
|
||||||
{ kVK_ANSI_6, Key_6 },
|
{ kHIDUsage_Keyboard6, Key_6 },
|
||||||
{ kVK_ANSI_7, Key_7 },
|
{ kHIDUsage_Keyboard7, Key_7 },
|
||||||
{ kVK_ANSI_8, Key_8 },
|
{ kHIDUsage_Keyboard8, Key_8 },
|
||||||
{ kVK_ANSI_9, Key_9 },
|
{ kHIDUsage_Keyboard9, Key_9 },
|
||||||
{ kVK_ANSI_A, Key_A },
|
{ kHIDUsage_KeyboardA, Key_A },
|
||||||
{ kVK_ANSI_B, Key_B },
|
{ kHIDUsage_KeyboardB, Key_B },
|
||||||
{ kVK_ANSI_C, Key_C },
|
{ kHIDUsage_KeyboardC, Key_C },
|
||||||
{ kVK_ANSI_D, Key_D },
|
{ kHIDUsage_KeyboardD, Key_D },
|
||||||
{ kVK_ANSI_E, Key_E },
|
{ kHIDUsage_KeyboardE, Key_E },
|
||||||
{ kVK_ANSI_F, Key_F },
|
{ kHIDUsage_KeyboardF, Key_F },
|
||||||
{ kVK_ANSI_G, Key_G },
|
{ kHIDUsage_KeyboardG, Key_G },
|
||||||
{ kVK_ANSI_H, Key_H },
|
{ kHIDUsage_KeyboardH, Key_H },
|
||||||
{ kVK_ANSI_I, Key_I },
|
{ kHIDUsage_KeyboardI, Key_I },
|
||||||
{ kVK_ANSI_J, Key_J },
|
{ kHIDUsage_KeyboardJ, Key_J },
|
||||||
{ kVK_ANSI_K, Key_K },
|
{ kHIDUsage_KeyboardK, Key_K },
|
||||||
{ kVK_ANSI_L, Key_L },
|
{ kHIDUsage_KeyboardL, Key_L },
|
||||||
{ kVK_ANSI_M, Key_M },
|
{ kHIDUsage_KeyboardM, Key_M },
|
||||||
{ kVK_ANSI_N, Key_N },
|
{ kHIDUsage_KeyboardN, Key_N },
|
||||||
{ kVK_ANSI_O, Key_O },
|
{ kHIDUsage_KeyboardO, Key_O },
|
||||||
{ kVK_ANSI_P, Key_P },
|
{ kHIDUsage_KeyboardP, Key_P },
|
||||||
{ kVK_ANSI_Q, Key_Q },
|
{ kHIDUsage_KeyboardQ, Key_Q },
|
||||||
{ kVK_ANSI_R, Key_R },
|
{ kHIDUsage_KeyboardR, Key_R },
|
||||||
{ kVK_ANSI_S, Key_S },
|
{ kHIDUsage_KeyboardS, Key_S },
|
||||||
{ kVK_ANSI_T, Key_T },
|
{ kHIDUsage_KeyboardT, Key_T },
|
||||||
{ kVK_ANSI_U, Key_U },
|
{ kHIDUsage_KeyboardU, Key_U },
|
||||||
{ kVK_ANSI_V, Key_V },
|
{ kHIDUsage_KeyboardV, Key_V },
|
||||||
{ kVK_ANSI_W, Key_W },
|
{ kHIDUsage_KeyboardW, Key_W },
|
||||||
{ kVK_ANSI_X, Key_X },
|
{ kHIDUsage_KeyboardX, Key_X },
|
||||||
{ kVK_ANSI_Y, Key_Y },
|
{ kHIDUsage_KeyboardY, Key_Y },
|
||||||
{ kVK_ANSI_Z, Key_Z },
|
{ kHIDUsage_KeyboardZ, Key_Z },
|
||||||
{ kVK_ANSI_KeypadPlus, Key_Plus },
|
{ kHIDUsage_KeypadPlus, Key_Plus },
|
||||||
{ kVK_ANSI_KeypadMinus, Key_Minus },
|
{ kHIDUsage_KeypadHyphen, Key_Minus },
|
||||||
{ kVK_ANSI_Minus, Key_Minus },
|
{ kHIDUsage_KeyboardHyphen, Key_Minus },
|
||||||
{ kVK_ANSI_Period, Key_Period },
|
{ kHIDUsage_KeyboardPeriod, Key_Period },
|
||||||
{ kVK_ANSI_KeypadDivide, Key_Divide },
|
{ kHIDUsage_KeypadSlash, Key_Divide },
|
||||||
{ kVK_ANSI_KeypadMultiply, Key_Multiply },
|
{ kHIDUsage_KeypadAsterisk, Key_Multiply },
|
||||||
{ kVK_ANSI_Comma, Key_Comma },
|
{ kHIDUsage_KeyboardComma, Key_Comma },
|
||||||
{ kVK_ANSI_Keypad0, Key_Numpad0 },
|
{ kHIDUsage_Keypad0, Key_Numpad0 },
|
||||||
{ kVK_ANSI_Keypad1, Key_Numpad1 },
|
{ kHIDUsage_Keypad1, Key_Numpad1 },
|
||||||
{ kVK_ANSI_Keypad2, Key_Numpad2 },
|
{ kHIDUsage_Keypad2, Key_Numpad2 },
|
||||||
{ kVK_ANSI_Keypad3, Key_Numpad3 },
|
{ kHIDUsage_Keypad3, Key_Numpad3 },
|
||||||
{ kVK_ANSI_Keypad4, Key_Numpad4 },
|
{ kHIDUsage_Keypad4, Key_Numpad4 },
|
||||||
{ kVK_ANSI_Keypad5, Key_Numpad5 },
|
{ kHIDUsage_Keypad5, Key_Numpad5 },
|
||||||
{ kVK_ANSI_Keypad6, Key_Numpad6 },
|
{ kHIDUsage_Keypad6, Key_Numpad6 },
|
||||||
{ kVK_ANSI_Keypad7, Key_Numpad7 },
|
{ kHIDUsage_Keypad7, Key_Numpad7 },
|
||||||
{ kVK_ANSI_Keypad8, Key_Numpad8 },
|
{ kHIDUsage_Keypad8, Key_Numpad8 },
|
||||||
{ kVK_ANSI_Keypad9, Key_Numpad9 },
|
{ kHIDUsage_Keypad9, Key_Numpad9 },
|
||||||
|
{ kHIDUsage_KeyboardRightControl, Key_ControlRight },
|
||||||
|
{ kHIDUsage_KeyboardLeftControl, Key_ControlLeft },
|
||||||
|
{ kHIDUsage_KeyboardRightAlt, Key_AltRight },
|
||||||
|
{ kHIDUsage_KeyboardLeftAlt, Key_AltLeft },
|
||||||
};
|
};
|
||||||
|
|
||||||
CKeyboardMacOS::CKeyboardMacOS(QObject *parent) :
|
CKeyboardMacOS::CKeyboardMacOS(QObject *parent) : IKeyboard(parent)
|
||||||
IKeyboard(parent)
|
{ }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CKeyboardMacOS::~CKeyboardMacOS()
|
void CKeyboardMacOS::processKeyEvent(IOHIDValueRef value)
|
||||||
{
|
{
|
||||||
if (m_eventTap) { CGEventTapEnable(m_eventTap, false); }
|
IOHIDElementRef element = IOHIDValueGetElement(value);
|
||||||
if (m_sourceRef)
|
UInt32 usagePage = IOHIDElementGetUsagePage(element);
|
||||||
|
if (usagePage != kHIDPage_KeyboardOrKeypad) { return; }
|
||||||
|
|
||||||
|
quint32 scancode = IOHIDElementGetUsage(element);
|
||||||
|
|
||||||
|
KeyCode code = convertToKey(scancode);
|
||||||
|
if (code != Key_Unknown)
|
||||||
{
|
{
|
||||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_sourceRef, kCFRunLoopCommonModes);
|
CHotkeyCombination oldCombination(m_keyCombination);
|
||||||
CFRelease(m_sourceRef);
|
bool pressed = IOHIDValueGetIntegerValue(value) == 1;
|
||||||
}
|
if (pressed)
|
||||||
if (m_eventTap) { CFRelease(m_eventTap); }
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CKeyboardMacOS::init()
|
|
||||||
{
|
{
|
||||||
// 10.9 and later
|
m_keyCombination.addKeyboardKey(code);
|
||||||
const void *keys[] = { kAXTrustedCheckOptionPrompt };
|
|
||||||
const void *values[] = { kCFBooleanTrue };
|
|
||||||
|
|
||||||
CFDictionaryRef options = CFDictionaryCreate(
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
keys,
|
|
||||||
values,
|
|
||||||
sizeof(keys) / sizeof(*keys),
|
|
||||||
&kCFCopyStringDictionaryKeyCallBacks,
|
|
||||||
&kCFTypeDictionaryValueCallBacks);
|
|
||||||
|
|
||||||
bool accessibilityEnabled = AXIsProcessTrustedWithOptions(options);
|
|
||||||
CFRelease(options);
|
|
||||||
if (!accessibilityEnabled)
|
|
||||||
{
|
|
||||||
QMessageBox msgBox;
|
|
||||||
msgBox.setText("In order to enable hotkeys first add swift to the list of apps allowed to "
|
|
||||||
"control your computer in System Preferences / Security & Privacy / Privacy / Accessiblity and then restart Swift.");
|
|
||||||
msgBox.exec();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGEventMask eventMask = ((1 << kCGEventKeyDown) | (1 << kCGEventKeyUp) | (1 << kCGEventFlagsChanged));
|
|
||||||
|
|
||||||
// try creating an event tap just for keypresses. if it fails, we need Universal Access.
|
|
||||||
m_eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
|
|
||||||
eventMask, myCGEventCallback, this);
|
|
||||||
if (! m_eventTap) { return false; }
|
|
||||||
|
|
||||||
m_sourceRef = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTap, 0);
|
|
||||||
if (! m_sourceRef) { return false; }
|
|
||||||
|
|
||||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), m_sourceRef, kCFRunLoopCommonModes);
|
|
||||||
CGEventTapEnable(m_eventTap, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CKeyboardMacOS::processKeyEvent(CGEventType type,
|
|
||||||
CGEventRef event)
|
|
||||||
{
|
|
||||||
BlackMisc::Input::CHotkeyCombination oldCombination(m_keyCombination);
|
|
||||||
|
|
||||||
unsigned int vkcode = static_cast<unsigned int>(CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
|
|
||||||
|
|
||||||
if (type == kCGEventKeyDown)
|
|
||||||
{
|
|
||||||
auto key = convertToKey(vkcode);
|
|
||||||
if (key == Key_Unknown) { return; }
|
|
||||||
m_keyCombination.addKeyboardKey(key);
|
|
||||||
}
|
|
||||||
else if (type == kCGEventKeyUp)
|
|
||||||
{
|
|
||||||
auto key = convertToKey(vkcode);
|
|
||||||
if (key == Key_Unknown) { return; }
|
|
||||||
m_keyCombination.removeKeyboardKey(key);
|
|
||||||
}
|
|
||||||
else if (type == kCGEventFlagsChanged)
|
|
||||||
{
|
|
||||||
CGEventFlags f = CGEventGetFlags(event);
|
|
||||||
|
|
||||||
if ((f & kCGEventFlagMaskShift) == kCGEventFlagMaskShift)
|
|
||||||
{
|
|
||||||
if (vkcode == 56)
|
|
||||||
{
|
|
||||||
m_keyCombination.addKeyboardKey(Key_ShiftLeft);
|
|
||||||
}
|
|
||||||
else if (vkcode == 60)
|
|
||||||
{
|
|
||||||
m_keyCombination.addKeyboardKey(Key_ShiftRight);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_keyCombination.removeKeyboardKey(Key_ShiftLeft);
|
m_keyCombination.removeKeyboardKey(code);
|
||||||
m_keyCombination.removeKeyboardKey(Key_ShiftRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((f & kCGEventFlagMaskControl) == kCGEventFlagMaskControl)
|
|
||||||
{
|
|
||||||
// at least on the mac wireless keyboard there is no right ctrl key
|
|
||||||
if (vkcode == 59)
|
|
||||||
{
|
|
||||||
m_keyCombination.addKeyboardKey(Key_ControlLeft);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_keyCombination.removeKeyboardKey(Key_ControlLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((f & kCGEventFlagMaskAlternate) == kCGEventFlagMaskAlternate)
|
|
||||||
{
|
|
||||||
if (vkcode == 58)
|
|
||||||
{
|
|
||||||
m_keyCombination.addKeyboardKey(Key_AltLeft);
|
|
||||||
}
|
|
||||||
else if (vkcode == 61)
|
|
||||||
{
|
|
||||||
m_keyCombination.addKeyboardKey(Key_AltRight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_keyCombination.removeKeyboardKey(Key_AltLeft);
|
|
||||||
m_keyCombination.removeKeyboardKey(Key_AltRight);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldCombination != m_keyCombination)
|
if (oldCombination != m_keyCombination)
|
||||||
@@ -212,32 +111,61 @@ namespace BlackInput
|
|||||||
emit keyCombinationChanged(m_keyCombination);
|
emit keyCombinationChanged(m_keyCombination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode CKeyboardMacOS::convertToKey(int keyCode)
|
CKeyboardMacOS::~CKeyboardMacOS()
|
||||||
|
{
|
||||||
|
if (m_hidManager)
|
||||||
|
{
|
||||||
|
IOHIDManagerClose(m_hidManager, kIOHIDOptionsTypeNone);
|
||||||
|
CFRelease(m_hidManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CKeyboardMacOS::init()
|
||||||
|
{
|
||||||
|
m_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||||
|
|
||||||
|
CFMutableArrayRef matchingArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
||||||
|
if (!matchingArray)
|
||||||
|
{
|
||||||
|
CLogMessage(this).warning(u"Cocoa: Failed to create array");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFDictionaryRef matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
|
||||||
|
if (matchingDict)
|
||||||
|
{
|
||||||
|
CFArrayAppendValue(matchingArray, matchingDict);
|
||||||
|
CFRelease(matchingDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Keypad);
|
||||||
|
if (matchingDict)
|
||||||
|
{
|
||||||
|
CFArrayAppendValue(matchingArray, matchingDict);
|
||||||
|
CFRelease(matchingDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOHIDManagerSetDeviceMatchingMultiple(m_hidManager, matchingArray);
|
||||||
|
CFRelease(matchingArray);
|
||||||
|
|
||||||
|
IOHIDManagerRegisterInputValueCallback(m_hidManager, valueCallback, this);
|
||||||
|
IOHIDManagerScheduleWithRunLoop(m_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||||
|
IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CKeyboardMacOS::valueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value)
|
||||||
|
{
|
||||||
|
Q_UNUSED(result);
|
||||||
|
Q_UNUSED(sender);
|
||||||
|
CKeyboardMacOS *obj = static_cast<CKeyboardMacOS *>(context);
|
||||||
|
obj->processKeyEvent(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyCode CKeyboardMacOS::convertToKey(quint32 keyCode)
|
||||||
{
|
{
|
||||||
return keyMapping.value(keyCode, Key_Unknown);
|
return keyMapping.value(keyCode, Key_Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
CGEventRef CKeyboardMacOS::myCGEventCallback(CGEventTapProxy,
|
|
||||||
CGEventType type,
|
|
||||||
CGEventRef event,
|
|
||||||
void *refcon)
|
|
||||||
{
|
|
||||||
// If disabled on purpose, don't do anything further.
|
|
||||||
if (type == kCGEventTapDisabledByUserInput) { return event; }
|
|
||||||
|
|
||||||
CKeyboardMacOS *keyboardMac = static_cast<CKeyboardMacOS*>(refcon);
|
|
||||||
if (type == kCGEventTapDisabledByTimeout)
|
|
||||||
{
|
|
||||||
BlackMisc::CLogMessage(static_cast<CKeyboardMacOS *>(nullptr)).warning(u"Event tap got disabled by timeout. Enable it again.");
|
|
||||||
CGEventTapEnable(keyboardMac->m_eventTap, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
keyboardMac->processKeyEvent(type, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// send event to next application
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user