3

I have seen many post on this topic. But haven't found a clear answer anywhere.

Is there a way to toggle CAPS LOCK in Objective-C or C code? I am not looking for a solution using X11 libs. I am not bothered about the LED on/off status. But just the functionality of CAPS LOCK (changing the case of letters and printing the special characters on number keys).

Why is CGEvent not supporting this the way it does for other keys?

Seema Kadavan
  • 2,538
  • 1
  • 16
  • 31
  • Explain what you tried with the CGEvent API that didn't work. – Ken Thomases May 04 '15 at 18:36
  • @KenThomases I've tried this: CGEventRef keyDownEvent = CGEventCreateKeyboardEvent (NULL, kVK_CapsLock, true); CGEventPost(kCGHIDEventTap, keyDownEvent); - and the corresponding key up event. Nothing happens. – Simon Jun 17 '15 at 14:50
  • You can try `CGEventCreate()`, then `CGEventSetType(…, kCGEventFlagsChanged)`, and `CGEventSetFlags(…, kCGEventFlagMaskAlphaShift). Then post that. – Ken Thomases Jun 17 '15 at 16:00

4 Answers4

3
var ioConnect: io_connect_t = .init(0)
let ioService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass))
IOServiceOpen(ioService, mach_task_self_, UInt32(kIOHIDParamConnectType), &ioConnect)

var modifierLockState = false
IOHIDGetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), &modifierLockState) 

modifierLockState.toggle()
IOHIDSetModifierLockState(ioConnect, Int32(kIOHIDCapsLockState), modifierLockState)

IOServiceClose(ioConnect)
3

The following command line program in Objective-C will toggle the current caps lock state:

#import <IOKit/IOKitLib.h> // or @import IOKit;
#import <IOKit/hid/IOHIDBase.h> // needed for kIOHIDSystemClass

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Use kIOMasterPortDefault instead of kIOMainPortDefault for macOS 11.x or earlier
        io_service_t ioService = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(kIOHIDSystemClass));
        io_connect_t ioConnect = 0;
        IOServiceOpen(ioService, mach_task_self_, kIOHIDParamConnectType, &ioConnect);

        bool state = false;
        IOHIDGetModifierLockState(ioConnect, kIOHIDCapsLockState, &state);

        // Toggle state
        state = !state; // 1 = CapsLock on, 0 = CapsLock off
        IOHIDSetModifierLockState(ioConnect, kIOHIDCapsLockState, state);

        IOServiceClose(ioConnect);
    }
    return 0;
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
  • Note: This isn't an API that updates immediately. If I do a chain of get -> toggle -> get -> toggle -> get, I frequently see the state from the getter logged: 0, 0, 1, or 1, 1, 0. Expected: 0, 1, 0, or 1, 0, 1. Only with a short delay in-between do I see the correct logs. Both functions return kIOReturnSuccess. – Nick Apr 02 '23 at 10:21
0

The following method also works when you want CapsLock to toggle the current keyboard language (if you have the CapsLock key configured to do that).

CFMutableDictionaryRef mdict = IOServiceMatching(kIOHIDSystemClass);
io_service_t ios = IOServiceGetMatchingService(kIOMasterPortDefault, (CFDictionaryRef)mdict);
if (ios) {
    io_connect_t ioc = 0;
    IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, &ioc);
    if (ioc) {
        NXEventData event{};
        IOGPoint loc{};

        // press CapsLock key
        UInt32 evtInfo = NX_KEYTYPE_CAPS_LOCK << 16 | NX_KEYDOWN << 8;
        event.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS;
        event.compound.misc.L[0] = evtInfo;
        IOHIDPostEvent(ioc, NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE);

        // release CapsLock key
        evtInfo = NX_KEYTYPE_CAPS_LOCK << 16 | NX_KEYUP << 8;
        event.compound.subType = NX_SUBTYPE_AUX_CONTROL_BUTTONS;
        event.compound.misc.L[0] = evtInfo;
        IOHIDPostEvent(ioc, NX_SYSDEFINED, loc, &event, kNXEventDataVersion, 0, FALSE);

        IOServiceClose(ioc);
    }
}
Byteboon
  • 120
  • 9
-1

I got this working, after a long struggle.

Invoke the method given below twice. Once for up event and another for down event. For example for simulating CAPS A, we need to do the following.

[self handleKeyEventWithCapsOn:0 andKeyDown:NO];
[self handleKeyEventWithCapsOn:0 andKeyDown:YES];

0 is the keycode for 'a'.

- (void) handleKeyEventWithCapsOn:(int) keyCode andKeyDown:(BOOL)keyDown
{
    if(keyDown)
    {
        CGEventRef eventDown;
        eventDown = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)keyCode, true);
        CGEventSetFlags(eventDown, kCGEventFlagMaskShift);
        CGEventPost(kCGSessionEventTap, eventDown);
        CFRelease(eventDown);
    }
    else
    {
        CGEventRef eventUp;
        eventUp = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)keyCode, false);
        CGEventSetFlags(eventUp, kCGEventFlagMaskShift);
        CGEventPost(kCGSessionEventTap, eventUp);

        // SHIFT Up Event
        CGEventRef eShiftUp = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)56, false);
        CGEventPost(kCGSessionEventTap, eShiftUp);
        CFRelease(eventUp);
        CFRelease(eShiftUp);
    }
}
Seema Kadavan
  • 2,538
  • 1
  • 16
  • 31