feat: add definputdevices and (device N) switch conditions#12
feat: add definputdevices and (device N) switch conditions#12
Conversation
Code Review:
|
Code ReviewNice work on the architecture — the Bugs / Correctness1. Hash matching uses substring comparison ( let hash_matches = matcher
.hash
.as_ref()
.map_or(true, |h| format!("{:x}", kb.hash).contains(h.as_str()));This means 2. Vendor/product ID range not validated (
3. Duplicate property silently overwrites ( 1 ((name "Kinesis") (name "Advantage360"))The second 4. Multi-ID match for a single device takes last writer silently ( In Design / API5. The 6. let input_devices_vec: Option<Vec<_>> = k
.input_devices
.as_ref()
.map(|m| m.iter().map(|(k, v)| (*k, v.clone())).collect());This clones all the matchers just to pass them as a slice. You could pass 7. Hot-plugging not supported — should document The Testing8. No unit tests for All the existing #[test]
fn switch_device_id() {
let id1 = NonZeroU8::new(1).unwrap();
let id2 = NonZeroU8::new(2).unwrap();
let (op1, op2) = OpCode::new_device(id1);
assert!(evaluate_boolean(&[op1, op2], ..., Some(id1)));
assert!(!evaluate_boolean(&[op1, op2], ..., Some(id2)));
assert!(!evaluate_boolean(&[op1, op2], ..., None));
}9. No integration test for A test in Minor / Style10. 11. Overall this is a clean implementation of a complex feature. The main things I'd want addressed before merge: the hash substring matching bug (#1), vendor/product ID range validation (#2), and adding at least basic unit tests for the new opcode path (#8). |
Code Review:
|
Code Review —
|
Code Review:
|
| Category | Item | Severity |
|---|---|---|
| Bug | Live-reload leaves KbdIn.device_hash_to_id stale |
Medium |
| Bug | Hash comparison is case-sensitive | Medium |
| Bug | as u16 silent truncation risk |
Low–Medium |
| Design | (device N) silently fails on non-macOS |
Medium |
| Design | current_device_id as public mutable field |
Low |
| Design | too_many_arguments suppressions on hot path |
Low |
| Minor | Per-startup string allocation for hash comparison | Negligible |
| Minor | Missing comment on device_hash: 0 sentinel |
Negligible |
The live-reload staleness and hash case-sensitivity are the two issues most likely to create confusing user-facing behavior. The rest are either low-risk or polish items. Overall the architecture is sound and the implementation is clean.
Code Review:
|
Code Review:
|
Code Review:
|
PR Review:
|
| Category | Finding | Severity |
|---|---|---|
| Correctness | Last-match-wins vs first-match-wins for overlapping device matchers | Medium |
| Dead code | has_device_id! macro unused |
Low |
| API | current_device_id pub field; verbose #[cfg] |
Low |
| Tests | No sim-level integration tests; no not/and/or tests |
Medium |
| Docs | --list flag reference; hot-reload note in sample |
Low |
The core implementation is solid and the approach is consistent with how LAYER_VAL/BASE_LAYER_VAL work. The last-match-wins behavior (finding #1) and missing integration tests (finding #6) are the most important things to address before merging.
Code ReviewThis is a well-structured PR with a clear design rationale. The Bug: Non-deterministic winner when a physical device matches multiple configured IDsIn let input_devices_vec: Option<Vec<_>> = k
.input_devices
.as_ref()
.map(|m| m.iter().map(|(k, v)| (*k, v.clone())).collect());
Suggestion: Use a No validation that hash strings are valid hexIn "hash" => {
...
matcher.hash = Some(prop_val.to_string());
}A user typo like
|
Code Review:
|
4e55e5b to
31d8855
Compare
Code Review:
|
| Category | Severity |
|---|---|
expect on hot-path decode |
Medium — potential panic in release |
| Silent miss on device match | Medium — bad UX, hard to debug |
name case sensitivity undocumented |
Medium |
current_device_id pub field |
Low — encapsulation |
| Missing parser tests | Low-Medium |
cfg double-negative |
Low — readability |
Redundant eq_ignore_ascii_case |
Nit |
The architecture is sound and the macOS end-to-end plumbing is clean. Address the hot-path expect and the silent-miss diagnostic before merging, and add at least a few parser error-case tests.
… no matches (jtroo#1986) ## Summary - When `macos-dev-names-include` or `macos-dev-names-exclude` is configured but no connected devices match, kanata was falling back to `register_device("")` which intercepts **all** devices - This caused kanata to grab the wrong keyboard (e.g., the internal MacBook keyboard when an external keyboard was specified but not connected) - Now kanata only falls back to the catch-all registration when no device filter is configured at all — when a filter is present but nothing matches, kanata exits with an error Fixes jtroo#1479 ## Test plan - [x] Configured `macos-dev-names-include` with a nonexistent device name — kanata now errors out instead of grabbing all devices - [x] Configured `macos-dev-names-include` with a real connected device — kanata correctly grabs only that device and remapping works - [x] No include/exclude list — default behavior unchanged (grabs all devices) ## Possible follow-up Linux has `linux-continue-if-no-devs-found` which lets kanata wait for devices instead of exiting. A cross-platform `continue-if-no-devs-found` option could be added to support the multi-instance use case where kanata should stay running and pick up the device when it connects. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to 0.3.0 (jtroo#1987) ## Describe the changes Bump `karabiner-driverkit` to 0.3.0 and improve `--list` output for BLE device users. The driverkit update adds a fallback to the IORegistry `"Product"` property when `kIOHIDProductKey` is empty, fixing BLE HID keyboards (e.g. ZMK-based boards) showing blank `product_key` in `kanata --list`. The `--list` output changes: - Only show the "use hash values" hint when there are actually devices with empty names - Warn when Karabiner virtual devices are detected that hash-based matching may not be stable (Karabiner re-exposes all devices with a shared hash) Fixes jtroo#1944 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-device key mappings via a new top-level `definputdevices`
configuration block and `(device N)` switch condition. Implement
end-to-end on macOS using driverkit 0.3.0's per-event device_hash.
Config syntax:
(definputdevices
1 ((name "Apple Internal Keyboard"))
2 ((name "Go60") (vendor_id 0x1D50)))
(defsrc a)
(deflayer base
(switch
((device 1)) x break
((device 2)) y break
() a break))
Design:
- IDs are NonZeroU8 (1-255); Option<NonZeroU8> fits in 1 byte
- definputdevices required when (device N) is used; undefined IDs
error at parse time
- Matchers use ALL-of semantics with definition-order precedence
- Hash values validated as hex, matched case-insensitively
- vendor_id/product_id validated as u16 (USB range)
- New DEVICE_VAL (855) sentinel opcode, consistent with LAYER_VAL
- device_id on KeyEvent conditionally compiled out for Windows LLHOOK;
accessed via device_id()/set_device_id() helpers
- Bump karabiner-driverkit to 0.3.0 for per-event device_hash
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
31d8855 to
d1e4123
Compare
Code Review:
|
| Severity | Issue |
|---|---|
| Bug (minor) | Hash format not zero-padded — may silently fail if --list output diverges |
| Design | Box<dyn Error> in parse_hex_or_decimal_u16 allocates needlessly |
| Readability | Double-negation cfg guard needs a clarifying comment |
| Test coverage | No unit tests for parse_definputdevices parser |
| UX | Empty definputdevices block should warn or error |
| Nit | Redundant eq_ignore_ascii_case on pre-normalized string |
The core design and implementation are solid. The hash formatting issue is the only one I'd call a real bug risk — the rest are polish and maintainability.
Describe the changes
Add
definputdevicesand(device N)switch conditions for per-device mappings.definputdevicesmaps user-chosen numeric device IDs to matcher lists.(device N)checks whether the current input event came from deviceN.Implement the end-to-end path on macOS by matching
definputdevicesentries against connected devices at startup, then tagging each input event with the configured device ID before switch evaluation.Also bump
karabiner-driverkitto 0.3.0 so macOS input events exposedevice_hash, which is used to translate matched devices into per-event IDs.Related to jtroo#1944 and the earlier attempt in jtroo#1974.
Checklist
definputdevices, unknown IDs, and duplicate matcher propertiesswitch_device_matchandswitch_device_opcode_roundtrip