diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..16d3f02 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,60 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Custom QMK firmware for a System76 Launch 3 keyboard (RP2040, 84-key). The custom keymap adds OS-aware shortcut translation (Linux/Mac/Windows) with automatic OS detection. + +## Build Commands + +```bash +# Development (uses symlink to custom keymap) +nix develop +qmk compile -kb system76/launch_3 -km custom + +# Production build (sandboxed, outputs result/system76_launch_3_custom.uf2) +nix build +``` + +New files in `keymaps/custom/` must be `git add`ed before `nix build` — flakes only see git-tracked files. + +## Architecture + +### Repository Layout + +- `qmk_firmware/` — System76's QMK fork (git submodule, do not modify) +- `keymaps/custom/` — Custom keymap, kept separate from the submodule +- `flake.nix` — Dev shell and sandboxed firmware build; uses `self.submodules = true` + +### Custom Keymap Files (`keymaps/custom/`) + +- `keymap.c` — Keymaps, layer override, modifier rewriting, process_record_user +- `os_detect.c` — OS auto-detection polling, os_mode/os_mode_manual definitions, RGB flash +- `os_mode.h` — Shared `os_mode_t` enum and extern declarations +- `rules.mk` — Enables OS_DETECTION and adds `SRC += os_detect.c` + +### Key Constraints + +**keymap_introspection.c textually `#include`s keymap.c** — this means: +- Headers in `keymaps/custom/` can't be `#include`d from `keymap.c` (the compiler resolves includes relative to `quantum/keymap_introspection.c`, not the included file) +- Variables shared between `keymap.c` and other files must be non-static with `extern` declarations +- Guard OS-specific code with `#ifdef OS_DETECTION_ENABLE` if it uses types from guarded headers, or put it in a separate `.c` file (preferred) + +**keyboard_post_init_user is taken** by `launch_3.c` — use `matrix_init_user` instead. + +**DYNAMIC_KEYMAP_ENABLE** causes EEPROM to override PROGMEM keymaps — `dynamic_keymap_reset()` in `matrix_init_user` forces PROGMEM back. + +### Modifier Rewriting System + +`mod_rewrite_t` structs define from→to modifier translations. `normalize_mods()` collapses left/right modifier variants into a single nibble for comparison. `mods_match()` does exact mod matching and skips rewrites when FN layer is active. + +### OS Mode + +`os_mode_t` enum (NONE/LINUX/MAC/WINDOWS). Auto-detected via USB enumeration polling after 2s. Manual cycling via FN+Del (`CK_OSMODE`). Once manually set, `os_mode_manual` prevents auto-detection override. RGB indicator: red=none, green=linux, yellow=mac, blue=windows. + +OS detection on System76's QMK fork often misidentifies Linux as Windows — both currently map to `OS_MODE_LINUX`. + +### Nix Build + +The flake copies `keymaps/custom/` into the QMK tree during `postUnpack`. `patchShebangs` is needed for `uf2conv.py` in the sandbox. `python3` and `python3Packages.appdirs` are required nativeBuildInputs. diff --git a/README.md b/README.md new file mode 100644 index 0000000..df8c861 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# System76 Launch 3 Custom Firmware + +Custom QMK firmware for the [System76 Launch 3](https://system76.com/accessories/launch) keyboard, built with Nix. + +## Features + +### OS Mode + +The keyboard auto-detects the host OS on plug-in and rewrites shortcuts so that muscle memory from Linux (Ctrl-based shortcuts) works on macOS. Mode can also be cycled manually with **FN+Del**. + +Four modes, indicated by RGB flash color: + +| Mode | Color | Behavior | +|---------|--------|----------| +| None | Red | No rewrites, all keys pass through | +| Linux | Green | Super+Up → Super+W (KDE overview) | +| Mac | Yellow | Full Ctrl→Cmd rewrite (see table below) | +| Windows | Blue | Super+Up → Super+Tab (Task View) | + +#### Mac Mode Shortcut Rewrites + +| Input (Linux muscle memory) | Output (macOS) | Action | +|---|---|---| +| Ctrl+C/V | Cmd+C/V | Copy/Paste | +| Ctrl+Shift+C/V | Cmd+C/V | Terminal copy/paste | +| Ctrl+X/A/S/F | Cmd+X/A/S/F | Cut, Select All, Save, Find | +| Ctrl+W/L/R | Cmd+W/L/R | Close tab, Address bar, Reload | +| Ctrl+Z/T/N | Cmd+Z/T/N | Undo, New tab, New window | +| Ctrl+Shift+Z/T/N | Cmd+Shift+Z/T/N | Redo, Reopen tab, Incognito | +| Ctrl+Left/Right | Alt+Left/Right | Word navigation | +| Ctrl+Alt+Left/Right | Ctrl+Left/Right | Switch workspace | +| Ctrl+Backspace | Alt+Backspace | Delete word | +| Home/End | Cmd+Left/Right | Line start/end | +| Alt+F4 | Cmd+Q | Quit app | +| Alt+Tab | Cmd+Tab | App switcher | +| Super tap | Cmd+Space | Spotlight | +| Super+Up | Ctrl+Up | Mission Control | + +#### Layer Override + +When any modifier is held (in any mode except None), the FN layer is bypassed — keys always resolve from Layer 0. This allows bypassing shortcut rewrites when wanted. For example, if you want to send Ctrl+c on mac (which is normally rewritten to cmd+c), simply use fn+Ctrl+c instead. + +### Layer 1 (FN Layer) + +| Key | FN+ | +|-----|-----| +| ESC | Print Screen | +| F1/F2/F3 | Mute / Vol Down / Vol Up | +| 1/2/3 | Play-Pause / Prev / Next | +| 0/-/= | LED Toggle / LED Down / LED Up | +| Del | Cycle OS Mode | +| Arrows | Home / PgDn / PgUp / End | + +Modifier keys (Shift, Ctrl, GUI, Alt) cannot be rebound on Layer 1 due to the layer override behavior. + +## Building + +Requires [Nix](https://nixos.org/) with flakes enabled. + +```sh +nix build +``` + +The `.uf2` firmware file is output to `./result/`. + +### Flashing + +1. Put the keyboard into bootloader mode (press the reset button on the bottom) +2. Copy the `.uf2` file to the mounted USB drive + +### Development Shell + +```sh +nix develop +qmk compile -kb system76/launch_3 -km custom +``` + +## Project Structure + +``` +keymaps/custom/ +├── keymap.c # Keymaps, shortcut rewrites, layer override +├── os_detect.c # OS auto-detection, RGB flash indicator +├── os_mode.h # OS mode enum and shared state +└── rules.mk # QMK build flags +qmk_firmware/ # System76 QMK fork (git submodule) +flake.nix # Nix build system +```