Skip to content

Instantly share code, notes, and snippets.

@jaxbot
Last active September 3, 2023 11:37
Show Gist options
  • Save jaxbot/eba94ef8d193a1abe407 to your computer and use it in GitHub Desktop.
Save jaxbot/eba94ef8d193a1abe407 to your computer and use it in GitHub Desktop.
Vim MacBook LEDs
/*
* keyboard_leds.c
* Manipulate keyboard LEDs (capslock and numlock) programmatically.
*
* gcc -Wall -o keyboard_leds keyboard_leds.c -framework IOKit\
* -framework CoreFoundation
*
* Copyright (c) 2007,2008 Amit Singh. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Modified by Jonathan Warner (Jaxbot) on 11/9/2014 to remove inline statements
* and update compile command.
*
* Original source: http://googlemac.blogspot.de/2008/04/manipulating-keyboard-leds-through.html
*/
#define PROGNAME "keyboard_leds"
#define PROGVERS "0.1"
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <sysexits.h>
#include <mach/mach_error.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDUsageTables.h>
static IOHIDElementCookie capslock_cookie = (IOHIDElementCookie)0;
static IOHIDElementCookie numlock_cookie = (IOHIDElementCookie)0;
static int capslock_value = -1;
static int numlock_value = -1;
void usage(void);
io_service_t find_a_keyboard(void);
void find_led_cookies(IOHIDDeviceInterface122** handle);
void create_hid_interface(io_object_t hidDevice,
IOHIDDeviceInterface*** hdi);
int manipulate_led(UInt32 whichLED, UInt32 value);
void
usage(void)
{
fprintf(stderr, "%s (version %s)\n"
"Copyright (c) 2007,2008 Amit Singh. All Rights Reserved.\n"
"Manipulate keyboard LEDs\n\n"
"Usage: %s [OPTIONS...], where OPTIONS is one of the following\n\n"
" -c[1|0], --capslock[=1|=0] get or set (on=1, off=0) caps lock LED\n"
" -h, --help print this help message and exit\n"
" -n[1|0], --numlock[=1|=0] get or set (on=1, off=0) num lock LED\n",
PROGNAME, PROGVERS, PROGNAME);
}
io_service_t
find_a_keyboard(void)
{
io_service_t result = (io_service_t)0;
CFNumberRef usagePageRef = (CFNumberRef)0;
CFNumberRef usageRef = (CFNumberRef)0;
CFMutableDictionaryRef matchingDictRef = (CFMutableDictionaryRef)0;
if (!(matchingDictRef = IOServiceMatching(kIOHIDDeviceKey))) {
return result;
}
UInt32 usagePage = kHIDPage_GenericDesktop;
UInt32 usage = kHIDUsage_GD_Keyboard;
if (!(usagePageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,
&usagePage))) {
goto out;
}
if (!(usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,
&usage))) {
goto out;
}
CFDictionarySetValue(matchingDictRef, CFSTR(kIOHIDPrimaryUsagePageKey),
usagePageRef);
CFDictionarySetValue(matchingDictRef, CFSTR(kIOHIDPrimaryUsageKey),
usageRef);
result = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDictRef);
out:
if (usageRef) {
CFRelease(usageRef);
}
if (usagePageRef) {
CFRelease(usagePageRef);
}
return result;
}
void
find_led_cookies(IOHIDDeviceInterface122** handle)
{
IOHIDElementCookie cookie;
CFTypeRef object;
long number;
long usage;
long usagePage;
CFArrayRef elements;
CFDictionaryRef element;
IOReturn result;
if (!handle || !(*handle)) {
return;
}
result = (*handle)->copyMatchingElements(handle, NULL, &elements);
if (result != kIOReturnSuccess) {
fprintf(stderr, "Failed to copy cookies.\n");
exit(1);
}
CFIndex i;
for (i = 0; i < CFArrayGetCount(elements); i++) {
element = CFArrayGetValueAtIndex(elements, i);
object = (CFDictionaryGetValue(element, CFSTR(kIOHIDElementCookieKey)));
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) {
continue;
}
if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType,
&number)) {
continue;
}
cookie = (IOHIDElementCookie)number;
object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsageKey));
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) {
continue;
}
if (!CFNumberGetValue((CFNumberRef)object, kCFNumberLongType,
&number)) {
continue;
}
usage = number;
object = CFDictionaryGetValue(element,CFSTR(kIOHIDElementUsagePageKey));
if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) {
continue;
}
if (!CFNumberGetValue((CFNumberRef)object, kCFNumberLongType,
&number)) {
continue;
}
usagePage = number;
if (usagePage == kHIDPage_LEDs) {
switch (usage) {
case kHIDUsage_LED_NumLock:
numlock_cookie = cookie;
break;
case kHIDUsage_LED_CapsLock:
capslock_cookie = cookie;
break;
default:
break;
}
}
}
return;
}
void
create_hid_interface(io_object_t hidDevice, IOHIDDeviceInterface*** hdi)
{
IOCFPlugInInterface** plugInInterface = NULL;
io_name_t className;
HRESULT plugInResult = S_OK;
SInt32 score = 0;
IOReturn ioReturnValue = kIOReturnSuccess;
ioReturnValue = IOObjectGetClass(hidDevice, className);
ioReturnValue = IOCreatePlugInInterfaceForService(
hidDevice, kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID, &plugInInterface, &score);
if (ioReturnValue != kIOReturnSuccess) {
return;
}
plugInResult = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID)hdi);
(*plugInInterface)->Release(plugInInterface);
}
int
manipulate_led(UInt32 whichLED, UInt32 value)
{
io_service_t hidService = (io_service_t)0;
io_object_t hidDevice = (io_object_t)0;
IOHIDDeviceInterface **hidDeviceInterface = NULL;
IOReturn ioReturnValue = kIOReturnError;
IOHIDElementCookie theCookie = (IOHIDElementCookie)0;
IOHIDEventStruct theEvent;
if (!(hidService = find_a_keyboard())) {
fprintf(stderr, "No keyboard found.\n");
return ioReturnValue;
}
hidDevice = (io_object_t)hidService;
create_hid_interface(hidDevice, &hidDeviceInterface);
find_led_cookies((IOHIDDeviceInterface122 **)hidDeviceInterface);
ioReturnValue = IOObjectRelease(hidDevice);
if (ioReturnValue != kIOReturnSuccess) {
goto out;
}
ioReturnValue = kIOReturnError;
if (hidDeviceInterface == NULL) {
fprintf(stderr, "Failed to create HID device interface.\n");
return ioReturnValue;
}
if (whichLED == kHIDUsage_LED_NumLock) {
theCookie = numlock_cookie;
} else if (whichLED == kHIDUsage_LED_CapsLock) {
theCookie = capslock_cookie;
}
if (theCookie == 0) {
fprintf(stderr, "Bad or missing LED cookie.\n");
goto out;
}
ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, 0);
if (ioReturnValue != kIOReturnSuccess) {
fprintf(stderr, "Failed to open HID device interface.\n");
goto out;
}
ioReturnValue = (*hidDeviceInterface)->getElementValue(hidDeviceInterface,
theCookie, &theEvent);
if (ioReturnValue != kIOReturnSuccess) {
(void)(*hidDeviceInterface)->close(hidDeviceInterface);
goto out;
}
fprintf(stdout, "%s\n", (theEvent.value) ? "on" : "off");
if (value != -1) {
if (theEvent.value != value) {
theEvent.value = value;
ioReturnValue = (*hidDeviceInterface)->setElementValue(
hidDeviceInterface, theCookie,
&theEvent, 0, 0, 0, 0);
if (ioReturnValue == kIOReturnSuccess) {
fprintf(stdout, "%s\n", (theEvent.value) ? "on" : "off");
}
}
}
ioReturnValue = (*hidDeviceInterface)->close(hidDeviceInterface);
out:
(void)(*hidDeviceInterface)->Release(hidDeviceInterface);
return ioReturnValue;
}
static const char *options = "c::hn::";
static struct option
long_options[] = {
{ "capslock", optional_argument, 0, 'c' },
{ "help", no_argument, 0, 'h' },
{ "numlock", optional_argument, 0, 'n' },
{ 0, 0, 0, 0 },
};
int
main (int argc, char **argv)
{
UInt32 whichLED = (UInt32)0;
int c, ctr = 0;
int target_value = -1;
while ((c = getopt_long(argc, argv, options, long_options, NULL)) != -1) {
switch (c) {
case 0:
break;
case 'c':
ctr++;
whichLED = kHIDUsage_LED_CapsLock;
if (optarg) {
capslock_value = atoi(optarg);
target_value = (capslock_value);
}
break;
case 'h':
usage();
exit(0);
break;
case 'n':
ctr++;
whichLED = kHIDUsage_LED_NumLock;
if (optarg) {
numlock_value = atoi(optarg);
target_value = (numlock_value);
}
break;
default:
usage();
exit(1);
break;
}
}
if (ctr != 1) {
fprintf(stderr, "Missing options or invalid combination of options. "
"Try -h for help.\n");
exit(1);
}
return manipulate_led(whichLED, target_value);
}
" Assuming keyboard_leds is built and available in your PATH,
" this will make capslock indicate whether or not you are in insert mode.
autocmd InsertEnter * :!keyboard_leds -c1
autocmd InsertLeave * :!keyboard_leds -c0
" To make Vim control the keyboard backlight, use this.
" Note that it's glitchy and you'll probably toss the idea soon after.
" I can see programmatically controlling the lights to be useful in other cases, though.
" Install Lab tick and set a hotkey for Toggle, and one for Brighten.
" http://labtick.proculo.de/
" Then use something like:
autocmd InsertLeave * silent :!osascript -e 'tell application "System Events" to keystroke "a" using control down'
autocmd InsertEnter * silent :!osascript -e 'tell application "System Events" to keystroke "b" using control down'
@rdlugosz
Copy link

