Merge pull request #11739 from OatmealDome/iokit-be-gone

InputCommon: Remove OSX (IOKit) input backend
This commit is contained in:
Admiral H. Curtiss 2023-05-19 19:31:40 +02:00 committed by GitHub
commit 50fe493990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1 additions and 731 deletions

View File

@ -107,11 +107,6 @@ if(WIN32)
)
elseif(APPLE)
target_sources(inputcommon PRIVATE
ControllerInterface/OSX/OSX.h
ControllerInterface/OSX/OSX.mm
ControllerInterface/OSX/OSXJoystick.h
ControllerInterface/OSX/OSXJoystick.mm
ControllerInterface/OSX/RunLoopStopper.h
ControllerInterface/Quartz/Quartz.h
ControllerInterface/Quartz/Quartz.mm
ControllerInterface/Quartz/QuartzKeyboardAndMouse.h
@ -124,7 +119,6 @@ elseif(APPLE)
${CARBON_LIBRARY}
${COCOA_LIBRARY}
${FORCEFEEDBACK_LIBRARY}
${IOK_LIBRARY}
)
target_compile_options(inputcommon PRIVATE
-fobjc-arc

View File

@ -16,7 +16,6 @@
#include "InputCommon/ControllerInterface/Xlib/XInput2.h"
#endif
#ifdef CIFACE_USE_OSX
#include "InputCommon/ControllerInterface/OSX/OSX.h"
#include "InputCommon/ControllerInterface/Quartz/Quartz.h"
#endif
#ifdef CIFACE_USE_SDL
@ -64,7 +63,7 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
// nothing needed
#endif
#ifdef CIFACE_USE_OSX
// nothing needed for OSX and Quartz
// nothing needed for Quartz
#endif
#ifdef CIFACE_USE_SDL
m_input_backends.emplace_back(ciface::SDL::CreateInputBackend(this));
@ -120,18 +119,6 @@ void ControllerInterface::RefreshDevices(RefreshReason reason)
if (!m_is_init)
return;
#ifdef CIFACE_USE_OSX
if (m_wsi.type == WindowSystemType::MacOS)
{
std::lock_guard lk_pre_population(m_pre_population_mutex);
// This is needed to stop its threads before locking our mutexes, to avoid deadlocks
// (in case it tried to add a device after we had locked m_devices_population_mutex).
// There doesn't seem to be an easy to way to repopulate OSX devices without restarting its
// hotplug thread. This should not remove its devices, and if it did, calls should be ignored.
ciface::OSX::DeInit();
}
#endif
// We lock m_devices_population_mutex here to make everything simpler.
// Multiple devices classes have their own "hotplug" thread, and can add/remove devices at any
// time, while actual writes to "m_devices" are safe, the order in which they happen is not. That
@ -180,10 +167,6 @@ void ControllerInterface::RefreshDevices(RefreshReason reason)
#ifdef CIFACE_USE_OSX
if (m_wsi.type == WindowSystemType::MacOS)
{
{
std::lock_guard lk_pre_population(m_pre_population_mutex);
ciface::OSX::Init();
}
ciface::Quartz::PopulateDevices(m_wsi.render_window);
}
#endif
@ -239,7 +222,6 @@ void ControllerInterface::Shutdown()
// nothing needed
#endif
#ifdef CIFACE_USE_OSX
ciface::OSX::DeInit();
ciface::Quartz::DeInit();
#endif
#ifdef CIFACE_USE_ANDROID

View File

@ -1,12 +0,0 @@
// Copyright 2010 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace ciface::OSX
{
void Init();
void DeInit();
void DeviceElementDebugPrint(const void*, void*);
} // namespace ciface::OSX

View File

@ -1,222 +0,0 @@
// Copyright 2013 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControllerInterface/OSX/OSX.h"
#include <thread>
#include <Cocoa/Cocoa.h>
#include <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h"
#include "InputCommon/ControllerInterface/OSX/RunLoopStopper.h"
namespace ciface::OSX
{
constexpr CFTimeInterval FOREVER = 1e20;
static std::thread s_hotplug_thread;
static RunLoopStopper s_stopper;
static IOHIDManagerRef HIDManager = nullptr;
static CFStringRef OurRunLoop = CFSTR("DolphinOSXInput");
void DeviceElementDebugPrint(const void* value, void* context)
{
IOHIDElementRef e = (IOHIDElementRef)value;
bool recurse = false;
if (context)
recurse = *(bool*)context;
std::string type = "";
switch (IOHIDElementGetType(e))
{
case kIOHIDElementTypeInput_Axis:
type = "axis";
break;
case kIOHIDElementTypeInput_Button:
type = "button";
break;
case kIOHIDElementTypeInput_Misc:
type = "misc";
break;
case kIOHIDElementTypeInput_ScanCodes:
type = "scancodes";
break;
case kIOHIDElementTypeOutput:
type = "output";
break;
case kIOHIDElementTypeFeature:
type = "feature";
break;
case kIOHIDElementTypeCollection:
type = "collection";
break;
}
std::string c_type = "";
if (type == "collection")
{
switch (IOHIDElementGetCollectionType(e))
{
case kIOHIDElementCollectionTypePhysical:
c_type = "physical";
break;
case kIOHIDElementCollectionTypeApplication:
c_type = "application";
break;
case kIOHIDElementCollectionTypeLogical:
c_type = "logical";
break;
case kIOHIDElementCollectionTypeReport:
c_type = "report";
break;
case kIOHIDElementCollectionTypeNamedArray:
c_type = "namedArray";
break;
case kIOHIDElementCollectionTypeUsageSwitch:
c_type = "usageSwitch";
break;
case kIOHIDElementCollectionTypeUsageModifier:
c_type = "usageModifier";
break;
}
}
c_type.append(" ");
NSLog(@"%s%s%spage: 0x%x usage: 0x%x name: %@ "
"lmin: %ld lmax: %ld pmin: %ld pmax: %ld",
type.c_str(), type == "collection" ? ":" : "", type == "collection" ? c_type.c_str() : " ",
IOHIDElementGetUsagePage(e), IOHIDElementGetUsage(e),
IOHIDElementGetName(e), // usually just nullptr
IOHIDElementGetLogicalMin(e), IOHIDElementGetLogicalMax(e), IOHIDElementGetPhysicalMin(e),
IOHIDElementGetPhysicalMax(e));
if ((type == "collection") && recurse)
{
CFArrayRef elements = IOHIDElementGetChildren(e);
CFRange range = {0, CFArrayGetCount(elements)};
// this leaks...but it's just debug code, right? :D
CFArrayApplyFunction(elements, range, DeviceElementDebugPrint, nullptr);
}
}
static void DeviceDebugPrint(IOHIDDeviceRef device)
{
#if 0
#define shortlog(x) NSLog(@"%s: %@", x, IOHIDDeviceGetProperty(device, CFSTR(x)));
NSLog(@"-------------------------");
NSLog(@"Got Device: %@",
IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)));
shortlog(kIOHIDTransportKey)
shortlog(kIOHIDVendorIDKey)
shortlog(kIOHIDVendorIDSourceKey)
shortlog(kIOHIDProductIDKey)
shortlog(kIOHIDVersionNumberKey)
shortlog(kIOHIDManufacturerKey)
shortlog(kIOHIDProductKey)
shortlog(kIOHIDSerialNumberKey)
shortlog(kIOHIDCountryCodeKey)
shortlog(kIOHIDLocationIDKey)
shortlog(kIOHIDDeviceUsageKey)
shortlog(kIOHIDDeviceUsagePageKey)
shortlog(kIOHIDDeviceUsagePairsKey)
shortlog(kIOHIDPrimaryUsageKey)
shortlog(kIOHIDPrimaryUsagePageKey)
shortlog(kIOHIDMaxInputReportSizeKey)
shortlog(kIOHIDMaxOutputReportSizeKey)
shortlog(kIOHIDMaxFeatureReportSizeKey)
shortlog(kIOHIDReportIntervalKey)
shortlog(kIOHIDReportDescriptorKey)
#endif
}
static std::string GetDeviceRefName(IOHIDDeviceRef inIOHIDDeviceRef)
{
const NSString* name = reinterpret_cast<const NSString*>(
IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey)));
return (name != nullptr) ? std::string(StripWhitespace([name UTF8String])) : "Unknown device";
}
static void DeviceRemovalCallback(void* inContext, IOReturn inResult, void* inSender,
IOHIDDeviceRef inIOHIDDeviceRef)
{
g_controller_interface.RemoveDevice([&inIOHIDDeviceRef](const auto* device) {
const Joystick* joystick = dynamic_cast<const Joystick*>(device);
if (joystick && joystick->IsSameDevice(inIOHIDDeviceRef))
return true;
return false;
});
}
static void DeviceMatchingCallback(void* inContext, IOReturn inResult, void* inSender,
IOHIDDeviceRef inIOHIDDeviceRef)
{
DeviceDebugPrint(inIOHIDDeviceRef);
std::string name = GetDeviceRefName(inIOHIDDeviceRef);
// Add a device if it's of a type we want
if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick) ||
IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad) ||
IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop,
kHIDUsage_GD_MultiAxisController))
{
g_controller_interface.AddDevice(std::make_shared<Joystick>(inIOHIDDeviceRef, name));
}
}
void Init()
{
HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (!HIDManager)
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to create HID Manager reference");
IOHIDManagerSetDeviceMatching(HIDManager, nullptr);
if (IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to open HID Manager");
// Callbacks for acquisition or loss of a matching device
IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, DeviceMatchingCallback, nullptr);
IOHIDManagerRegisterDeviceRemovalCallback(HIDManager, DeviceRemovalCallback, nullptr);
// Match devices that are plugged in right now
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop);
while (CFRunLoopRunInMode(OurRunLoop, 0, TRUE) == kCFRunLoopRunHandledSource)
{
};
IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop);
// Enable hotplugging
s_hotplug_thread = std::thread([] {
Common::SetCurrentThreadName("IOHIDManager Hotplug Thread");
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "IOHIDManager hotplug thread started");
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop);
s_stopper.AddToRunLoop(CFRunLoopGetCurrent(), OurRunLoop);
CFRunLoopRunInMode(OurRunLoop, FOREVER, FALSE);
s_stopper.RemoveFromRunLoop(CFRunLoopGetCurrent(), OurRunLoop);
IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop);
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "IOHIDManager hotplug thread stopped");
});
}
void DeInit()
{
if (HIDManager)
{
s_stopper.Signal();
s_hotplug_thread.join();
// This closes all devices as well
IOHIDManagerClose(HIDManager, kIOHIDOptionsTypeNone);
CFRelease(HIDManager);
HIDManager = nullptr;
}
}
} // namespace ciface::OSX

