hid-keymap 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/native/hid-keymap +0 -0
- package/package.json +1 -1
- package/src/hid-keymap.swift +74 -5
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ hid-keymap --map 0x01/0x97=key:escape
|
|
|
22
22
|
hid-keymap --map 0x01/0x97=left_command+key:space
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Mapping mode
|
|
25
|
+
Mapping mode launches a separate menu bar utility and returns to the shell. Open its keyboard icon to see the configured source-to-target mappings, current status, and controls to stop, restart, or quit. The `--monitor` Event Viewer remains in the terminal and prints raw HID usage/value events.
|
|
26
26
|
|
|
27
27
|
Use `hid-keymap --list-targets` to list accepted target names. By default, it listens to all keyboard collections; add `--device VID/PID` to scope a monitor or mapping to one keyboard.
|
|
28
28
|
|
package/native/hid-keymap
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/hid-keymap.swift
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import ApplicationServices
|
|
2
2
|
import AppKit
|
|
3
3
|
import CoreGraphics
|
|
4
|
+
import Darwin
|
|
4
5
|
import Foundation
|
|
5
6
|
import IOKit.hid
|
|
6
7
|
import IOKit.hidsystem
|
|
7
8
|
|
|
8
9
|
private let genericDesktopPage = 0x01
|
|
9
10
|
private let keyboardUsage = 0x06
|
|
11
|
+
private let appVersion = "0.2.1"
|
|
10
12
|
|
|
11
13
|
private struct HIDCode: Hashable, CustomStringConvertible {
|
|
12
14
|
let page: UInt32
|
|
@@ -184,6 +186,51 @@ private func parseInteger(_ raw: String) -> UInt32? {
|
|
|
184
186
|
return UInt32(raw, radix: 10)
|
|
185
187
|
}
|
|
186
188
|
|
|
189
|
+
private func launchDetachedMapping(_ arguments: [String]) throws {
|
|
190
|
+
let executablePath = CommandLine.arguments[0]
|
|
191
|
+
let childArguments = [executablePath, "--_background_mapping"] + arguments
|
|
192
|
+
var cArguments = childArguments.map { strdup($0) }
|
|
193
|
+
guard cArguments.allSatisfy({ $0 != nil }) else {
|
|
194
|
+
cArguments.forEach { if let argument = $0 { free(argument) } }
|
|
195
|
+
throw CLIError("Could not prepare background process arguments")
|
|
196
|
+
}
|
|
197
|
+
cArguments.append(nil)
|
|
198
|
+
defer { cArguments.forEach { if let argument = $0 { free(argument) } } }
|
|
199
|
+
|
|
200
|
+
var fileActions: posix_spawn_file_actions_t?
|
|
201
|
+
var spawnAttributes: posix_spawnattr_t?
|
|
202
|
+
guard posix_spawn_file_actions_init(&fileActions) == 0 else {
|
|
203
|
+
throw CLIError("Could not initialize background process file actions")
|
|
204
|
+
}
|
|
205
|
+
defer { posix_spawn_file_actions_destroy(&fileActions) }
|
|
206
|
+
guard posix_spawnattr_init(&spawnAttributes) == 0 else {
|
|
207
|
+
throw CLIError("Could not initialize background process attributes")
|
|
208
|
+
}
|
|
209
|
+
defer { posix_spawnattr_destroy(&spawnAttributes) }
|
|
210
|
+
|
|
211
|
+
let redirectInput = posix_spawn_file_actions_addopen(&fileActions, STDIN_FILENO, "/dev/null", O_RDONLY, 0)
|
|
212
|
+
let redirectOutput = posix_spawn_file_actions_addopen(&fileActions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)
|
|
213
|
+
let redirectError = posix_spawn_file_actions_addopen(&fileActions, STDERR_FILENO, "/dev/null", O_WRONLY, 0)
|
|
214
|
+
guard redirectInput == 0, redirectOutput == 0, redirectError == 0 else {
|
|
215
|
+
throw CLIError("Could not detach background process from the terminal")
|
|
216
|
+
}
|
|
217
|
+
guard posix_spawnattr_setflags(&spawnAttributes, Int16(POSIX_SPAWN_SETSID)) == 0 else {
|
|
218
|
+
throw CLIError("Could not create a detached session for the mapping process")
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var childPID: pid_t = 0
|
|
222
|
+
let result = executablePath.withCString { path in
|
|
223
|
+
cArguments.withUnsafeBufferPointer { buffer in
|
|
224
|
+
posix_spawn(&childPID, path, &fileActions, &spawnAttributes, buffer.baseAddress, environ)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
guard result == 0 else {
|
|
228
|
+
throw CLIError("Could not start the menu bar mapping process (system error \(result))")
|
|
229
|
+
}
|
|
230
|
+
print("Started hid-keymap mapping process (pid \(childPID)). Open the keyboard icon in the menu bar.")
|
|
231
|
+
fflush(stdout)
|
|
232
|
+
}
|
|
233
|
+
|
|
187
234
|
private struct CLIError: Error, CustomStringConvertible {
|
|
188
235
|
let message: String
|
|
189
236
|
init(_ message: String) { self.message = message }
|
|
@@ -199,6 +246,10 @@ private func parseArguments(_ arguments: [String]) throws -> CLIOptions {
|
|
|
199
246
|
printUsage()
|
|
200
247
|
exit(EXIT_SUCCESS)
|
|
201
248
|
}
|
|
249
|
+
if arguments == ["--version"] || arguments == ["-V"] {
|
|
250
|
+
print("hid-keymap \(appVersion)")
|
|
251
|
+
exit(EXIT_SUCCESS)
|
|
252
|
+
}
|
|
202
253
|
if arguments == ["--list-targets"] {
|
|
203
254
|
printTargets()
|
|
204
255
|
exit(EXIT_SUCCESS)
|
|
@@ -247,7 +298,7 @@ private func parseArguments(_ arguments: [String]) throws -> CLIOptions {
|
|
|
247
298
|
|
|
248
299
|
private func printUsage() {
|
|
249
300
|
print("""
|
|
250
|
-
hid-keymap — HID Event Viewer / keyboard mapper
|
|
301
|
+
hid-keymap \(appVersion) — HID Event Viewer / keyboard mapper
|
|
251
302
|
|
|
252
303
|
Usage:
|
|
253
304
|
hid-keymap --monitor [--device VID/PID]
|
|
@@ -256,6 +307,8 @@ private func printUsage() {
|
|
|
256
307
|
Map one or more non-keyboard HID state usages to held keys or modifiers.
|
|
257
308
|
hid-keymap --list-targets
|
|
258
309
|
List supported target key names.
|
|
310
|
+
hid-keymap --version
|
|
311
|
+
Print the installed version.
|
|
259
312
|
|
|
260
313
|
Example:
|
|
261
314
|
hid-keymap --monitor
|
|
@@ -462,7 +515,12 @@ private final class HIDMapper {
|
|
|
462
515
|
fputs("Could not create event for \(target)\n", stderr)
|
|
463
516
|
return
|
|
464
517
|
}
|
|
465
|
-
event
|
|
518
|
+
// A modifier's key-up event must report that the released modifier is
|
|
519
|
+
// no longer down. Chord key-up events still carry their chord flags;
|
|
520
|
+
// modifier-only releases rely on the remaining active targets' flags.
|
|
521
|
+
event.flags = down || target.modifierOnly == nil
|
|
522
|
+
? target.modifiers.reduce(flags) { $0.union($1.flag) }
|
|
523
|
+
: flags
|
|
466
524
|
event.post(tap: .cghidEventTap)
|
|
467
525
|
}
|
|
468
526
|
|
|
@@ -529,8 +587,14 @@ private final class MappingMenuApp: NSObject, NSApplicationDelegate {
|
|
|
529
587
|
}
|
|
530
588
|
|
|
531
589
|
private func buildMenu() {
|
|
532
|
-
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.
|
|
533
|
-
|
|
590
|
+
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
|
|
591
|
+
if let image = NSImage(systemSymbolName: "keyboard", accessibilityDescription: "hid-keymap")?
|
|
592
|
+
.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 18, weight: .medium)) {
|
|
593
|
+
image.isTemplate = true
|
|
594
|
+
item.button?.image = image
|
|
595
|
+
} else {
|
|
596
|
+
item.button?.title = "⌨"
|
|
597
|
+
}
|
|
534
598
|
item.button?.toolTip = "hid-keymap"
|
|
535
599
|
|
|
536
600
|
let menu = NSMenu()
|
|
@@ -589,11 +653,16 @@ private final class MappingMenuApp: NSObject, NSApplicationDelegate {
|
|
|
589
653
|
enum FnHIDMapMain {
|
|
590
654
|
static func main() {
|
|
591
655
|
do {
|
|
592
|
-
let
|
|
656
|
+
let arguments = Array(CommandLine.arguments.dropFirst())
|
|
657
|
+
let isBackgroundMapping = arguments.first == "--_background_mapping"
|
|
658
|
+
let userArguments = isBackgroundMapping ? Array(arguments.dropFirst()) : arguments
|
|
659
|
+
let options = try parseArguments(userArguments)
|
|
593
660
|
if options.monitor {
|
|
594
661
|
let mapper = HIDMapper(options: options)
|
|
595
662
|
try mapper.start()
|
|
596
663
|
mapper.run()
|
|
664
|
+
} else if !isBackgroundMapping {
|
|
665
|
+
try launchDetachedMapping(userArguments)
|
|
597
666
|
} else {
|
|
598
667
|
let app = NSApplication.shared
|
|
599
668
|
let delegate = MappingMenuApp(options: options)
|