Skip to content

Instantly share code, notes, and snippets.

@dollthing
Created October 8, 2026 00:25
Show Gist options
  • Select an option

  • Save dollthing/4b524cdbb3306863c1632d3552c5c590 to your computer and use it in GitHub Desktop.

Select an option

Save dollthing/4b524cdbb3306863c1632d3552c5c590 to your computer and use it in GitHub Desktop.
how to neuralesemax on macOS: a guide

Caps Lock → ✔️ / ❌ on macOS

Use the MacBook's built-in Caps Lock key for quick symbols while keeping real Caps Lock available:

Action Result
Tap Caps Lock ✔️ (U+2714 + U+FE0F)
Shift + tap Caps Lock ❌ (U+274C)
Hold Caps Lock for 250 ms Toggle real Caps Lock once

External keyboards keep their usual behavior. The clipboard is untouched. Symbols are typed directly with native Unicode events, so Unicode Hex Input is unnecessary and the English/Japanese input-source cycle stays unchanged.

Tested on a MacBook with macOS 27.0 and Karabiner-Elements 16.3.0. Other versions have not been verified.

Install

  1. Install and open Karabiner-Elements, select the desired profile, and complete its required permission setup.

  2. Have Python 3 and Apple's Command Line Tools available. If needed, install the tools with xcode-select --install.

  3. Download install-caps-symbols.py from this gist, open Terminal in its folder, and run:

    python3 install-caps-symbols.py

The installer compiles a small native helper, backs up existing files, and enables the rule in the selected Karabiner profile. It preserves unrelated rules and other profiles. No sudo is needed. An existing Caps Lock Simple Modification must be removed before installation.

Allow symbol typing

On macOS 27, open System Settings → Privacy & Security → Device Control and Data Access. Earlier macOS versions call this permission Accessibility.

Enable Karabiner-Console-User-Server. If it is missing from the list:

  1. Click +, then ⌘⇧G.

  2. Paste this path:

    /Library/Application Support/org.pqrs/Karabiner-Elements/Karabiner-Console-User-Server.app
    
  3. Click Open, enable its switch, and authenticate if prompted.

This allows Karabiner's shell-command process to send the symbol keystrokes. Its Core Service permission belongs to a separate process. If holding Caps works but the taps produce nothing, check this permission first. Quit and reopen Karabiner-Elements if macOS requests it or the change does not take effect.

Remove an old Unicode Hex Input setup

This installer never adds an input source. If Unicode Hex Input was added for an earlier version of this rule, install with:

python3 install-caps-symbols.py --remove-unicode-hex-input

That also removes Unicode Hex Input from the input-source list. The installer refuses to remove it if another active configuration still references it. English and Japanese sources are retained.

Verify and undo

In a blank document, test both taps, then hold Caps → type a → hold Caps → type a. Also check the apps and input methods used regularly; text insertion can depend on how an app handles native Unicode events.

To disable the mapping, remove “Caps Lock: ✔️ / ❌ / native hold (built-in keyboard)” from Karabiner's Complex Modifications. Generated helper and asset files live under ~/.config/karabiner/; they are inactive after the rule is removed. Existing files are backed up under ~/.config/karabiner/caps-symbols-backups/<timestamp>/, with the backup path printed during installation. Restoring the backed-up karabiner.json also restores the profile configuration as it was at installation time.

How it works

Karabiner distinguishes a tap from a hold and restricts the rule to the built-in keyboard. The helper uses CGEventKeyboardSetUnicodeString and CGEventPostToPid for symbols, and IOHIDGetModifierLockState / IOHIDSetModifierLockState for actual Caps Lock. It does not paste, switch input sources, or rely on synthetic Caps Lock keypresses.

