From 77d3cec7ff7dc7f2389d372d1d10dba9b9d6aaec Mon Sep 17 00:00:00 2001 From: Carlos Cabanero Date: Thu, 2 Nov 2023 17:19:25 -0400 Subject: [PATCH] Key Presses with Custom String input Implements #1888 --- Blink/SpaceController.swift | 2 +- KB/Native/Model/KeyBindingAction.swift | 55 ++++++----- KB/Native/Views/ShortcutsConfigView.swift | 109 +++++++++++++++++++--- 3 files changed, 129 insertions(+), 37 deletions(-) diff --git a/Blink/SpaceController.swift b/Blink/SpaceController.swift index 1b8aa8844..845c34eee 100644 --- a/Blink/SpaceController.swift +++ b/Blink/SpaceController.swift @@ -676,7 +676,7 @@ extension SpaceController { // input.reportStateReset() switch cmd.bindingAction { - case .hex(let hex, comment: _): + case .hex(let hex, stringInput: _, comment: _): input.reportHex(hex) case .press(let keyCode, mods: let mods): input.reportPress(UIKeyModifierFlags(rawValue: mods), keyId: keyCode.id) diff --git a/KB/Native/Model/KeyBindingAction.swift b/KB/Native/Model/KeyBindingAction.swift index 0900287ce..f0ce527c8 100644 --- a/KB/Native/Model/KeyBindingAction.swift +++ b/KB/Native/Model/KeyBindingAction.swift @@ -110,14 +110,14 @@ enum Command: String, Codable, CaseIterable { } enum KeyBindingAction: Codable, Identifiable { - case hex(String, comment: String?) + case hex(String, stringInput: String?, comment: String?) case press(KeyCode, mods: Int) case command(Command) case none var id: String { switch self { - case .hex(let str, _): return "hex-\(str)" + case .hex(let str, _, _): return "hex-\(str)" case .press(let keyCode, let mods): return "press-\(keyCode.id)-\(mods)" case .command(let cmd): return "cmd-\(cmd)" case .none: return "none" @@ -133,12 +133,20 @@ enum KeyBindingAction: Codable, Identifiable { var isCustomHEX: Bool { switch self { - case .hex(_, comment: let comment): + case .hex(_, _, comment: let comment): return comment == nil default: return false } } + var isHexStringInput: Bool { + switch self { + case .hex(_, stringInput: let stringInput, comment: let comment): + return comment == nil && stringInput != nil + default: return false + } + } + static func press(_ keyCode: KeyCode, _ mods: UIKeyModifierFlags) -> KeyBindingAction { KeyBindingAction.press(keyCode, mods: mods.rawValue) } @@ -164,15 +172,15 @@ enum KeyBindingAction: Codable, Identifiable { .press(.f10, []), .press(.f11, []), .press(.f12, []), - .hex("03", comment: "Press ^C"), - .hex("16", comment: "Press ^V"), - .hex("3C", comment: "Press <"), - .hex("3E", comment: "Press >"), - .hex("A7", comment: "Press §"), - .hex("B1", comment: "Press ±"), - .hex("7E", comment: "Press ~"), - .hex("7C", comment: "Press |"), - .hex("5C", comment: "Press \\"), + .hex("03", stringInput: nil, comment: "Press ^C"), + .hex("16", stringInput: nil, comment: "Press ^V"), + .hex("3C", stringInput: nil, comment: "Press <"), + .hex("3E", stringInput: nil, comment: "Press >"), + .hex("A7", stringInput: nil, comment: "Press §"), + .hex("B1", stringInput: nil, comment: "Press ±"), + .hex("7E", stringInput: nil, comment: "Press ~"), + .hex("7C", stringInput: nil, comment: "Press |"), + .hex("5C", stringInput: nil, comment: "Press \\"), .press(.w, [.command]), .press(.t, [.command]), // .press(.left, [.shift, .command]), @@ -186,8 +194,8 @@ enum KeyBindingAction: Codable, Identifiable { var title: String { switch self { - case .hex(let str, comment: let comment): - return comment ?? "Hex: \(str)" + case .hex(let str, stringInput: let stringInput, comment: let comment): + return comment ?? (stringInput != nil ? "String: \(stringInput!)" : "Hex: \(str)") case .press(let keyCode, let mods): var sym = UIKeyModifierFlags(rawValue: mods).toSymbols() sym += keyCode.symbol @@ -199,17 +207,17 @@ enum KeyBindingAction: Codable, Identifiable { var titleWithoutValue: String { switch self { - case .hex(_, comment: let comment): - return comment ?? "Send Hex Code" + case .hex(_, stringInput: let stringInput, comment: let comment): + return comment ?? (stringInput != nil ? "Send Input" : "Send Hex Code") default: return title } } - var hexValue: String { + var hexValues : (String, String?) { switch self { - case .hex(let str, comment: _): - return str - default: return "" + case .hex(let str, stringInput: let stringInput, comment: _): + return (str, stringInput) + default: return ("", nil) } } @@ -219,6 +227,7 @@ enum KeyBindingAction: Codable, Identifiable { case type case hex case value + case stringInput case key case press case mods @@ -230,9 +239,10 @@ enum KeyBindingAction: Codable, Identifiable { func encode(to encoder: Encoder) throws { var c = encoder.container(keyedBy: Keys.self) switch self { - case .hex(let str, comment: let comment): + case .hex(let str, stringInput: let stringInput, comment: let comment): try c.encode(Keys.hex.stringValue, forKey: .type) try c.encode(str, forKey: .value) + try c.encodeIfPresent(stringInput, forKey: .stringInput) try c.encodeIfPresent(comment, forKey: .comment) case .press(let keyCode, let mods): try c.encode(Keys.press.stringValue, forKey: .type) @@ -254,8 +264,9 @@ enum KeyBindingAction: Codable, Identifiable { switch k { case .hex: let hex = try c.decode(String.self, forKey: .value) + let stringInput = try c.decodeIfPresent(String.self, forKey: .stringInput) let comment = try c.decodeIfPresent(String.self, forKey: .comment) - self = .hex(hex, comment: comment) + self = .hex(hex, stringInput: stringInput, comment: comment) case .press: let keyCode = try c.decode(KeyCode.self, forKey: .key) let mods = try c.decode(Int.self, forKey: .mods) diff --git a/KB/Native/Views/ShortcutsConfigView.swift b/KB/Native/Views/ShortcutsConfigView.swift index dad471981..58fe2fb1b 100644 --- a/KB/Native/Views/ShortcutsConfigView.swift +++ b/KB/Native/Views/ShortcutsConfigView.swift @@ -51,6 +51,7 @@ struct ActionsList: View { } else { Section(header: Text("Send")) { self._rowHex(action: self.action) + self._rowCustomInput(action: self.action) } Section(header: Text("Press")) { ForEach(pressList, id: \.id) { ka in @@ -66,7 +67,8 @@ struct ActionsList: View { private func _rowHex(action: KeyBindingAction) -> some View { var checked = false var value = "" - if case .hex(let val, let comment) = action, comment == nil { + + if case .hex(let val, let input, let comment) = action, input == nil, comment == nil { checked = true value = val } @@ -76,7 +78,30 @@ struct ActionsList: View { Checkmark(checked: checked) }.overlay( Button(action: { - self.action = .hex(value, comment: nil) + self.action = .hex(value, stringInput: nil, comment: nil) + self.updatedAt = Date() + }, label: { EmptyView() } + ) + ) + } + + private func _rowCustomInput(action: KeyBindingAction) -> some View { + var checked = false + var value = "" + var stringInput = "" + + if case .hex(let val, let input, let comment) = action, input != nil, comment == nil { + checked = true + value = val + stringInput = input! + } + return HStack { + Text("Custom String") + Spacer() + Checkmark(checked: checked) + }.overlay( + Button(action: { + self.action = .hex(value, stringInput: stringInput, comment: nil) self.updatedAt = Date() }, label: { EmptyView() } ) @@ -132,22 +157,77 @@ class HexFormatter: Formatter { .prefix(1000) ) } + + func stringToHexString(_ input: String) -> String { + var result = "" + var currentIndex = input.startIndex + + while currentIndex < input.endIndex { + let currentCharacter = input[currentIndex] + + if currentCharacter == "\\" && input.index(currentIndex, offsetBy: 1) < input.endIndex && input[input.index(currentIndex, offsetBy: 1)] == "x" { + // Skip "\x" + currentIndex = input.index(currentIndex, offsetBy: 2) + var hexSubstring = "" + for _ in 0..<2 { + if currentIndex < input.endIndex, "0123456789ABCDEFabcdef".contains(input[currentIndex]) { + hexSubstring.append(input[currentIndex]) + currentIndex = input.index(after: currentIndex) + } else { + break + } + } + // Ensure is double digit. + if hexSubstring.count == 1 { + hexSubstring = "0" + hexSubstring + } + result.append(hexSubstring) + } else { + let hexValue = String(format: "%02X", currentCharacter.unicodeScalars.first?.value ?? 0) + result.append(hexValue) + currentIndex = input.index(after: currentIndex) + } + } + + return result + } } struct HexEditorView: View { @ObservedObject var shortcut: KeyShortcut - @State var value: String = "" + @State var input: String = "" + var value: String { shortcut.action.hexValues.0 } + var stringInput: String? { shortcut.action.hexValues.1 } private let _formatter = HexFormatter() var body: some View { - TextField("HEX", text: $value, onEditingChanged: { _ in - // Whenever the view is first shown, enter pressed, tap back on Navigation Link & TextField selected - // Update the HEX code using the HexFormatter to only accept valid HEX encoded Strings - value = _formatter.hexString(str: value) - shortcut.action = .hex(value, comment: nil) - }) - .disableAutocorrection(true) - .keyboardType(.asciiCapable) + _editor() + .onAppear { + if let stringInput = stringInput { + self.input = stringInput + } else { + self.input = value + } + } + .disableAutocorrection(true) + .keyboardType(.asciiCapable) + } + + private func _editor() -> some View { + if self.stringInput != nil { + return TextField("Custom String", text: $input, + onEditingChanged: { _ in + let value = _formatter.stringToHexString(input) + shortcut.action = .hex(value, stringInput: input, comment: nil) + }) + } else { + return TextField("HEX", text: $input, onEditingChanged: { _ in + // Whenever the view is first shown, enter pressed, tap back on Navigation Link & TextField selected + // Update the HEX code using the HexFormatter to only accept valid HEX encoded Strings + let value = _formatter.hexString(str: input) + shortcut.action = .hex(value, stringInput: nil, comment: nil) + }) + } } } @@ -171,15 +251,16 @@ struct ShortcutConfigView: View { } Section( header: Text("Action"), - footer: Text(self.shortcut.action.isCustomHEX ? "Use hex encoded sequence" : "")) + footer: Text(self.shortcut.action.isCustomHEX ? (self.shortcut.action.isHexStringInput ? + "Use string sequence, with \\x for escape characters." : + "Use hex encoded sequence") : "")) { DefaultRow(title: shortcut.action.titleWithoutValue) { ActionsList(action: self.$shortcut.action, commandsMode: self.commandsMode) } if self.shortcut.action.isCustomHEX { HexEditorView( - shortcut: self.shortcut, - value: self.shortcut.action.hexValue + shortcut: self.shortcut ) } }