As @ALYB above and others (KMF, SE, GH, …) have mentioned, the CapsLock state detection of the script by @CJK (see OP) stopped working with more recent macOS releases, probably since Catalina.
I fiddled around a bit with that, and was unable to find a really proper solution.
However, I have two approaches that work sufficiently well (for me). For setting the CapsLock state, both are using the setter part of CJK's script. The difference is in the method how to decide if CapsLock should be activated or deactivated:
Approach 1: "Brutal Workaround"
Keyboard: Toggle CapsLock State (remember state via KM var).kmmacros (6.1 KB)
Overview
Main Script
ObjC.import("IOKit");
const state = kmvar.keyboard_CapsLock_State;
const toState = !Number(state);
const ioConnect = Ref();
$.IOServiceOpen(
$.IOServiceGetMatchingService(
$.kIOMasterPortDefault,
$.IOServiceMatching(
$.kIOHIDSystemClass
)
),
$.mach_task_self_,
$.kIOHIDParamConnectType,
ioConnect
);
$.IOHIDSetModifierLockState(ioConnect, $.kIOHIDCapsLockState, toState);
$.IOServiceClose(ioConnect);
No effort is made to detect the actual CapsLock state. Instead, the macro sets a global variable at each run; the variable changes between 1
and 0
and represents the assumed CapsLock state. Depending on this variable, the macro either sets or unsets CapsLock.
If the macro is your only CapsLock toggle, this works perfectly fine, since the variable changes every time you change the CapsLock state.
If you toggled CapsLock by other means (e.g. you disabled CapsLock via the little typing popup), the variable will be out of sync with the true CapsLock state. But this is not really a game stopper, because after one extra macro run, the variable will be in sync again with the state.
Approach 2: "Better – probably"
Keyboard: Toggle CapsLock State (detect state via script).kmmacros (5.7 KB)
Overview
Main Script
ObjC.import("AppKit")
ObjC.import("IOKit");
const theMask = $.NSAlphaShiftKeyMask; // 65536
const theFlag = $.NSEvent.modifierFlags;
const state = (theFlag / theMask) % 2;
const toState = !state;
const ioConnect = Ref();
$.IOServiceOpen(
$.IOServiceGetMatchingService(
$.kIOMasterPortDefault,
$.IOServiceMatching(
$.kIOHIDSystemClass
)
),
$.mach_task_self_,
$.kIOHIDParamConnectType,
ioConnect
);
$.IOHIDSetModifierLockState(ioConnect, $.kIOHIDCapsLockState, toState);
$.IOServiceClose(ioConnect);
This one uses a true state detection (see script), based on a snippet by Shane Stanley.
A small catch here is that it only detects the CapsLock state correctly if any key is pressed, or was pressed since the last macro run.
But, if you use the macro with a trigger similar to the posted one (Long Press Shift), or even a simple hotkey trigger, everything should work flawlessly.
Other, possible, downsides of this approach:
- Since it runs code for the detection (using an additional framework), it may be slower. (Noticeable?)
- I noticed a one-time glitch in one particular application (Script Editor). But this could also be a glitch in the setter part (i.e. it would have happened with the other approach as well). I have not had a chance to verify this yet.
If anyone who is more familiar with these things knows a way to detect the state independently of other keys (like the original script – probably – did it before Catalina), I would be grateful for a hint.
So far, I tried a couple of variations, also as pure Swift scripts, but anything that involves NSEvent.modifierFlags
seems to expose the same behavior.