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 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 stays running as a menu bar utility. 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.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hid-keymap",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "macOS HID keyboard event viewer and key mapper",
5
5
  "bin": {
6
6
  "hid-keymap": "bin/hid-keymap.js"
@@ -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.flags = target.modifiers.reduce(flags) { $0.union($1.flag) }
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.variableLength)
533
- item.button?.title = "⌨"
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 options = try parseArguments(Array(CommandLine.arguments.dropFirst()))
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)