View File

@ -1,90 +0,0 @@
// Copyright 2010 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <set>
#include <IOKit/hid/IOHIDLib.h>
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"
namespace ciface::OSX
{
class Joystick : public ForceFeedback::ForceFeedbackDevice
{
private:
class Button : public Input
{
public:
Button(IOHIDElementRef element, IOHIDDeviceRef device) : m_element(element), m_device(device) {}
std::string GetName() const override;
ControlState GetState() const override;
private:
const IOHIDElementRef m_element;
const IOHIDDeviceRef m_device;
};
class Axis : public Input
{
public:
enum direction
{
positive = 0,
negative
};
Axis(IOHIDElementRef element, IOHIDDeviceRef device, direction dir);
std::string GetName() const override;
ControlState GetState() const override;
private:
const IOHIDElementRef m_element;
const IOHIDDeviceRef m_device;
std::string m_name;
const direction m_direction;
float m_neutral;
float m_scale;
};
class Hat : public Input
{
public:
enum direction
{
up = 0,
right,
down,
left
};
Hat(IOHIDElementRef element, IOHIDDeviceRef device, direction dir);
std::string GetName() const override;
ControlState GetState() const override;
private:
const IOHIDElementRef m_element;
const IOHIDDeviceRef m_device;
const char* m_name;
const direction m_direction;
};
public:
Joystick(IOHIDDeviceRef device, std::string name);
~Joystick();
std::string GetName() const override;
std::string GetSource() const override;
bool IsSameDevice(const IOHIDDeviceRef) const;
private:
const IOHIDDeviceRef m_device;
const std::string m_device_name;
void AddElements(CFArrayRef elements, std::set<IOHIDElementCookie>& cookies);
ForceFeedback::FFDeviceAdapterReference m_ff_device;
};
} // namespace ciface::OSX

