Skip to content

Macos mode - #6

Merged
reversebias merged 6 commits into
masterfrom
macos-mode
Aug 10, 2026
Merged

Macos mode#6
reversebias merged 6 commits into
masterfrom
macos-mode

Conversation

@reversebias

Copy link
Copy Markdown
Owner

No description provided.

reversebias and others added 6 commits August 10, 2026 12:53
Left CTRL keeps its Windows/Linux muscle memory on macOS: when the
active BLE profile is a mac (indices 2/3 = WORK MAC / MISC MAC; USB is
always PC mode for BIOS use), mac_mode.c activates the M_BASE overlay
and CTRL emits left-cmd. Four sparse M-layers (&trans everywhere except
mac deltas) sit directly above their PC siblings and compose via
conditional_layers; PC layers are untouched.

Mac deltas:
- CTRL -> left cmd (copy/paste/find/save/tabs all follow)
- GUI: hold = right cmd (distinct so iTerm2's Left-Cmd->Ctrl remap
  leaves a real cmd), tap = cmd+space Spotlight
- CTRL+TAB / CTRL+SHIFT+TAB -> ctrl(+shift)+tab morphs (in-app tabs)
- ALT+TAB -> hold-and-cycle app switcher via mac_swap behavior: holds
  RIGHT cmd (left-cmd-keyed morphs stay dormant so FUNC+arrows navigate
  the switcher), masks ALT, drops TAB before cmd on commit, 5s self-heal
  watchdog so a bug can never stick cmd
- CTRL+BACKSPACE -> option+delete (word delete)
- CTRL(+SHIFT)+ARROWS on FUNC/FUNCSHFT -> option(+shift)+arrow morphs
  (word jump/select; shift explicit - SHIFT here is a layer)
- PRINTSCREEN -> cmd+shift+ctrl+4 (area snip to clipboard)
- Combos: mod-click chords swap LC()->LG() on mac layers (highest-layer
  combo filtering verified in combo.c)

Also: profile nicknames renamed and widened to 8 visible chars
(HOME PC / MISC PC / WORK MAC / MISC MAC / ANDROID); layer names
exposed on both OLEDs distinguish the mode (M-BASE etc. via the relay).

Caveats documented in-tree: never bind &mo/&tog to M-layers (the
conditional engine force-deactivates then-layers); do not reorder
layers in Studio (index-order dependent; REORDERING is hard-selected by
ZMK_STUDIO so it cannot be compiled out). Kill switch:
CONFIG_ANAPHASE_MACOS_MODE=n leaves M-layers dormant.

Cost: +0.6% flash / +1.3% RAM on the central; right half unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardware test found cmd+option+esc (Force Quit) firing when cancelling
the app switcher with ESC. Root cause: upstream mod-morph clears the
global modifier mask unconditionally on EVERY release, even when the
morph took its default branch (behavior_mod_morph.c release path) - so
the first TAB release through the enclosing mm_tab_mac morph un-masked
ALT mid-session, leaving cmd+option held; a habitual ESC tap completed
the Force Quit chord.

Fix: delete mm_tab_mac; bind &mac_swap directly to the TAB cell and
make it the mask's only owner. The swapper now implements all three tab
behaviours itself: plain TAB, masked ctrl+tab with left-cmd held, and
the ALT-masked right-cmd switcher session. ESC during a session is now
a clean switcher-cancel (cmd+esc, option properly masked).

Also from the same test round, no firmware change: Firefox's default
MRU ctrl-tab order made discrete ctrl+tab chords toggle between the two
most-recent tabs; unticking 'Ctrl+Tab cycles through tabs in recently
used order' restores strip cycling (noted in the code comments).

Known residual, documented: the M_SHIFT prev-tab morph fired INSIDE the
switcher would still clear the ALT mask (exotic; tolerated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v2 made the swapper the modifier-mask's owner, but EVERY mod-morph
release clears the global mask - including the FUNC+arrow morphs used
to navigate the switcher. Arrow-release then ESC while phys-ALT was
still held reproduced cmd+option+esc (Force Quit) deterministically
(confirmed on hardware with that exact ordering).

v3 stops using the mask for sessions entirely: a synthetic LALT-release
keycode at session start drops the alt refcount 1->0 (OS sees alt up
for the whole session), raised before 'swapping' is set so our own
session-ending listener ignores it. The eventual physical ALT release
clamps at zero in hid.c with only a log line - verified upstream
unregister_mod refuses to go negative. Nothing shared remains for
morphs to clobber.

Residual, documented: the brief LGUI mask around a ctrl+tab press can
be clobbered by a simultaneous morph release (sub-100ms window, wrong
tab action at worst).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v3's synthetic ALT release left ZMK believing alt was up after the 5s
watchdog committed a session with phys-ALT still physically held - the
next TAB then typed a stray tab into the committed app instead of
reopening the switcher. The watchdog path now re-presses ALT
synthetically (safe inference: had ALT been released, the keycode
listener would have ended the session before the watchdog could), so
ZMK's state matches the finger and post-heal TAB starts a fresh
session. Listener-ended sessions are unchanged - alt is genuinely up
there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@reversebias
reversebias merged commit 96819d8 into master Aug 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant