@kelnishi/satmouse-client 0.9.1 → 0.9.4

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.
@@ -119,7 +119,18 @@ type AxisMap = {
119
119
  rz: keyof Vec3;
120
120
  };
121
121
 
122
+ /** Per-device transform overrides. Any field left undefined inherits from global defaults. */
123
+ interface DeviceConfig {
124
+ sensitivity?: Partial<SensitivityConfig>;
125
+ flip?: Partial<FlipConfig>;
126
+ deadZone?: number;
127
+ dominant?: boolean;
128
+ axisRemap?: Partial<AxisMap>;
129
+ lockPosition?: boolean;
130
+ lockRotation?: boolean;
131
+ }
122
132
  interface InputConfig {
133
+ /** Global defaults applied to all devices */
123
134
  sensitivity: SensitivityConfig;
124
135
  flip: FlipConfig;
125
136
  deadZone: number;
@@ -127,6 +138,12 @@ interface InputConfig {
127
138
  axisRemap: AxisMap;
128
139
  lockPosition: boolean;
129
140
  lockRotation: boolean;
141
+ /**
142
+ * Per-device overrides, keyed by device ID (e.g., "spacemouse-c635")
143
+ * or device family pattern (e.g., "spacemouse-*", "hid-054c-*").
144
+ * Values override global defaults for matching devices.
145
+ */
146
+ devices: Record<string, DeviceConfig>;
130
147
  }
131
148
 
132
149
  interface StorageAdapter {
@@ -148,18 +165,25 @@ interface InputManagerEvents {
148
165
  /** Configuration changed */
149
166
  configChange: (config: InputConfig) => void;
150
167
  }
168
+ /** A connected device paired with its resolved configuration */
169
+ interface DeviceWithConfig {
170
+ device: DeviceInfo$1;
171
+ config: DeviceConfig;
172
+ }
151
173
  /**
152
174
  * Unified device service that wraps one or more SatMouseConnections
153
175
  * and provides a single processed event stream.
154
176
  *
155
- * Applies a configurable transform pipeline to spatial data:
177
+ * Applies a configurable transform pipeline per-device:
156
178
  * deadZone → dominant → flip → axisRemap → sensitivity → lock
157
179
  *
158
- * Persists settings to storage (localStorage by default).
180
+ * Per-device overrides are resolved from InputConfig.devices using
181
+ * device ID matching (exact or pattern with wildcard "*").
159
182
  */
160
183
  declare class InputManager extends TypedEmitter<InputManagerEvents> {
161
184
  private connections;
162
185
  private storage?;
186
+ private knownDevices;
163
187
  private _config;
164
188
  get config(): InputConfig;
165
189
  constructor(config?: Partial<InputConfig>, storage?: StorageAdapter);
@@ -173,8 +197,14 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
173
197
  disconnect(): void;
174
198
  /** Fetch device info from all connections */
175
199
  fetchDeviceInfo(): Promise<DeviceInfo$1[]>;
176
- /** Update configuration. Persists by default. */
200
+ /** Get all known connected devices paired with their resolved config */
201
+ getDevicesWithConfig(): DeviceWithConfig[];
202
+ /** Get the resolved per-device config (global defaults + device overrides) */
203
+ getDeviceConfig(deviceId: string): DeviceConfig;
204
+ /** Update global configuration. Persists by default. */
177
205
  updateConfig(partial: Partial<InputConfig>, persist?: boolean): void;
206
+ /** Update configuration for a specific device. Persists by default. */
207
+ updateDeviceConfig(deviceId: string, partial: DeviceConfig, persist?: boolean): void;
178
208
  /** Register a callback for processed spatial data. Returns unsubscribe function. */
179
209
  onSpatialData(callback: (data: SpatialData) => void): () => void;
180
210
  /** Register a callback for button events. Returns unsubscribe function. */
@@ -119,7 +119,18 @@ type AxisMap = {
119
119
  rz: keyof Vec3;
120
120
  };
121
121
 
122
+ /** Per-device transform overrides. Any field left undefined inherits from global defaults. */
123
+ interface DeviceConfig {
124
+ sensitivity?: Partial<SensitivityConfig>;
125
+ flip?: Partial<FlipConfig>;
126
+ deadZone?: number;
127
+ dominant?: boolean;
128
+ axisRemap?: Partial<AxisMap>;
129
+ lockPosition?: boolean;
130
+ lockRotation?: boolean;
131
+ }
122
132
  interface InputConfig {
133
+ /** Global defaults applied to all devices */
123
134
  sensitivity: SensitivityConfig;
124
135
  flip: FlipConfig;
125
136
  deadZone: number;
@@ -127,6 +138,12 @@ interface InputConfig {
127
138
  axisRemap: AxisMap;
128
139
  lockPosition: boolean;
129
140
  lockRotation: boolean;
141
+ /**
142
+ * Per-device overrides, keyed by device ID (e.g., "spacemouse-c635")
143
+ * or device family pattern (e.g., "spacemouse-*", "hid-054c-*").
144
+ * Values override global defaults for matching devices.
145
+ */
146
+ devices: Record<string, DeviceConfig>;
130
147
  }
131
148
 
132
149
  interface StorageAdapter {
@@ -148,18 +165,25 @@ interface InputManagerEvents {
148
165
  /** Configuration changed */
149
166
  configChange: (config: InputConfig) => void;
150
167
  }
168
+ /** A connected device paired with its resolved configuration */
169
+ interface DeviceWithConfig {
170
+ device: DeviceInfo$1;
171
+ config: DeviceConfig;
172
+ }
151
173
  /**
152
174
  * Unified device service that wraps one or more SatMouseConnections
153
175
  * and provides a single processed event stream.
154
176
  *
155
- * Applies a configurable transform pipeline to spatial data:
177
+ * Applies a configurable transform pipeline per-device:
156
178
  * deadZone → dominant → flip → axisRemap → sensitivity → lock
157
179
  *
158
- * Persists settings to storage (localStorage by default).
180
+ * Per-device overrides are resolved from InputConfig.devices using
181
+ * device ID matching (exact or pattern with wildcard "*").
159
182
  */
160
183
  declare class InputManager extends TypedEmitter<InputManagerEvents> {
161
184
  private connections;
162
185
  private storage?;
186
+ private knownDevices;
163
187
  private _config;
164
188
  get config(): InputConfig;
165
189
  constructor(config?: Partial<InputConfig>, storage?: StorageAdapter);
@@ -173,8 +197,14 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
173
197
  disconnect(): void;
174
198
  /** Fetch device info from all connections */
175
199
  fetchDeviceInfo(): Promise<DeviceInfo$1[]>;
176
- /** Update configuration. Persists by default. */
200
+ /** Get all known connected devices paired with their resolved config */
201
+ getDevicesWithConfig(): DeviceWithConfig[];
202
+ /** Get the resolved per-device config (global defaults + device overrides) */
203
+ getDeviceConfig(deviceId: string): DeviceConfig;
204
+ /** Update global configuration. Persists by default. */
177
205
  updateConfig(partial: Partial<InputConfig>, persist?: boolean): void;
206
+ /** Update configuration for a specific device. Persists by default. */
207
+ updateDeviceConfig(deviceId: string, partial: DeviceConfig, persist?: boolean): void;
178
208
  /** Register a callback for processed spatial data. Returns unsubscribe function. */
179
209
  onSpatialData(callback: (data: SpatialData) => void): () => void;
180
210
  /** Register a callback for button events. Returns unsubscribe function. */
@@ -433,15 +433,57 @@ var DEFAULT_CONFIG = {
433
433
  dominant: false,
434
434
  axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
435
435
  lockPosition: false,
436
- lockRotation: false
436
+ lockRotation: false,
437
+ devices: {}
437
438
  };
438
439
  function mergeConfig(base, partial) {
439
- return {
440
+ const merged = {
440
441
  ...base,
441
442
  ...partial,
442
443
  sensitivity: { ...base.sensitivity, ...partial.sensitivity },
443
444
  flip: { ...base.flip, ...partial.flip },
444
- axisRemap: { ...base.axisRemap, ...partial.axisRemap }
445
+ axisRemap: { ...base.axisRemap, ...partial.axisRemap },
446
+ devices: { ...base.devices }
447
+ };
448
+ if (partial.devices) {
449
+ for (const [key, devCfg] of Object.entries(partial.devices)) {
450
+ merged.devices[key] = mergeDeviceConfig(merged.devices[key], devCfg);
451
+ }
452
+ }
453
+ return merged;
454
+ }
455
+ function mergeDeviceConfig(base, partial) {
456
+ if (!base) return partial;
457
+ return {
458
+ ...base,
459
+ ...partial,
460
+ sensitivity: partial.sensitivity ? { ...base.sensitivity, ...partial.sensitivity } : base.sensitivity,
461
+ flip: partial.flip ? { ...base.flip, ...partial.flip } : base.flip,
462
+ axisRemap: partial.axisRemap ? { ...base.axisRemap, ...partial.axisRemap } : base.axisRemap
463
+ };
464
+ }
465
+ function resolveDeviceConfig(config, deviceId) {
466
+ let deviceOverride;
467
+ if (config.devices[deviceId]) {
468
+ deviceOverride = config.devices[deviceId];
469
+ } else {
470
+ for (const [pattern, cfg] of Object.entries(config.devices)) {
471
+ if (pattern.endsWith("*") && deviceId.startsWith(pattern.slice(0, -1))) {
472
+ deviceOverride = cfg;
473
+ break;
474
+ }
475
+ }
476
+ }
477
+ if (!deviceOverride) return config;
478
+ return {
479
+ ...config,
480
+ sensitivity: { ...config.sensitivity, ...deviceOverride.sensitivity },
481
+ flip: { ...config.flip, ...deviceOverride.flip },
482
+ deadZone: deviceOverride.deadZone ?? config.deadZone,
483
+ dominant: deviceOverride.dominant ?? config.dominant,
484
+ axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },
485
+ lockPosition: deviceOverride.lockPosition ?? config.lockPosition,
486
+ lockRotation: deviceOverride.lockRotation ?? config.lockRotation
445
487
  };
446
488
  }
447
489
 
@@ -553,6 +595,7 @@ function applyAxisRemap(data, map) {
553
595
  var InputManager = class extends TypedEmitter {
554
596
  connections = [];
555
597
  storage;
598
+ knownDevices = /* @__PURE__ */ new Map();
556
599
  _config;
557
600
  get config() {
558
601
  return this._config;
@@ -585,14 +628,45 @@ var InputManager = class extends TypedEmitter {
585
628
  /** Fetch device info from all connections */
586
629
  async fetchDeviceInfo() {
587
630
  const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));
588
- return results.flat();
631
+ const devices = results.flat();
632
+ for (const d of devices) this.knownDevices.set(d.id, d);
633
+ return devices;
634
+ }
635
+ /** Get all known connected devices paired with their resolved config */
636
+ getDevicesWithConfig() {
637
+ return Array.from(this.knownDevices.values()).map((device) => ({
638
+ device,
639
+ config: this.getDeviceConfig(device.id)
640
+ }));
641
+ }
642
+ /** Get the resolved per-device config (global defaults + device overrides) */
643
+ getDeviceConfig(deviceId) {
644
+ const resolved = resolveDeviceConfig(this._config, deviceId);
645
+ return {
646
+ sensitivity: resolved.sensitivity,
647
+ flip: resolved.flip,
648
+ deadZone: resolved.deadZone,
649
+ dominant: resolved.dominant,
650
+ axisRemap: resolved.axisRemap,
651
+ lockPosition: resolved.lockPosition,
652
+ lockRotation: resolved.lockRotation
653
+ };
589
654
  }
590
- /** Update configuration. Persists by default. */
655
+ /** Update global configuration. Persists by default. */
591
656
  updateConfig(partial, persist = true) {
592
657
  this._config = mergeConfig(this._config, partial);
593
658
  if (persist) saveSettings(this._config, this.storage);
594
659
  this.emit("configChange", this._config);
595
660
  }
661
+ /** Update configuration for a specific device. Persists by default. */
662
+ updateDeviceConfig(deviceId, partial, persist = true) {
663
+ const existing = this._config.devices[deviceId] ?? {};
664
+ this._config = mergeConfig(this._config, {
665
+ devices: { [deviceId]: { ...existing, ...partial } }
666
+ });
667
+ if (persist) saveSettings(this._config, this.storage);
668
+ this.emit("configChange", this._config);
669
+ }
596
670
  /** Register a callback for processed spatial data. Returns unsubscribe function. */
597
671
  onSpatialData(callback) {
598
672
  this.on("spatialData", callback);
@@ -611,7 +685,11 @@ var InputManager = class extends TypedEmitter {
611
685
  });
612
686
  connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
613
687
  connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
614
- connection.on("deviceStatus", (event, device) => this.emit("deviceStatus", event, device));
688
+ connection.on("deviceStatus", (event, device) => {
689
+ if (event === "connected") this.knownDevices.set(device.id, device);
690
+ else this.knownDevices.delete(device.id);
691
+ this.emit("deviceStatus", event, device);
692
+ });
615
693
  }
616
694
  processSpatialData(raw) {
617
695
  const cfg = this._config;