# macoc Native macOS menu-bar layout switcher for macOS Sonoma (14+) without the stock Caps Lock delay. Plain **Caps** switches to the next configured input source on **release**, including during fast typing. **Option+Caps** in either order toggles the real system Caps Lock state. The icon is a SFW cartoon of someone thoroughly fed up with keyboard layout switching, complete with a literally hot seat. ## Architecture Caps Lock is remapped to F19 via in-process Apple IOKit APIs, then suppressed by a CoreGraphics event tap. Layouts switch through Carbon TIS. No drivers, root daemons, third-party input libraries or hidutil permission required. Apple technical note: https://developer.apple.com/library/archive/technotes/tn2450/ ## Build and install Requires Apple's Command Line Tools (not the Xcode IDE). Task is optional. Quit any running **macoc** or legacy **Capswitch** before rebuilding. From this repository: git pull origin main bash scripts/test.sh bash scripts/install.sh With Task installed: task test task install Installation creates a signed macoc.app with the .icns icon in the stable user-owned location ~/Applications/macoc.app, then starts it through LaunchServices. No sudo is needed. For development without installing: bash scripts/bundle.sh or task bundle. The menu offers **Restart macoc**, **Install to ~/Applications…** and **Launch at Login**, registered via Apple's SMAppService.mainApp. If macOS requests approval, open System Settings -> General -> Login Items. ### Migrating from Capswitch The name and executable are now macoc; the existing bundle identifier remains dev.local.Capswitch to preserve old Accessibility grants and HID ownership data where macOS permits. Do not run the old Capswitch and macoc simultaneously. If the old ~/Applications/Capswitch.app is installed, quit it and disable its Login Item before installing macoc. Then you may delete the old app bundle. ## Code signing and repeat Accessibility prompts Restart now uses LaunchServices (open -n -a) instead of executing a raw binary under Contents/MacOS. That preserves the macOS .app attribution. For identical app binaries at the same path, Accessibility approval normally persists through restarts. But ad-hoc signing does **not** provide a stable designated requirement across code changes: each rebuild may require approval again. For stable identity across **rebuilt versions**, create one persistent local Code Signing certificate in Keychain Access named **macoc Local Signing** (Certificate Assistant -> Create a Certificate -> Self Signed Root -> Code Signing). Run bash scripts/setup-signing.sh or task signing-setup for the instructions. The build auto-detects that identity, or accepts MACOC_CODESIGN_IDENTITY for an existing signing certificate. It falls back to ad-hoc signing with an explicit warning otherwise. Moving to the installed path or changing the signer can still require **one new approval**. Keep the signing identity and app path stable. Reference: https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements macoc cannot silently grant itself TCC permissions. macOS still asks the user to allow Accessibility (and optionally Input Monitoring) for the app, not for a system binary. Disable macOS's own "Use Caps Lock to switch to/from ABC" under System Settings -> Keyboard -> Text Input -> Edit. Keep physical Caps assigned its normal modifier role rather than No Action. ## Taskfile commands and shell equivalents | Taskfile | No Task required | | --- | --- | | task lint | bash scripts/lint.sh | | task test | bash scripts/test.sh | | task verify | bash scripts/verify.sh | | task build | bash scripts/build.sh | | task icon | bash scripts/icon.sh | | task sign | bash scripts/sign.sh | | task bundle | bash scripts/bundle.sh | | task install | bash scripts/install.sh | | task doctor | bash scripts/doctor.sh | | task clean | bash scripts/clean.sh | | task package | bash scripts/package.sh | | task publish | bash scripts/publish-release.sh | | task signing-setup | bash scripts/setup-signing.sh | ## Forgejo CI and releases CI workflow: .forgejo/workflows/ci.yml. It uses the **existing Docker runner** and the official Linux Swift image. Pushes to main run shell validation and **actual XCTest for pure Swift core logic**. On version tags it also checks the tag against the application version. **Linux Docker cannot compile macOS .app binaries** because Apple AppKit, IOKit and macOS SDK are unavailable there. Docker CI does not publish nonexistent macOS binaries. For a release archive, check out the tag on a real Mac and run bash scripts/package.sh (or task package), then execute bash scripts/publish-release.sh with a repository-scoped Forgejo token. See [CI and release instructions](docs/ci.md). Fully automated macOS binary releases require a real Mac runner or another Mac build service. Ad-hoc artifacts are suited to local evaluation; public distribution requires Developer ID signing and Apple notarization. ## Diagnostics and recovery The status-bar menu shows layout, keyboard HID mapping count, event-tap state, permission grants, and F19 down/up counts (no typed characters are recorded). Run bash scripts/doctor.sh for read-only native HID diagnostics. Pause or Quit normally restores Caps and preserves unrelated remappings. If the process is killed during a remap, restart and Quit normally; macOS usually resets mappings when a keyboard service disappears or after reboot. The Linux development VM verifies shell syntax and static source checks, not macOS compilation or live TCC behavior.