Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ jobs:
rustup toolchain install stable --profile minimal --component clippy,rustfmt
rustup default stable
- name: Format
run: cargo fmt --all -- --check 2>&1 | tee fmt.log
shell: bash
run: set -o pipefail; cargo fmt --all -- --check 2>&1 | tee fmt.log
- name: Build
run: cargo build --locked 2>&1 | tee build.log
shell: bash
run: set -o pipefail; cargo build --locked 2>&1 | tee build.log
- name: Clippy (warnings are errors)
run: cargo clippy --all-targets --locked -- -D warnings 2>&1 | tee clippy.log
shell: bash
run: set -o pipefail; cargo clippy --all-targets --locked -- -D warnings 2>&1 | tee clippy.log
- name: Test
run: cargo test --locked 2>&1 | tee test.log
shell: bash
run: set -o pipefail; cargo test --locked 2>&1 | tee test.log
- name: Upload CI diagnostics
if: failure()
uses: actions/upload-artifact@v4
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to Keyjitsu are documented here. This project follows

## [Unreleased]

## [0.10.0-beta.1] - 2026-10-18

### Added
- Native CI and release archives for macOS, Linux x86_64 and Windows x86_64.
- Cross-platform Voyager discovery and firmware-target validation, including
rejection of ambiguous or non-Voyager bootloaders and mismatched MCU images.
- Regression coverage for safe generated QMK `rules.mk` assignments.

### Changed
- Updated release dependencies and removed obsolete audit exceptions.
- Documented that stock Oryx firmware cannot expose enough state for an exact
readback; Keyjitsu-authored firmware embeds a state marker instead.

### Fixed
- CI failures now propagate through diagnostic log pipelines on every runner.
- Local builds reject executable or unknown Oryx make rules instead of copying
them into generated firmware sources.
- Disconnect and shutdown paths clear transient input/effect state, release RGB
ownership and report worker panics instead of silently discarding them.

## [0.9.14] - 2026-10-08

### Changed
Expand Down
59 changes: 25 additions & 34 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "keyjitsu"
version = "0.9.14"
version = "0.10.0-beta.1"
edition = "2021"
description = "GUI and CLI manager for the ZSA Voyager: live layout, heatmap, per-key RGB and effects, local QMK builds with tap dances, a layer minimap. No daemon, no cloud."
repository = "https://github.com/martinezooo/keyjitsu"
Expand All @@ -27,7 +27,7 @@ eframe = "0.32.3"
tempfile = "3.27"

zapp-core = { git = "https://github.com/zsa/zapp" }
zip = { version = "2", default-features = false, features = ["deflate"] }
zip = { version = "9", default-features = false, features = ["deflate"] }

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.10"
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,45 @@ of treating the last saved app config as device truth. It never writes back to
the portal. So remapping here does not carry over to Oryx, and re-flashing from
Oryx later would overwrite your Keyjitsu changes.

## Device state, Oryx, and mixing workflows

There is an important limitation in ZSA's current Oryx raw-HID protocol:
**stock Oryx firmware does not expose the complete keymap back to the desktop
app**. The keyboard can report runtime information and its firmware identity
(`layout-hash/revision-hash`), but there is no supported HID command that
returns every key assignment, layer definition, combo, and persistent RGB
setting from the compiled firmware.

For stock Oryx firmware, keyjitsu can use the `hash/revision` reported by the
physical keyboard to load the matching Oryx layout. That is a reconstruction
from Oryx data selected by the device's own revision id; it is **not** a full
keymap extracted from the firmware image itself.

Keyjitsu-built firmware adds its own compact state id to the firmware serial.
That lets keyjitsu reconstruct the exact Keyjitsu-authored additions from its
matching local firmware-state record. It still does not make stock Oryx
firmware suddenly readable as a complete keymap.

### Practical rule: pick one source of authoring truth

For the least surprising workflow, use **either Oryx or keyjitsu as the main
place where you author a keyboard configuration**.

- If you edit and flash in Oryx, treat that Oryx revision as the source of
truth. Keyjitsu can recognize the revision reported by the keyboard and load
the corresponding Oryx layout.
- If you edit and flash in keyjitsu, treat the Keyjitsu firmware state as the
source of truth. Those local-only edits are not written back to Oryx.
- Re-flashing from Oryx overwrites Keyjitsu-only firmware changes.
- Re-flashing from keyjitsu does not update your Oryx layout.
- Switching between the two workflows is possible, but save the configuration
you want to keep before flashing and expect the two representations to
diverge unless you deliberately synchronize them.

Live/device status should always be driven by the **currently connected
keyboard's identity**. A saved layout or restore point must never silently
replace what Live claims is on the physical keyboard.

## How it works

- **Protocol.** ZSA's open Oryx raw-HID protocol v4 (32-byte reports, usage
Expand Down
2 changes: 1 addition & 1 deletion src/cmd_autolayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pub fn running_apps() -> Vec<(String, String)> {
}
}
}
apps.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
apps.sort_by_key(|a| a.0.to_lowercase());
apps.dedup_by(|a, b| a.1 == b.1);
apps
}
Expand Down
Loading
Loading