Skip to content
Closed
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
32 changes: 22 additions & 10 deletions Sources/WLKit/KeymapManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,25 @@ public enum KeymapManager {
return config
}

/// Which of `count` profiles `activeProfileId` names. The id is trusted
/// only when it is a real index: a Creator Micro 2 has been seen reporting
/// -1 here, and an upper-bound-only check let that straight through to a
/// subscript that took the app down the moment the bridge started. Out of
/// range in either direction falls back to the first profile, which is
/// what an oversized id always did.
static func activeProfileIndex(_ config: [String: Any], among count: Int) -> Int {
let id = config["activeProfileId"] as? Int ?? 0
return (0..<count).contains(id) ? id : 0
}

/// `layer_index` from `device.status` is 1-based against this list, so the
/// active layer is the first one.
static func activeLayerKeymap(_ config: [String: Any]) -> [[String]]? {
let index = config["activeProfileId"] as? Int ?? 0
guard let profiles = config["profiles"] as? [[String: Any]] else { return nil }
let profile = index < profiles.count ? profiles[index] : profiles.first
guard let layers = profile?["layers"] as? [[String: Any]],
guard let profiles = config["profiles"] as? [[String: Any]], !profiles.isEmpty else {
return nil
}
let profile = profiles[activeProfileIndex(config, among: profiles.count)]
guard let layers = profile["layers"] as? [[String: Any]],
let layer = layers.first,
let layout = layer["layout"] as? [String: Any],
let keymap = layout["keymap"] as? [[String]]
Expand All @@ -96,10 +108,11 @@ public enum KeymapManager {
}

static func activeLayerLayout(_ config: [String: Any]) -> [String: Any]? {
let index = config["activeProfileId"] as? Int ?? 0
guard let profiles = config["profiles"] as? [[String: Any]] else { return nil }
let profile = index < profiles.count ? profiles[index] : profiles.first
guard let layers = profile?["layers"] as? [[String: Any]],
guard let profiles = config["profiles"] as? [[String: Any]], !profiles.isEmpty else {
return nil
}
let profile = profiles[activeProfileIndex(config, among: profiles.count)]
guard let layers = profile["layers"] as? [[String: Any]],
let layer = layers.first
else { return nil }
return layer["layout"] as? [String: Any]
Expand Down Expand Up @@ -142,11 +155,10 @@ public enum KeymapManager {
/// joystick and lighting setting untouched.
public static func withAgentKeymap(_ config: [String: Any]) throws -> [String: Any] {
var next = config
let index = next["activeProfileId"] as? Int ?? 0
guard var profiles = next["profiles"] as? [[String: Any]], !profiles.isEmpty else {
throw Failure.noProfiles
}
let profileIndex = index < profiles.count ? index : 0
let profileIndex = activeProfileIndex(next, among: profiles.count)
var profile = profiles[profileIndex]
guard var layers = profile["layers"] as? [[String: Any]], !layers.isEmpty else {
throw Failure.noProfiles
Expand Down
7 changes: 3 additions & 4 deletions Sources/WLKit/PadEmulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,9 @@ public final class PadEmulator: ObservableObject {
dict.values.forEach(scan)
}
}
let index = keymap["activeProfileId"] as? Int ?? 0
if let profiles = keymap["profiles"] as? [[String: Any]] {
let profile = index < profiles.count ? profiles[index] : profiles.first
if let layers = profile?["layers"] as? [[String: Any]], let layer = layers.first {
if let profiles = keymap["profiles"] as? [[String: Any]], !profiles.isEmpty {
let profile = profiles[KeymapManager.activeProfileIndex(keymap, among: profiles.count)]
if let layers = profile["layers"] as? [[String: Any]], let layer = layers.first {
scan(layer)
}
}
Expand Down
25 changes: 25 additions & 0 deletions Tests/WLKitTests/KeymapManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ final class KeymapManagerTests: XCTestCase {
XCTAssertNotNil(layers[0]["lights"], "per-layer lighting survives")
}

/// A Creator Micro 2 has been seen reporting `activeProfileId: -1`. The
/// old check only caught ids that were too large, so -1 went straight into
/// a subscript and took the app down the moment the bridge started.
func testNegativeActiveProfileIdFallsBackToTheFirstProfile() throws {
var config = try backupKeymap()
config["activeProfileId"] = -1

XCTAssertEqual(KeymapManager.activeLayerKeymap(config)?[0], ["KC_F13", "KC_F14"])
XCTAssertNotNil(KeymapManager.activeLayerLayout(config))
XCTAssertFalse(KeymapManager.isAgentKeymapApplied(config))

let next = try KeymapManager.withAgentKeymap(config)
XCTAssertTrue(KeymapManager.isAgentKeymapApplied(next))
XCTAssertEqual(next["activeProfileId"] as? Int, -1,
"the id is the pad's to own; only the lookup is clamped")
}

func testOversizedActiveProfileIdFallsBackToTheFirstProfile() throws {
var config = try backupKeymap()
config["activeProfileId"] = 99

XCTAssertEqual(KeymapManager.activeLayerKeymap(config)?[0], ["KC_F13", "KC_F14"])
XCTAssertTrue(KeymapManager.isAgentKeymapApplied(try KeymapManager.withAgentKeymap(config)))
}

func testMalformedInputIsRejectedRatherThanSilentlyAccepted() {
XCTAssertThrowsError(try KeymapManager.parse(["nope": 1]))
XCTAssertThrowsError(try KeymapManager.parse(["data": "not json"]))
Expand Down