#include "driverkit.hpp"
#include <exception>
template<typename T>
int send_key(T& keyboard, struct DKEvent* e) {
if(e->value == 1) keyboard.keys.insert(e->code);
else if(e->value == 0) keyboard.keys.erase(e->code);
else return 1;
#ifdef USE_KEXT
return pqrs::karabiner_virtual_hid_device_methods::post_keyboard_input_report(connect, keyboard);
#else
client->async_post_report(keyboard);
return 0;
#endif
}
#ifdef USE_KEXT
int init_sink() {
kern_return_t kr;
connect = IO_OBJECT_NULL;
service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceNameMatching(pqrs::karabiner_virtual_hid_device::get_virtual_hid_root_name()));
if (!service) {
print_iokit_error("IOServiceGetMatchingService");
return 1;
}
kr = IOServiceOpen(service, mach_task_self(), kIOHIDServerConnectType, &connect);
if (kr != KERN_SUCCESS) {
print_iokit_error("IOServiceOpen", kr);
return kr;
}
{
pqrs::karabiner_virtual_hid_device::properties::keyboard_initialization properties;
kr = pqrs::karabiner_virtual_hid_device_methods::initialize_virtual_hid_keyboard(connect, properties);
if (kr != KERN_SUCCESS) {
print_iokit_error("initialize_virtual_hid_keyboard", kr);
return 1;
}
while (true) {
bool ready;
kr = pqrs::karabiner_virtual_hid_device_methods::is_virtual_hid_keyboard_ready(connect, ready);
if (kr != KERN_SUCCESS) {
print_iokit_error("is_virtual_hid_keyboard_ready", kr);
return kr;
} else {
if (ready)
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
{
pqrs::karabiner_virtual_hid_device::properties::keyboard_initialization properties;
properties.country_code = 33;
kr = pqrs::karabiner_virtual_hid_device_methods::initialize_virtual_hid_keyboard(connect, properties);
if (kr != KERN_SUCCESS) {
print_iokit_error("initialize_virtual_hid_keyboard", kr);
return kr;
}
}
return 0;
}
int exit_sink() {
int retval = 0;
kern_return_t kr = pqrs::karabiner_virtual_hid_device_methods::reset_virtual_hid_keyboard(connect);
if (kr != KERN_SUCCESS) {
print_iokit_error("reset_virtual_hid_keyboard", kr);
retval = 1;
}
if (connect) {
kr = IOServiceClose(connect);
if(kr != KERN_SUCCESS) {
print_iokit_error("IOServiceClose", kr);
retval = 1;
}
connect = IO_OBJECT_NULL;
}
if (service) {
kr = IOObjectRelease(service);
if(kr != KERN_SUCCESS) {
print_iokit_error("IOObjectRelease", kr);
retval = 1;
}
service = IO_OBJECT_NULL;
}
return retval;
}
#else
int init_sink() {
try {
pqrs::dispatcher::extra::initialize_shared_dispatcher();
client = new pqrs::karabiner::driverkit::virtual_hid_device_service::client();
auto copy = client;
client->connected.connect([copy] {
std::cout << "connected" << std::endl;
pqrs::karabiner::driverkit::virtual_hid_device_service::virtual_hid_keyboard_parameters parameters;
parameters.set_country_code(pqrs::hid::country_code::us);
copy->async_virtual_hid_keyboard_initialize(parameters);
copy->async_virtual_hid_pointing_initialize();
});
client->virtual_hid_keyboard_ready.connect([](auto&& ready) {
std::cout << "virtual_hid_keyboard_ready " << ready << std::endl;
sink_ready.store(ready, std::memory_order_release);
});
client->virtual_hid_pointing_ready.connect([](auto&& ready) {
std::cout << "virtual_hid_pointing_ready " << ready << std::endl;
pointing_sink_ready.store(ready, std::memory_order_release);
});
client->closed.connect([] {
std::cout << "closed" << std::endl;
sink_ready.store(false, std::memory_order_release);
pointing_sink_ready.store(false, std::memory_order_release);
});
client->connect_failed.connect([](auto&& error_code) {
std::cout << "connect_failed " << error_code << std::endl;
sink_ready.store(false, std::memory_order_release);
pointing_sink_ready.store(false, std::memory_order_release);
});
client->error_occurred.connect([](auto&& error_code) {
std::cout << "error_occurred " << error_code << std::endl;
sink_ready.store(false, std::memory_order_release);
pointing_sink_ready.store(false, std::memory_order_release);
});
client->driver_activated.connect([](auto&& driver_activated) {
static std::optional<bool> previous_value;
if (previous_value != driver_activated) {
std::cout << "driver activated: " << std::boolalpha << driver_activated << std::endl;
previous_value = driver_activated;
}
});
client->driver_connected.connect([](auto&& driver_connected) {
static std::optional<bool> previous_value;
if (previous_value != driver_connected) {
std::cout << "driver connected: " << driver_connected << std::endl;
previous_value = driver_connected;
}
});
client->driver_version_mismatched.connect([](auto&& driver_version_mismatched) {
static std::optional<bool> previous_value;
if (previous_value != driver_version_mismatched) {
std::cout << "driver version matched: " << !driver_version_mismatched << std::endl;
previous_value = driver_version_mismatched;
}
});
client->async_start();
return 0;
} catch (const std::exception& e) {
std::cerr << "Exception in init_sink: " << e.what() << std::endl;
return 1;
}
}
int exit_sink() {
if (client) {
delete client;
client = nullptr;
}
pqrs::dispatcher::extra::terminate_shared_dispatcher();
return 0;
}
#endif
void fire_listener_thread() {
if (!listener_thread.joinable())
listener_thread = std::thread{
[&]() {
listener_loop = CFRunLoopGetCurrent();
capture_registered_devices();
CFRunLoopRun();
} };
}
void input_callback(void* context, IOReturn result, void* sender, IOHIDValueRef value) {
struct DKEvent e;
IOHIDElementRef element = IOHIDValueGetElement(value);
e.value = IOHIDValueGetIntegerValue(value);
e.page = IOHIDElementGetUsagePage(element);
e.code = IOHIDElementGetUsage(element);
e.device_hash = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(context));
write(fd[1], &e, sizeof(struct DKEvent));
}
void device_connected_callback(void* context, io_iterator_t iter) {
uint64_t device_hash = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(context));
for (mach_port_t curr = IOIteratorNext(iter); curr; curr = IOIteratorNext(iter)) {
uint64_t curr_hash = hash_device(curr);
if ( curr_hash == device_hash )
capture_device(IOHIDDeviceCreate(kCFAllocatorDefault, curr), curr_hash);
IOObjectRelease(curr);
}
}
void device_connected_vidpid_callback(void* context, io_iterator_t iter) {
uint64_t vidpid = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(context));
uint32_t vid = static_cast<uint32_t>(vidpid >> 32);
uint32_t pid = static_cast<uint32_t>(vidpid & 0xFFFFFFFF);
CFStringRef karabiner = from_cstr("Karabiner");
for (mach_port_t curr = IOIteratorNext(iter); curr; curr = IOIteratorNext(iter)) {
uint32_t curr_vid = get_number_property(curr, kIOHIDVendorIDKey);
uint32_t curr_pid = get_number_property(curr, kIOHIDProductIDKey);
if (curr_vid == vid && curr_pid == pid) {
CFStringRef name = get_product_name_cf(curr);
if (name && !isSubstring(karabiner, name)) {
uint64_t curr_hash = hash_device(curr);
capture_device(IOHIDDeviceCreate(kCFAllocatorDefault, curr), curr_hash);
}
if (name) CFRelease(name);
}
IOObjectRelease(curr);
}
CFRelease(karabiner);
}
void close_registered_devices() {
for(auto& [hash, device_ref] : opened_device_refs) {
kern_return_t kr = IOHIDDeviceClose(device_ref, kIOHIDOptionsTypeSeizeDevice);
if(kr != KERN_SUCCESS) { print_iokit_error("IOHIDDeviceClose", kr); }
CFRelease(device_ref);
}
opened_device_refs.clear();
}
void init_keyboards_dictionary() {
if( matching_dictionary ) return;
matching_dictionary = IOServiceMatching(kIOHIDDeviceKey);
UInt32 generic_desktop = kHIDPage_GenericDesktop;
UInt32 gd_keyboard = kHIDUsage_GD_Keyboard;
CFNumberRef page_number = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, &generic_desktop );
CFNumberRef usage_number = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, &gd_keyboard );
CFDictionarySetValue(matching_dictionary, CFSTR(kIOHIDDeviceUsagePageKey), page_number);
CFDictionarySetValue(matching_dictionary, CFSTR(kIOHIDDeviceUsageKey), usage_number);
release_strings(page_number, usage_number);
}
io_iterator_t get_keyboards_iterator() {
io_iterator_t iter = IO_OBJECT_NULL;
CFRetain(matching_dictionary);
IOServiceGetMatchingServices(kIOMainPortDefault, matching_dictionary, &iter);
return iter;
}
template <typename Func>
bool consume_devices(Func consume) {
init_keyboards_dictionary();
io_iterator_t iter = get_keyboards_iterator();
if(iter == IO_OBJECT_NULL) return false;
bool result = false;
for(mach_port_t curr = IOIteratorNext(iter); curr; curr = IOIteratorNext(iter))
result = consume(curr) || result;
IOObjectRelease(iter);
return result;
}
void subscribe_to_notification(const char* notification_type, void* cb_arg, callback_type callback) {
io_iterator_t iter = IO_OBJECT_NULL;
CFRetain(matching_dictionary);
kern_return_t kr = IOServiceAddMatchingNotification(notification_port, notification_type,
matching_dictionary, callback, cb_arg, &iter);
if (kr != KERN_SUCCESS) { print_iokit_error(notification_type, kr); return; }
for (io_object_t obj = IOIteratorNext(iter); obj; obj = IOIteratorNext(iter))
IOObjectRelease(obj);
}
bool capture_device(IOHIDDeviceRef device_ref, uint64_t device_hash) {
kern_return_t kr = IOHIDDeviceOpen(device_ref, kIOHIDOptionsTypeSeizeDevice);
if(kr != kIOReturnSuccess) {
print_iokit_error("IOHIDDeviceOpen", kr, CFStringToStdString(get_device_name(device_ref)));
CFRelease(device_ref);
return false;
}
void* ctx = reinterpret_cast<void*>(static_cast<uintptr_t>(device_hash));
IOHIDDeviceRegisterInputValueCallback(device_ref, input_callback, ctx);
IOHIDDeviceScheduleWithRunLoop(device_ref, listener_loop, kCFRunLoopDefaultMode);
opened_device_refs[device_hash] = device_ref;
return true;
}
bool capture_registered_devices() {
CFRunLoopAddSource(listener_loop, IONotificationPortGetRunLoopSource(notification_port), kCFRunLoopDefaultMode);
bool any_captured = consume_devices([](mach_port_t c) {
uint64_t device_hash = hash_device(c);
if ( registered_devices_hashes.find(device_hash) != registered_devices_hashes.end() ) {
return capture_device(IOHIDDeviceCreate(kCFAllocatorDefault, c), device_hash);
}
uint32_t vid = get_number_property(c, kIOHIDVendorIDKey);
uint32_t pid = get_number_property(c, kIOHIDProductIDKey);
uint64_t vidpid = (static_cast<uint64_t>(vid) << 32) | pid;
if ( registered_device_vidpids.find(vidpid) != registered_device_vidpids.end() ) {
CFStringRef name = get_product_name_cf(c);
CFStringRef karabiner = from_cstr("Karabiner");
bool is_karabiner = name && isSubstring(karabiner, name);
release_strings(karabiner, name);
if (!is_karabiner) {
return capture_device(IOHIDDeviceCreate(kCFAllocatorDefault, c), device_hash);
}
}
return false;
});
for (auto hash : registered_devices_hashes) {
void* dev_hash = reinterpret_cast<void*>(static_cast<uintptr_t>(hash));
subscribe_to_notification(kIOMatchedNotification, dev_hash, device_connected_callback);
}
for (auto vidpid : registered_device_vidpids) {
void* ctx = reinterpret_cast<void*>(static_cast<uintptr_t>(vidpid));
subscribe_to_notification(kIOMatchedNotification, ctx, device_connected_vidpid_callback);
}
return any_captured;
}
IOHIDDeviceRef get_device_by_hash(uint64_t device_hash) {
init_keyboards_dictionary();
io_iterator_t iter = get_keyboards_iterator();
if(iter == IO_OBJECT_NULL) return nullptr;
for(mach_port_t curr = IOIteratorNext(iter); curr; curr = IOIteratorNext(iter))
if ( hash_device(curr) == device_hash )
return IOHIDDeviceCreate(kCFAllocatorDefault, curr);
IOObjectRelease(iter);
return nullptr;
}
uint64_t fnv_hash(const std::string& key) {
const uint64_t FNV_OFFSET = 14695981039346656037ull;
const uint64_t FNV_PRIME = 1099511628211ull;
uint64_t hash = FNV_OFFSET;
for (unsigned char c : key) {
hash ^= c;
hash *= FNV_PRIME;
}
return hash;
}
uint64_t hash_device(mach_port_t device) {
std::string product_key = get_product_name(device);
uint32_t vendor_id = get_number_property(device, kIOHIDVendorIDKey);
uint32_t product_id = get_number_property(device, kIOHIDProductIDKey);
std::string key = std::to_string(vendor_id) + ":" + std::to_string(product_id) + ":" + product_key;
return fnv_hash(key);
}
extern "C" {
bool register_device(const char* product_key) {
return consume_devices([product_key](mach_port_t current_device) {
CFStringRef product_key_cfstring = product_key ? from_cstr(product_key) : from_cstr("");
CFStringRef karabiner = from_cstr("Karabiner"); CFStringRef current_product_key = get_product_name_cf(current_device);
if(!current_product_key || isSubstring(karabiner, current_product_key) ) {
release_strings(karabiner, current_product_key, product_key_cfstring);
return false;
}
bool registered = false;
if(!product_key || (CFStringCompare(current_product_key, product_key_cfstring, 0) == kCFCompareEqualTo)) {
registered_devices_hashes.insert(hash_device(current_device));
registered = true;
}
release_strings(karabiner, current_product_key, product_key_cfstring);
return registered;
});
}
void list_keyboards() { consume_devices([](mach_port_t c) { std::cout << get_product_name(c) << std::endl; return true;});
}
void list_keyboards_with_ids() {
consume_devices([](mach_port_t current_device) {
std::printf("vendor id: 0x%04X\t product id: 0x%04X\t Product key (name): %s hash: %llu\n",
get_number_property(current_device, kIOHIDVendorIDKey),
get_number_property(current_device, kIOHIDProductIDKey),
get_product_name(current_device).c_str(),
hash_device(current_device));
return true;
});
}
#ifdef USE_KEXT
bool driver_activated() {
return true;
}
#else
bool driver_activated() {
auto service = IOServiceGetMatchingService(type_safe::get(pqrs::osx::iokit_mach_port::null),
IOServiceNameMatching("org_pqrs_Karabiner_DriverKit_VirtualHIDDeviceRoot"));
if (!service) return false;
IOObjectRelease(service);
return true;
}
#endif
int wait_key(struct DKEvent* e) { return read(fd[0], e, sizeof(struct DKEvent)) == sizeof(struct DKEvent); }
bool device_matches(const char* product) {
if (!product) return true;
init_keyboards_dictionary();
io_iterator_t iter = get_keyboards_iterator();
CFStringRef device = from_cstr(product);
for(mach_port_t curr = IOIteratorNext(iter); curr; curr = IOIteratorNext(iter)) {
CFStringRef current_device = get_product_name_cf(curr);
if( current_device == NULL || CFStringCompare(current_device, device, 0) != kCFCompareEqualTo ) {
if (current_device) CFRelease(current_device);
continue;
} else {
release_strings(device, current_device);
return true;
}
}
CFRelease(device);
IOObjectRelease(iter);
return false;
}
bool register_device_vidpid(uint32_t vendor_id, uint32_t product_id) {
uint64_t vidpid = (static_cast<uint64_t>(vendor_id) << 32) | product_id;
registered_device_vidpids.insert(vidpid);
return consume_devices([vendor_id, product_id](mach_port_t current_device) {
CFStringRef name = get_product_name_cf(current_device);
CFStringRef karabiner = from_cstr("Karabiner");
if (name && isSubstring(karabiner, name)) {
release_strings(karabiner, name);
return false;
}
release_strings(karabiner, name);
return get_number_property(current_device, kIOHIDVendorIDKey) == vendor_id
&& get_number_property(current_device, kIOHIDProductIDKey) == product_id;
});
}
int grab() {
if (!registered_devices_hashes.size() && !registered_device_vidpids.size()) {
std::cout << "At least one device has to be registered via register_device()" << std::endl;
return 1;
}
if (pipe(fd) == -1) { std::cerr << "pipe error: " << errno << std::endl; return errno; }
int sink_err = init_sink();
if (sink_err) return sink_err;
fire_listener_thread();
return 0;
}
void release() {
std::cout << "release called" << std::endl;
if(listener_thread.joinable()) { CFRunLoopStop(listener_loop); listener_thread.join(); }
close_registered_devices();
keyboard.keys.clear();
close(fd[0]); close(fd[1]);
exit_sink();
}
int send_key(struct DKEvent* e) {
#ifdef USE_KEXT
auto usage_page = pqrs::karabiner_virtual_hid_device::usage_page(e->page);
if(usage_page == pqrs::karabiner_virtual_hid_device::usage_page::keyboard_or_keypad)
return send_key(keyboard, e);
else if(usage_page == pqrs::karabiner_virtual_hid_device::usage_page::apple_vendor_top_case)
return send_key(top_case, e);
else if(usage_page == pqrs::karabiner_virtual_hid_device::usage_page::apple_vendor_keyboard)
return send_key(apple_keyboard, e);
else if(usage_page == pqrs::karabiner_virtual_hid_device::usage_page::consumer)
return send_key(consumer, e);
else if(usage_page == pqrs::karabiner_virtual_hid_device::usage_page::generic_desktop)
return send_key(generic_desktop, e);
else
return 1;
#else
if(!sink_ready.load(std::memory_order_acquire)) return 2;
auto usage_page = pqrs::hid::usage_page::value_t(e->page);
if(usage_page == pqrs::hid::usage_page::keyboard_or_keypad)
return send_key(keyboard, e);
else if(usage_page == pqrs::hid::usage_page::apple_vendor_top_case)
return send_key(top_case, e);
else if(usage_page == pqrs::hid::usage_page::apple_vendor_keyboard)
return send_key(apple_keyboard, e);
else if(usage_page == pqrs::hid::usage_page::consumer)
return send_key(consumer, e);
else if(usage_page == pqrs::hid::usage_page::generic_desktop)
return send_key(generic_desktop, e);
else return 1;
#endif
}
const DeviceData* get_device_list(size_t* array_length) {
static std::vector<std::string> products; static std::vector<DeviceData> devices;
products.clear(); devices.clear();
consume_devices([](mach_port_t current_device) {
DeviceData d;
products.emplace_back(get_product_name(current_device));
d.vendor_id = get_number_property(current_device, kIOHIDVendorIDKey);
d.product_id = get_number_property(current_device, kIOHIDProductIDKey);
devices.push_back({ products.back().c_str(), d.vendor_id, d.product_id });
return true;
});
*array_length = devices.size();
return devices.data();
}
bool is_sink_ready() {
#ifdef USE_KEXT
return true;
#else
return sink_ready.load(std::memory_order_acquire);
#endif
}
void release_input_only() {
#ifndef USE_KEXT
if(listener_thread.joinable()) {
CFRunLoopRemoveSource(listener_loop, IONotificationPortGetRunLoopSource(notification_port), kCFRunLoopDefaultMode);
CFRunLoopStop(listener_loop);
listener_thread.join();
}
close_registered_devices();
keyboard.keys.clear();
close(fd[0]); close(fd[1]);
#endif
}
bool regrab_input() {
#ifdef USE_KEXT
return true;
#else
if (!registered_devices_hashes.size() && !registered_device_vidpids.size()) return false;
if (pipe(fd) == -1) { std::cerr << "pipe error: " << errno << std::endl; return false; }
fire_listener_thread();
return true;
#endif
}
void pointing_button_press(uint8_t button) {
#ifndef USE_KEXT
if (!pointing_sink_ready.load(std::memory_order_acquire)) return;
pointing.buttons.insert(button);
client->async_post_report(pointing);
#endif
}
void pointing_button_release(uint8_t button) {
#ifndef USE_KEXT
if (!pointing_sink_ready.load(std::memory_order_acquire)) return;
pointing.buttons.erase(button);
client->async_post_report(pointing);
#endif
}
void pointing_post_motion(int8_t x, int8_t y, int8_t vertical_wheel, int8_t horizontal_wheel) {
#ifndef USE_KEXT
if (!pointing_sink_ready.load(std::memory_order_acquire)) return;
pointing.x = static_cast<uint8_t>(x);
pointing.y = static_cast<uint8_t>(y);
pointing.vertical_wheel = static_cast<uint8_t>(vertical_wheel);
pointing.horizontal_wheel = static_cast<uint8_t>(horizontal_wheel);
client->async_post_report(pointing);
pointing.x = 0;
pointing.y = 0;
pointing.vertical_wheel = 0;
pointing.horizontal_wheel = 0;
#endif
}
bool is_pointing_ready() {
#ifdef USE_KEXT
return true;
#else
return pointing_sink_ready.load(std::memory_order_acquire);
#endif
}
}
#ifdef BUILD_AS_BINARY
int main() {
list_keyboards();
list_keyboards_with_ids();
std::cout << "test device_matches:" << std::boolalpha << std::endl <<
"device_matches(NULL): " << device_matches(NULL) << std::endl <<
"device_matches(appl): " << device_matches("Apple Internal Keyboard / Trackpad") << std::endl <<
"device_matches(____): " << device_matches("nano") << std::noboolalpha << std::endl;
const char* keeb = "Apple Internal Keyboard / Trackpad";
const char* othr = "DZ60RGB_ANSI";
register_device(othr);
for ( uint64_t hash : registered_devices_hashes )
std::cout << "registered device: " << CFStringToStdString( get_device_name( get_device_by_hash(hash) ) ) <<
std::hex << " hash: " << hash << std::dec << " dev: " << get_device_by_hash(hash) << std::endl;
grab();
listener_thread.join();
release();
return 0;
}
#endif