View File

@ -1,342 +0,0 @@
// Copyright 2013 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h"
#include <algorithm>
#include <sstream>
#include <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
namespace ciface::OSX
{
void Joystick::AddElements(CFArrayRef elements, std::set<IOHIDElementCookie>& cookies)
{
for (int i = 0; i < CFArrayGetCount(elements); i++)
{
IOHIDElementRef e = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
const uint32_t type = IOHIDElementGetType(e);
switch (type)
{
case kIOHIDElementTypeCollection:
AddElements(IOHIDElementGetChildren(e), cookies);
continue;
case kIOHIDElementTypeOutput:
continue;
}
IOHIDElementCookie cookie = IOHIDElementGetCookie(e);
// Check for any existing elements with the same cookie
if (cookies.count(cookie) > 0)
continue;
cookies.insert(cookie);
const uint32_t usage = IOHIDElementGetUsage(e);
switch (usage)
{
// Axis
case kHIDUsage_GD_X:
case kHIDUsage_GD_Y:
case kHIDUsage_GD_Z:
case kHIDUsage_GD_Rx:
case kHIDUsage_GD_Ry:
case kHIDUsage_GD_Rz:
case kHIDUsage_GD_Slider:
case kHIDUsage_GD_Dial:
case kHIDUsage_GD_Wheel:
case kHIDUsage_GD_Hatswitch:
// Simulator
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Throttle:
{
if (usage == kHIDUsage_GD_Hatswitch)
{
AddInput(new Hat(e, m_device, Hat::up));
AddInput(new Hat(e, m_device, Hat::right));
AddInput(new Hat(e, m_device, Hat::down));
AddInput(new Hat(e, m_device, Hat::left));
}
else
{
AddAnalogInputs(new Axis(e, m_device, Axis::negative),
new Axis(e, m_device, Axis::positive));
}
break;
}
// Buttons
case kHIDUsage_GD_DPadUp:
case kHIDUsage_GD_DPadDown:
case kHIDUsage_GD_DPadRight:
case kHIDUsage_GD_DPadLeft:
case kHIDUsage_GD_Start:
case kHIDUsage_GD_Select:
case kHIDUsage_GD_SystemMainMenu:
AddInput(new Button(e, m_device));
break;
default:
// Catch any easily identifiable axis and buttons that slipped through
if (type == kIOHIDElementTypeInput_Button)
{
AddInput(new Button(e, m_device));
break;
}
const uint32_t usage_page = IOHIDElementGetUsagePage(e);
if (usage_page == kHIDPage_Button || usage_page == kHIDPage_Consumer)
{
AddInput(new Button(e, m_device));
break;
}
if (type == kIOHIDElementTypeInput_Axis)
{
AddAnalogInputs(new Axis(e, m_device, Axis::negative),
new Axis(e, m_device, Axis::positive));
break;
}
NOTICE_LOG_FMT(CONTROLLERINTERFACE,
"Unknown IOHIDElement, ignoring (Usage: {:x}, Type: {:x})", usage,
IOHIDElementGetType(e));
break;
}
}
}
Joystick::Joystick(IOHIDDeviceRef device, std::string name)
: m_device(device), m_device_name(name), m_ff_device(nullptr)
{
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(m_device, nullptr, kIOHIDOptionsTypeNone);
std::set<IOHIDElementCookie> known_cookies;
AddElements(elements, known_cookies);
// Force Feedback
FFCAPABILITIES ff_caps;
if (SUCCEEDED(
ForceFeedback::FFDeviceAdapter::Create(IOHIDDeviceGetService(m_device), &m_ff_device)) &&
SUCCEEDED(FFDeviceGetForceFeedbackCapabilities(m_ff_device->m_device, &ff_caps)))
{
InitForceFeedback(m_ff_device, ff_caps.numFfAxes);
}
}
Joystick::~Joystick()
{
DeInitForceFeedback();
if (m_ff_device)
m_ff_device->Release();
}
std::string Joystick::GetName() const
{
return m_device_name;
}
std::string Joystick::GetSource() const
{
return "Input";
}
ControlState Joystick::Button::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
return IOHIDValueGetIntegerValue(value);
else
return 0;
}
std::string Joystick::Button::GetName() const
{
std::ostringstream s;
s << IOHIDElementGetUsage(m_element);
return std::string("Button ").append(StripWhitespace(s.str()));
}
Joystick::Axis::Axis(IOHIDElementRef element, IOHIDDeviceRef device, direction dir)
: m_element(element), m_device(device), m_direction(dir)
{
// Need to parse the element a bit first
std::string description("unk");
int const usage = IOHIDElementGetUsage(m_element);
switch (usage)
{
case kHIDUsage_GD_X:
description = "X";
break;
case kHIDUsage_GD_Y:
description = "Y";
break;
case kHIDUsage_GD_Z:
description = "Z";
break;
case kHIDUsage_GD_Rx:
description = "Rx";
break;
case kHIDUsage_GD_Ry:
description = "Ry";
break;
case kHIDUsage_GD_Rz:
description = "Rz";
break;
case kHIDUsage_GD_Wheel:
description = "Wheel";
break;
case kHIDUsage_Csmr_ACPan:
description = "Pan";
break;
default:
{
IOHIDElementCookie elementCookie = IOHIDElementGetCookie(m_element);
// This axis isn't a 'well-known' one so cook a descriptive and uniquely
// identifiable name. macOS provides a 'cookie' for each element that
// will persist between sessions and identify the same physical controller
// element so we can use that as a component of the axis name
std::ostringstream s;
s << "CK-";
s << elementCookie;
description = StripWhitespace(s.str());
break;
}
}
m_name = std::string("Axis ") + description;
m_name.append((m_direction == positive) ? "+" : "-");
m_neutral = (IOHIDElementGetLogicalMax(m_element) + IOHIDElementGetLogicalMin(m_element)) / 2.;
m_scale = 1 / fabs(IOHIDElementGetLogicalMax(m_element) - m_neutral);
}
ControlState Joystick::Axis::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
{
// IOHIDValueGetIntegerValue() crashes when trying
// to convert unusually large element values.
if (IOHIDValueGetLength(value) > 2)
return 0;
float position = IOHIDValueGetIntegerValue(value);
if (m_direction == positive && position > m_neutral)
return (position - m_neutral) * m_scale;
if (m_direction == negative && position < m_neutral)
return (m_neutral - position) * m_scale;
}
return 0;
}
std::string Joystick::Axis::GetName() const
{
return m_name;
}
Joystick::Hat::Hat(IOHIDElementRef element, IOHIDDeviceRef device, direction dir)
: m_element(element), m_device(device), m_direction(dir)
{
switch (dir)
{
case up:
m_name = "Up";
break;
case right:
m_name = "Right";
break;
case down:
m_name = "Down";
break;
case left:
m_name = "Left";
break;
default:
m_name = "unk";
}
}
ControlState Joystick::Hat::GetState() const
{
IOHIDValueRef value;
if (IOHIDDeviceGetValue(m_device, m_element, &value) == kIOReturnSuccess)
{
int position = IOHIDValueGetIntegerValue(value);
int min = IOHIDElementGetLogicalMin(m_element);
int max = IOHIDElementGetLogicalMax(m_element);
// if the position is outside the min or max, don't register it as a valid button press
if (position < min || position > max)
{
return 0;
}
// normalize the position so that its lowest value is 0
position -= min;
switch (position)
{
case 0:
if (m_direction == up)
return 1;
break;
case 1:
if (m_direction == up || m_direction == right)
return 1;
break;
case 2:
if (m_direction == right)
return 1;
break;
case 3:
if (m_direction == right || m_direction == down)
return 1;
break;
case 4:
if (m_direction == down)
return 1;
break;
case 5:
if (m_direction == down || m_direction == left)
return 1;
break;
case 6:
if (m_direction == left)
return 1;
break;
case 7:
if (m_direction == left || m_direction == up)
return 1;
break;
};
}
return 0;
}
std::string Joystick::Hat::GetName() const
{
return m_name;
}
bool Joystick::IsSameDevice(const IOHIDDeviceRef other_device) const
{
return m_device == other_device;
}
} // namespace ciface::OSX

View File

@ -1,40 +0,0 @@
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Foundation/Foundation.h>
namespace ciface::OSX
{
class RunLoopStopper
{
CFRunLoopSourceRef m_source;
CFRunLoopRef m_runloop = nullptr;
public:
RunLoopStopper()
{
CFRunLoopSourceContext ctx = {.version = 0,
.perform = [](void*) { CFRunLoopStop(CFRunLoopGetCurrent()); }};
m_source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &ctx);
}
~RunLoopStopper() { CFRelease(m_source); }
void Signal()
{
CFRunLoopSourceSignal(m_source);
if (m_runloop != nullptr)
CFRunLoopWakeUp(m_runloop);
}
void AddToRunLoop(CFRunLoopRef runloop, CFStringRef mode)
{
m_runloop = runloop;
CFRunLoopAddSource(runloop, m_source, mode);
}
void RemoveFromRunLoop(CFRunLoopRef runloop, CFStringRef mode)
{
m_runloop = nullptr;
CFRunLoopRemoveSource(runloop, m_source, mode);
}
};
} // namespace ciface::OSX