#!/usr/bin/env python3
"""Install built-in Caps Lock: tap ✔️, Shift+tap ❌, hold real Caps Lock."""
import copy
import datetime
import json
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import tempfile
DESCRIPTION = "Caps Lock: ✔️ / ❌ / native hold (built-in keyboard)"
NATIVE_SOURCE = r'''
#include <Carbon/Carbon.h>
#include <CoreGraphics/CoreGraphics.h>
#import <AppKit/AppKit.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/hidsystem/IOHIDLib.h>
#include <IOKit/hidsystem/IOHIDParameter.h>
#include <IOKit/hidsystem/IOHIDShared.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static CGEventRef text_event(bool down, const UniChar *text, UniCharCount length) {
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
CGEventRef event = CGEventCreateKeyboardEvent(source, 0, down);
if (source) CFRelease(source);
if (event) {
CGEventSetFlags(event, 0);
CGEventKeyboardSetUnicodeString(event, length, text);
}
return event;
}
static int insert_symbol(bool cross, bool verify_only) {
const UniChar check[] = {0x2714, 0xfe0f};
const UniChar negative[] = {0x274c};
const UniChar *text = cross ? negative : check;
UniCharCount length = cross ? 1 : 2;
CGEventRef down = text_event(true, text, length);
CGEventRef up = text_event(false, text, length);
if (!down || !up) {
if (down) CFRelease(down);
if (up) CFRelease(up);
return 1;
}
if (verify_only) {
UniChar actual[2] = {0, 0};
UniCharCount count = 0;
CGEventKeyboardGetUnicodeString(down, 2, &count, actual);
bool valid = count == length && !memcmp(actual, text, length * sizeof(UniChar)) && CGEventGetFlags(down) == 0;
CFRelease(down); CFRelease(up);
return valid ? 0 : 1;
}
if (!CGPreflightPostEventAccess()) {
fprintf(stderr, "Direct Unicode input needs Device Control and Data Access (Accessibility on older macOS) permission for Karabiner-Console-User-Server.\n");
CFRelease(down); CFRelease(up); return 2;
}
@autoreleasepool {
NSRunningApplication *application = [[NSWorkspace sharedWorkspace] frontmostApplication];
pid_t target = application ? [application processIdentifier] : 0;
if (target <= 0) { CFRelease(down); CFRelease(up); return 1; }
CGEventPostToPid(target, down);
usleep(10000);
CGEventPostToPid(target, up);
}
CFRelease(down); CFRelease(up);
printf("Inserted %s directly.\n", cross ? "cross" : "check");
return 0;
}
static int remove_unicode_layout(void) {
CFArrayRef sources = TISCreateInputSourceList(NULL, true);
TISInputSourceRef target = NULL;
for (CFIndex i = 0; sources && i < CFArrayGetCount(sources); ++i) {
TISInputSourceRef source = (TISInputSourceRef)CFArrayGetValueAtIndex(sources, i);
CFStringRef identifier = TISGetInputSourceProperty(source, kTISPropertyInputSourceID);
if (identifier && CFEqual(identifier, CFSTR("com.apple.keylayout.UnicodeHexInput"))) target = source;
}
OSStatus result = noErr;
if (target && CFBooleanGetValue(TISGetInputSourceProperty(target, kTISPropertyInputSourceIsEnabled))) {
if (CFBooleanGetValue(TISGetInputSourceProperty(target, kTISPropertyInputSourceIsSelected))) {
bool selected = false;
for (CFIndex i = 0; i < CFArrayGetCount(sources); ++i) {
TISInputSourceRef other = (TISInputSourceRef)CFArrayGetValueAtIndex(sources, i);
CFStringRef type = TISGetInputSourceProperty(other, kTISPropertyInputSourceType);
CFBooleanRef enabled = TISGetInputSourceProperty(other, kTISPropertyInputSourceIsEnabled);
if (other != target && type && CFEqual(type, kTISTypeKeyboardLayout) && enabled && CFBooleanGetValue(enabled)) {
selected = TISSelectInputSource(other) == noErr;
if (selected) break;
}
}
if (!selected) { CFRelease(sources); fprintf(stderr, "Switch to English or Japanese first.\n"); return 1; }
}
result = TISDisableInputSource(target);
}
if (sources) CFRelease(sources);
if (result) fprintf(stderr, "Cannot remove Unicode Hex Input (error %d).\n", (int)result);
return result ? 1 : 0;
}
static bool wait_for_caps_state(io_connect_t connection, bool expected) {
for (int attempt = 0; attempt < 20; ++attempt) {
bool actual = !expected;
kern_return_t result = IOHIDGetModifierLockState(connection, kIOHIDCapsLockState, &actual);
bool flags = (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & kCGEventFlagMaskAlphaShift) != 0;
if (!result && actual == expected && flags == expected) return true;
usleep(50000);
}
return false;
}
int main(int argc, char **argv) {
if (argc != 2) return 1;
if (!strcmp(argv[1], "check")) return insert_symbol(false, false);
if (!strcmp(argv[1], "cross")) return insert_symbol(true, false);
if (!strcmp(argv[1], "verify-unicode")) return insert_symbol(false, true) || insert_symbol(true, true);
if (!strcmp(argv[1], "post-access")) { printf("%s\n", CGPreflightPostEventAccess() ? "allowed" : "denied"); return 0; }
if (!strcmp(argv[1], "remove-unicode")) return remove_unicode_layout();
bool test = !strcmp(argv[1], "self-test");
if (!test && strcmp(argv[1], "toggle")) return 1;
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOHIDSystem"));
if (!service) { fprintf(stderr, "IOHIDSystem unavailable.\n"); return 1; }
io_connect_t connection = IO_OBJECT_NULL;
kern_return_t result = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection);
IOObjectRelease(service);
if (result) { fprintf(stderr, "Cannot open IOHIDSystem: 0x%x\n", result); return 1; }
bool original = false;
result = IOHIDGetModifierLockState(connection, kIOHIDCapsLockState, &original);
if (result) { IOServiceClose(connection); return 1; }
result = IOHIDSetModifierLockState(connection, kIOHIDCapsLockState, !original);
bool passed = !result && wait_for_caps_state(connection, !original);
if (test) {
kern_return_t restore = IOHIDSetModifierLockState(connection, kIOHIDCapsLockState, original);
bool restored = !restore && wait_for_caps_state(connection, original);
passed = passed && restored;
}
IOServiceClose(connection);
if (!passed) fprintf(stderr, "Caps Lock native verification failed.\n");
return passed ? 0 : 1;
}
'''
def make_rule(helper):
def manipulator(shift, action):
modifiers = {"optional": ["caps_lock"]}
if shift:
modifiers["mandatory"] = ["shift"]
return {
"type": "basic",
"from": {"key_code": "caps_lock", "modifiers": modifiers},
"to": [{"key_code": "vk_none"}],
"to_if_alone": [{"shell_command": shlex.quote(str(helper)) + " " + action}],
"to_if_held_down": [{"shell_command": shlex.quote(str(helper)) + " toggle"}],
"parameters": {"basic.to_if_alone_timeout_milliseconds": 250, "basic.to_if_held_down_threshold_milliseconds": 250},
"conditions": [{"type": "device_if", "identifiers": [{"is_built_in_keyboard": True}]}],
}
return {"description": DESCRIPTION, "manipulators": [manipulator(True, "cross"), manipulator(False, "check")]}
def update_config(config, rule):
config = copy.deepcopy(config)
profile = next((p for p in config.get("profiles", []) if p.get("selected")), None)
if profile is None:
raise RuntimeError("Open Karabiner-Elements and select a profile first.")
mappings = list(profile.get("simple_modifications", []))
for device in profile.get("devices", []):
mappings.extend(device.get("simple_modifications", []))
if any(mapping.get("from", {}).get("key_code") == "caps_lock" for mapping in mappings):
raise RuntimeError("Remove the existing Caps Lock Simple Modification in Karabiner first, then rerun this installer.")
rules = profile.setdefault("complex_modifications", {}).setdefault("rules", [])
names = {DESCRIPTION, "caps: tap ✔️, shift-tap ❌, hold caps lock"}
rules[:] = [rule] + [old for old in rules if old.get("description") not in names]
return config
def write_atomic(path, data, mode=0o600):
path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary = tempfile.mkstemp(prefix=".caps-symbols-", dir=path.parent)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
stream.write(data)
os.chmod(temporary, mode)
os.replace(temporary, path)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def main():
if any(argument != "--remove-unicode-hex-input" for argument in sys.argv[1:]):
raise RuntimeError("Usage: install-caps-symbols.py [--remove-unicode-hex-input]")
if sys.platform != "darwin":
raise RuntimeError("This installer is for macOS.")
root = Path.home() / ".config/karabiner"
config_path = root / "karabiner.json"
cli = Path("/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_cli")
if not cli.is_file() or not config_path.is_file():
raise RuntimeError("Install and open Karabiner-Elements first, then rerun this installer.")
subprocess.run(["/usr/bin/xcode-select", "-p"], check=True, stdout=subprocess.DEVNULL)
config = json.loads(config_path.read_text(encoding="utf-8"))
helper = root / "bin/caps_symbols_native"
update_config(config, make_rule(helper)) # Preflight before changing anything.
asset = root / "assets/complex_modifications/caps_symbols.json"
source_path = root / "assets/helpers/caps_symbols_native.m"
legacy_asset = root / "assets/complex_modifications/doll_caps.json"
backup = root / "caps-symbols-backups" / datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
backup.mkdir(parents=True)
for path in [config_path, asset, legacy_asset, source_path, helper]:
if path.exists():
destination = backup / path.relative_to(root)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, destination)
with tempfile.TemporaryDirectory(prefix="caps-symbols-") as temporary:
source = Path(temporary) / "helper.m"
binary = Path(temporary) / "helper"
source.write_text(NATIVE_SOURCE, encoding="utf-8")
subprocess.run(["/usr/bin/clang", str(source), "-framework", "IOKit", "-framework", "CoreGraphics", "-framework", "Carbon", "-framework", "AppKit", "-o", str(binary)], check=True)
subprocess.run([str(binary), "verify-unicode"], check=True)
rule = make_rule(helper)
artifact = {"title": "Caps Lock symbols", "rules": [rule]}
candidate = Path(temporary) / "caps_symbols.json"
candidate.write_text(json.dumps(artifact, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
subprocess.run([str(cli), "--lint-complex-modifications", str(candidate)], check=True)
subprocess.run([str(binary), "self-test"], check=True)
# Re-read immediately before writing so unrelated intervening edits survive.
config = update_config(json.loads(config_path.read_text(encoding="utf-8")), rule)
if "--remove-unicode-hex-input" in sys.argv[1:] and "UnicodeHexInput" in json.dumps(config):
raise RuntimeError("Other rules still use Unicode Hex Input. Update those rules before removing that input source.")
helper.parent.mkdir(parents=True, exist_ok=True)
staging = helper.with_name(helper.name + ".new")
shutil.copyfile(binary, staging)
os.chmod(staging, 0o755)
staging.replace(helper)
write_atomic(source_path, NATIVE_SOURCE)
write_atomic(asset, candidate.read_text(encoding="utf-8"))
if legacy_asset.exists():
previous = json.loads(legacy_asset.read_text(encoding="utf-8"))
managed_names = {DESCRIPTION, "caps: tap ✔️, shift-tap ❌, hold caps lock"}
if previous.get("rules") and all(item.get("description") in managed_names for item in previous["rules"]):
write_atomic(legacy_asset, candidate.read_text(encoding="utf-8"))
write_atomic(config_path, json.dumps(config, ensure_ascii=False, indent=4) + "\n")
if "--remove-unicode-hex-input" in sys.argv[1:]:
subprocess.run([str(helper), "remove-unicode"], check=True)
print("Installed: tap Caps Lock → ✔️; Shift+tap → ❌; hold 250 ms → Caps Lock.")
print("Built-in keyboard only; clipboard untouched.")
print("Symbols are inserted directly; input sources are never switched or added.")
print("Enable Karabiner-Console-User-Server in System Settings → Privacy & Security → Device Control and Data Access (Accessibility on older macOS).")
print("If missing, add: /Library/Application Support/org.pqrs/Karabiner-Elements/Karabiner-Console-User-Server.app")
print("Test both symbols and hold → type a → hold → type a in a blank text document.")
print("Backup:", backup)
if __name__ == "__main__":
try:
main()
except (RuntimeError, OSError, ValueError, subprocess.CalledProcessError) as error:
print("Installation stopped:", error, file=sys.stderr)
print("Karabiner must already be running with its required permissions; Apple's Command Line Tools must be installed.", file=sys.stderr)
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment