From macro-keyboard
Emit a real Fn key on macOS (for Typeless push-to-talk, macOS dictation, screenshot shortcuts, emoji picker, Spotlight via.
How this skill is triggered — by the user, by Claude, or both
Slash command
/macro-keyboard:emit-fn-key-on-macosThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Self-Evolving Skill**: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Macos's Fn key (also called the "globe" key on modern MacBooks) is special. It's not a regular modifier — it carries kCGEventFlagMaskSecondaryFn, a flag that macOS's input subsystem discards if set from userland via CGEventPost. Many "remap anything" tools assume Fn behaves like Cmd / Option / Ctrl and try to emit it via CGEventPost — and silently fail for dictation, Typeless, macOS's dictation shortcut, Spotlight-via-globe, emoji picker, etc.
The only userland path that emits a Fn the kernel accepts is a DriverKit VirtualHIDDevice. Karabiner-Elements ships one. That's why Karabiner is the only FOSS tool for this job in 2026.
pushToTalk: "Fn" by default)Fn Fn double-tap, or single-Fn in System Settings)Fn+E)Fn)hidutil property --set with Fn mappings and sees no effect# 1. Karabiner-Elements installed (the only userland tool that can emit real Fn)?
test -d /Applications/Karabiner-Elements.app && echo OK || brew install --cask karabiner-elements
# 2. Karabiner has Input Monitoring + Accessibility?
# System Settings → Privacy & Security → Input Monitoring → Karabiner = ON
# System Settings → Privacy & Security → Accessibility → Karabiner = ON
# 3. On macOS Sequoia+: privileged daemon approved in Login Items?
# System Settings → General → Login Items → Allow in the Background
# → Karabiner-Elements Privileged Daemon = ON
# Without this, Karabiner's DriverKit VirtualHIDDevice won't load and
# your Fn remap will silently do nothing.
If Fn already works when pressed on your built-in keyboard but not via a remap, the DriverKit daemon is loaded but the remap rule is wrong — jump to "The One Magic Incantation" below. If even the built-in Fn stops working, re-check prerequisite 3.
┌───────────────────────────┐ ┌───────────────────────────┐
│ Real Fn (from keyboard) │ │ Fn via CGEventPost(...) │
│ Hardware → DriverKit → │ │ Userland app → Quartz → │
│ HIDEvent with │ │ kCGEventFlagMaskSecon- │
│ NX_DEVICE_CAPABILITY_ │ │ daryFn flag is DROPPED │
│ INPUTKEYBOARD_FUNCTION │ │ before reaching input │
│ flag set │ │ subsystem. │
│ │ │ │
│ ✅ Typeless / dictation │ │ ❌ Typeless / dictation │
│ accept it. │ │ never fire. │
└───────────────────────────┘ └───────────────────────────┘
Karabiner-Elements registers a DriverKit VirtualHIDDevice. When its remap rule says "emit Fn", it does so through the same kernel path a hardware keyboard uses. The NX_DEVICE_CAPABILITY_INPUTKEYBOARD_FUNCTION flag survives and Typeless/dictation accept it.
In a Karabiner manipulator's to block:
{ "apple_vendor_top_case_key_code": "keyboard_fn" }
Not:
{"key_code": "fn"} — no such key_code, silently ignored{"modifiers": ["fn"]} — modifier flag without a key-down, doesn't trigger Fn semantics{"consumer_key_code": "..."} — wrong HID usage page{"key_code": "function"} — not a Karabiner keywordThe apple_vendor_top_case_key_code namespace is Karabiner's mapping for HID Usage Page 0x00FF (Apple Top Case) with Usage 0x03 (Keyboard Fn). That's the descriptor Apple's own internal keyboard uses.
Remap the Caps Lock key (on a specific external keyboard only — adjust VID/PID) to emit real Fn:
{
"description": "Caps Lock → Fn (on external keyboard)",
"manipulators": [
{
"type": "basic",
"from": { "key_code": "caps_lock" },
"to": [{ "apple_vendor_top_case_key_code": "keyboard_fn" }],
"conditions": [
{
"type": "device_if",
"identifiers": [{ "vendor_id": 19530, "product_id": 16725 }]
}
]
}
]
}
Drop into ~/.config/karabiner/karabiner.json → profile 0 → complex_modifications.rules.
# Open Karabiner-EventViewer
open -a "Karabiner-EventViewer"
# Press your remapped key; the Main tab should show:
# name: fn (not apple_vendor_top_case_key_code)
# ↑ Karabiner normalizes the display back to "fn" but under the hood it's emitting
# the Apple-Top-Case variant that carries the kernel flag.
# Then test the real consumer (e.g., Typeless with pushToTalk: "Fn"):
# Press and hold → mic opens
# Release → mic closes, transcript appears
If EventViewer shows the right event but Typeless doesn't fire:
pushToTalk in Typeless app-settings.json is set to "Fn" (not "fn", not "Function")Attempted pattern: "tap top button → Return, hold top button → Fn" using Karabiner's to_if_alone + to_if_held_down.
This breaks Fn system-wide. Verified failure 2026-04-21: after loading a rule with to_if_held_down: [{"apple_vendor_top_case_key_code": "keyboard_fn"}], macOS's native Fn key stopped producing Fn even when pressed directly on the built-in keyboard. Full rollback required.
The reason is still unclear — possibly Karabiner's held-down state-machine holds the Fn-flag in a way that conflicts with real hardware Fn events. Until Karabiner upstream fixes it, use a separate button for Fn.
BTT's "Trigger Key Sequence" and "Send Shortcut" actions go through CGEventPost with CGEventCreateKeyboardEvent + CGEventSetFlags. The flag kCGEventFlagMaskSecondaryFn is accepted by CGEventSetFlags (no error) but dropped by the input event manager before delivery. Apple has never documented this constraint; it's consistent across macOS 13 / 14 / 15.
hidutil property --set '{"UserKeyMapping":[...]}' operates at the HID report level and can swap keycodes, but it cannot synthesize the Fn flag. The flag is set by the keyboard driver (DriverKit or Apple's internal keyboard driver) based on which key was pressed — you can't forge it at the HID report level because it's a per-event computed property, not a persisted one.
A QMK board with NKRO + Apple Fn keycode on an actual USB HID report descriptor that advertises Usage Page 0x00FF, Usage 0x03 can emit Fn natively. The kernel accepts it because it's a real hardware report.
But cheap Jieli/Realtek/CH57x pads are not flashable — their firmware is burned in and exposes a fixed HID descriptor (usually standard keyboard Usage Page 0x07). No amount of reflashing with QMK/VIA/Vial works because the bootloader won't accept new firmware.
For flashable boards see: https://github.com/qmk/qmk_firmware → APPLE_FN_ENABLE.
../configure-macro-keyboard/references/03-patterns.md — "Apple vendor Fn encoding" pattern with full rule excerpt../configure-macro-keyboard/references/04-anti-patterns.md — BTT CGEventPost failure, tap-vs-hold Fn failure, QMK/VIA-on-Jieli failure./references/failed-approaches.md — condensed failure catalog for this specific taskconfigure-macro-keyboard — the end-to-end setup workflow that uses this skill as one step (wiring an external macro pad's button to Fn inside a larger Karabiner rule)diagnose-hid-keycodes — use this FIRST if you don't yet know which keycode your hardware emits; only then come back here to remap it to FnAfter this skill completes, reflect before closing the task:
04-anti-patterns.md row) with trigger, fix, evidence.Do NOT defer. The next invocation inherits whatever you leave behind.
npx claudepluginhub terrylica/cc-skills --plugin macro-keyboardGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.