Pretty slick. I noticed that it matters which keyboard you're using when you issue the command... My Matias Quiet Pro has a capslock LED, but is not triggered by this code. If I reach over and use the built-in keyboard on my MBP its caps LED will light up as expected.

As far as the Vim Autocmds go, you may want to switch to a call to system() like this, which (for me) avoids the "shell flash" you see when executing a :! command.

    " Attempt to toggle the Capslock LED on Insert mode, because why not?
    if executable('keyboard_leds')
      autocmd InsertEnter * let _caps=system('keyboard_leds -c1')
      autocmd InsertLeave * let _caps=system('keyboard_leds -c0')
    endif

@chrisvacc
Copy link

chrisvacc commented Jan 28, 2019

Pretty slick. I noticed that it matters which keyboard you're using when you issue the command... My Matias Quiet Pro has a capslock LED, but is not triggered by this code. If I reach over and use the built-in keyboard on my MBP its caps LED will light up as expected.

As far as the Vim Autocmds go, you may want to switch to a call to system() like this, which (for me) avoids the "shell flash" you see when executing a :! command.

    " Attempt to toggle the Capslock LED on Insert mode, because why not?
    if executable('keyboard_leds')
      autocmd InsertEnter * let _caps=system('keyboard_leds -c1')
      autocmd InsertLeave * let _caps=system('keyboard_leds -c0')
    endif

just tried this.. no bueno. maybe there’s a different way with newer macOS versions? I noticed this post is from 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment