#include <unistd.h>
#include <atomic>
#include <thread>
#include <iostream>
#include <mach/mach_error.h>
#include <AvailabilityMacros.h>
#include <filesystem>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hidsystem/IOHIDShared.h>
#include <set>
#include <unordered_map>
#if (MAC_OS_X_VERSION_MIN_REQUIRED < 120000)
#define kIOMainPortDefault kIOMasterPortDefault
#endif
#ifdef USE_KEXT
#include "karabiner_virtual_hid_device_methods.hpp"
mach_port_t connect;
io_service_t service;
pqrs::karabiner_virtual_hid_device::hid_report::keyboard_input keyboard;
pqrs::karabiner_virtual_hid_device::hid_report::apple_vendor_top_case_input top_case;
pqrs::karabiner_virtual_hid_device::hid_report::apple_vendor_keyboard_input apple_keyboard;
pqrs::karabiner_virtual_hid_device::hid_report::consumer_input consumer;
pqrs::karabiner_virtual_hid_device::hid_report::generic_desktop_input generic_desktop;
#else
#include "virtual_hid_device_driver.hpp"
#include "virtual_hid_device_service.hpp"
#include <pqrs/osx/iokit_types.hpp>
static_assert(
type_safe::get(pqrs::karabiner::driverkit::client_protocol_version::embedded_client_protocol_version) == 7,
"karabiner-driverkit must be audited before changing VirtualHIDDevice protocol versions"
);
pqrs::karabiner::driverkit::virtual_hid_device_service::client* client;
std::atomic<bool> sink_ready{false};
std::atomic<bool> pointing_sink_ready{false};
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::keyboard_input keyboard;
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::apple_vendor_top_case_input top_case;
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::apple_vendor_keyboard_input apple_keyboard;
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::consumer_input consumer;
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::generic_desktop_input generic_desktop;
pqrs::karabiner::driverkit::virtual_hid_device_driver::hid_report::pointing_input pointing;
#endif
IONotificationPortRef notification_port = IONotificationPortCreate(kIOMainPortDefault);
std::thread listener_thread;
CFRunLoopRef listener_loop;
std::set<uint64_t> registered_devices_hashes;
std::set<uint64_t> registered_device_vidpids;
std::unordered_map<uint64_t, IOHIDDeviceRef> opened_device_refs;
int fd[2];
CFMutableDictionaryRef matching_dictionary = NULL;
struct DKEvent {
uint64_t value;
uint32_t page;
uint32_t code;
uint64_t device_hash;
};
struct DeviceData {
const char* product_key;
uint32_t vendor_id;
uint32_t product_id;
};
using callback_type = void(*)(void*, io_iterator_t);
void subscribe_to_notification(const char* notification_type, void* cb_arg, callback_type callback);
void device_connected_callback(void* context, io_iterator_t iter);
void device_connected_vidpid_callback(void* context, io_iterator_t iter);
void fire_listener_thread();
void init_keyboards_dictionary();
void close_registered_devices();
void input_callback(void* context, IOReturn result, void* sender, IOHIDValueRef value);
template <typename Func>
bool consume_devices(Func consume);
bool capture_registered_devices();
bool capture_device(IOHIDDeviceRef device_ref, uint64_t device_hash);
int init_sink();
int exit_sink();
uint64_t hash_device(mach_port_t device);
io_iterator_t get_keyboards_iterator();
IOHIDDeviceRef get_device_by_hash(uint64_t device_hash);
inline void print_iokit_error(const char* fname, int freturn, std::string data = "") {
std::cerr << fname << " error: " << ( freturn ? mach_error_string(freturn) : "" ) << " " << data << std::endl;
}
inline CFStringRef from_cstr( const char* str) {
if (!str) return nullptr;
return CFStringCreateWithCString(kCFAllocatorDefault, str, CFStringGetSystemEncoding());
}
inline CFStringRef get_property(mach_port_t item, const char* property) {
return (CFStringRef) IORegistryEntryCreateCFProperty(item, from_cstr(property), kCFAllocatorDefault, kIOHIDOptionsTypeNone);
}
inline uint32_t get_number_property(mach_port_t item, const char* property) {
uint32_t value = 0;
if (CFTypeRef ref = IORegistryEntryCreateCFProperty(item, from_cstr(property), kCFAllocatorDefault, kIOHIDOptionsTypeNone)) {
if (CFGetTypeID(ref) == CFNumberGetTypeID())
CFNumberGetValue((CFNumberRef)ref, kCFNumberSInt32Type, &value);
CFRelease(ref);
}
return value;
}
inline CFStringRef get_device_name(IOHIDDeviceRef device) {
return (CFStringRef) IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
}
template<typename... Args>
inline void release_strings(Args... strings) {
((strings ? CFRelease(strings) : void()), ...);
}
inline bool isSubstring(CFStringRef subString, CFStringRef mainString) {
if (!subString || !mainString) return false;
return CFStringFind(mainString, subString, kCFCompareCaseInsensitive).location != kCFNotFound;
}
inline std::string CFStringToStdString(CFStringRef cfString) {
if (cfString == nullptr) return std::string();
CFIndex length = CFStringGetLength(cfString);
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
std::string utf8String(maxSize, '\0');
if (CFStringGetCString(cfString, &utf8String[0], maxSize, kCFStringEncodingUTF8)) {
utf8String.resize(strlen( utf8String.c_str()));
return utf8String;
}
return std::string();
}
inline std::string get_product_name(mach_port_t device) {
CFStringRef hid_key = get_property(device, kIOHIDProductKey);
std::string name = CFStringToStdString(hid_key);
if (hid_key) CFRelease(hid_key);
if (name.empty()) {
CFStringRef product_ref = (CFStringRef)IORegistryEntryCreateCFProperty(
device, CFSTR("Product"), kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (product_ref) {
name = CFStringToStdString(product_ref);
CFRelease(product_ref);
}
}
return name;
}
inline CFStringRef get_product_name_cf(mach_port_t device) {
CFStringRef name = get_property(device, kIOHIDProductKey);
if (!name || CFStringGetLength(name) == 0) {
if (name) CFRelease(name);
name = (CFStringRef)IORegistryEntryCreateCFProperty(
device, CFSTR("Product"), kCFAllocatorDefault, kIOHIDOptionsTypeNone);
}
return name;
}
extern "C" {
int grab();
int send_key(struct DKEvent* e);
int wait_key(struct DKEvent* e);
void release();
void list_keyboards();
void list_keyboards_with_ids();
bool device_matches(const char* product);
bool driver_activated();
bool register_device(const char* product_key);
bool register_device_vidpid(uint32_t vendor_id, uint32_t product_id);
bool register_device_hash(uint64_t device_hash) {
registered_devices_hashes.insert(device_hash);
return consume_devices([device_hash](mach_port_t current_device) {
CFStringRef name = get_product_name_cf(current_device);
if( isSubstring(from_cstr("Karabiner"), name) ) {
if (name) CFRelease(name);
return false;
}
if (name) CFRelease(name);
return hash_device(current_device) == device_hash;
});
}
const DeviceData* get_device_list(size_t* array_length);
bool is_sink_ready();
void release_input_only();
bool regrab_input();
void pointing_button_press(uint8_t button);
void pointing_button_release(uint8_t button);
void pointing_post_motion(int8_t x, int8_t y, int8_t vertical_wheel, int8_t horizontal_wheel);
bool is_pointing_ready();
}