// gcc LEDs.c -o LEDs -framework IOKit -framework Carbon

#include <stdio.h>
#include <stdlib.h>

#include <IOKit/IOCFPlugin.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDUsageTables.h>

int num_deviceinterfaces;

IOHIDDeviceInterface122 ** * devs;
io_object_t * objs;
int OpenDeviceInterfacesForHIDPages(UInt32 usagePage, UInt32 usage);

IOHIDElementCookie * cookies;
int CookiesForHIDPages(UInt32 usagePage, UInt32 usage);

int main(int argc, char ** argv) {
	int HIDCapsLockAccess = 1;
	if(HIDCapsLockAccess && !OpenDeviceInterfacesForHIDPages(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) {
		HIDCapsLockAccess = 0;
		printf("Couldnt find any keyboards via HID\n");
	}
	if(HIDCapsLockAccess && !CookiesForHIDPages(kHIDPage_LEDs, kHIDUsage_LED_CapsLock)) {
		HIDCapsLockAccess = 0;
		printf("Couldnt find any accessable LEDs on any HID keyboards\n");
	}
	if(HIDCapsLockAccess && !num_deviceinterfaces) {
		HIDCapsLockAccess = 0;
		printf("You dont have any keyboards attached :/\n");
	}
	if(HIDCapsLockAccess) {
		// For now, we just use the first keyboard
		// who the hell uses two keyboards anyway?
		
		printf("There is %d keyboards\n", num_deviceinterfaces);
		
		if(cookies[0] == (IOHIDElementCookie)-1) {
			HIDCapsLockAccess = 0;
			printf("Couldnt get the LED cookie for HID keyboard\n");
		}
		else {
			IOHIDEventStruct ev_str = (IOHIDEventStruct){kIOHIDElementTypeOutput, cookies[0], 0, 0, 0, 0};
			
			(*devs[0])->open(devs[0], 0);
			while(1) {
				ev_str.value = !ev_str.value;
				(*devs[0])->setElementValue(devs[0], cookies[0], &ev_str, 0, NULL, NULL, NULL);
				usleep(50000);
			}
		}
	}
}

int OpenDeviceInterfacesForHIDPages(UInt32 usagePage, UInt32 usage) {
	kern_return_t err;
	
	CFMutableDictionaryRef hidMatchDict;
	CFNumberRef number;
	
	io_iterator_t hid_iterator;
	io_object_t object;
	
	IOCFPlugInInterface ** plugInInterface;
	SInt32 score;
	
	IOHIDDeviceInterface122 ** dev;
	
	// Find the devices that are from usagePage and of type usage
	// (from IOKit/hid/IOHIDUsageTables.h)
	
	hidMatchDict = IOServiceMatching(kIOHIDDeviceKey);
	
	number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &usagePage);
	CFDictionarySetValue(hidMatchDict, CFSTR(kIOHIDPrimaryUsagePageKey), number);
	CFRelease (number);
	
	number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &usage);
	CFDictionarySetValue(hidMatchDict, CFSTR(kIOHIDPrimaryUsageKey), number);
	CFRelease (number);
	
	err = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDict, &hid_iterator);
	if(err != kIOReturnSuccess) {
		printf("Could not create HID object iterator\n");
		return 0;
	}
	if(!hid_iterator) {
		printf("No matching devices\n");
		return 0;
	}
	
	// Now iterate the objects
	
	num_deviceinterfaces = 0;
	
	while((object = IOIteratorNext(hid_iterator)) != 0) {
		err = IOCreatePlugInInterfaceForService(object, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
		if(err == kIOReturnSuccess) {
			err = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (void **)&dev);
			if(err != kIOReturnSuccess)
				printf("Could not query the interface :/\n");
			
			num_deviceinterfaces++;
			if(num_deviceinterfaces) {
				devs = realloc(devs, num_deviceinterfaces*sizeof(IOHIDDeviceInterface122 **));
				objs = realloc(objs, num_deviceinterfaces*sizeof(io_object_t));
			}
			else {
				devs = malloc(sizeof(IOHIDDeviceInterface122 **));
				objs = malloc(sizeof(io_object_t));
			}
			
			devs[num_deviceinterfaces-1] = dev;
			objs[num_deviceinterfaces-1] = object;
		}
		else {
			printf("Error in IOCreatePlugInInterfaceForService\n");
		}
		
//		IOObjectRelease(object);
	}
	IOObjectRelease(hid_iterator);
	
	return 1;
}

int CookiesForHIDPages(UInt32 usagePage, UInt32 usage) {
	int ret;
	int i;
	
	CFDictionaryRef dict;
	CFNumberRef num;
	CFArrayRef outArray;
	
	CFStringRef keys[2] = {CFSTR("UsagePage"), CFSTR("Usage")};
	CFNumberRef * values = malloc(2*sizeof(CFNumberRef));
	
	ret = 1;
	
	cookies = malloc(num_deviceinterfaces*sizeof(IOHIDElementCookie));
	for(i = 0; i < num_deviceinterfaces; i++) {
		values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
		values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
		dict = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		CFRelease(values[0]);
		CFRelease(values[1]);
		
		//CFShow(dict);
		
		(*devs[0])->copyMatchingElements(devs[0], dict, &outArray);
		
		if(outArray == NULL) {
			printf("Could not find matching elements for useage page and usage\n");
			cookies[i] = (IOHIDElementCookie *)-1;
			ret = 0;
			CFRelease(dict);
			continue;
		}
		CFRelease(dict);
		
		dict = (CFDictionaryRef)CFArrayGetValueAtIndex(outArray, 0);
		if(CFArrayGetCount(outArray) &&
			CFDictionaryContainsKey(dict, CFSTR("ElementCookie"))) {
			num = CFDictionaryGetValue(dict, CFSTR("ElementCookie"));
			
			CFNumberGetValue(num, kCFNumberSInt32Type, (void*)&cookies[i]);
		}
		else {
			printf("Could not find cookie for useage page and usage\n");
			cookies[i] = (IOHIDElementCookie *)-1;
			ret = 0;
		}
		CFRelease(dict);
	}
	
	return ret;
}
