@kelnishi/satmouse-client 0.9.3 → 0.9.5
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/dist/react/index.cjs +150 -9
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +64 -4
- package/dist/react/index.d.ts +64 -4
- package/dist/react/index.js +150 -9
- package/dist/react/index.js.map +1 -1
- package/dist/utils/index.cjs +165 -9
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +79 -6
- package/dist/utils/index.d.ts +79 -6
- package/dist/utils/index.js +161 -10
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/dist/react/index.d.ts
CHANGED
|
@@ -119,7 +119,42 @@ type AxisMap = {
|
|
|
119
119
|
rz: keyof Vec3;
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
/** Input axis identifier */
|
|
123
|
+
type InputAxis = "tx" | "ty" | "tz" | "rx" | "ry" | "rz";
|
|
124
|
+
/** A single action binding — maps one input axis to a named output */
|
|
125
|
+
interface ActionBinding {
|
|
126
|
+
/** Which input axis drives this action */
|
|
127
|
+
source: InputAxis;
|
|
128
|
+
/** Scale multiplier (default: 1) */
|
|
129
|
+
scale?: number;
|
|
130
|
+
/** Invert the value (default: false) */
|
|
131
|
+
invert?: boolean;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* ActionMap defines how raw 6DOF axes map to named output actions.
|
|
135
|
+
*
|
|
136
|
+
* Client apps declare the actions they support and how device axes
|
|
137
|
+
* feed into them. Users can reassign axes via the settings UI.
|
|
138
|
+
*
|
|
139
|
+
* Default: 6 actions matching the 6 input axes (passthrough).
|
|
140
|
+
*/
|
|
141
|
+
type ActionMap = Record<string, ActionBinding>;
|
|
142
|
+
/** Result of applying an ActionMap to spatial data */
|
|
143
|
+
type ActionValues = Record<string, number>;
|
|
144
|
+
|
|
145
|
+
/** Per-device transform overrides. Any field left undefined inherits from global defaults. */
|
|
146
|
+
interface DeviceConfig {
|
|
147
|
+
sensitivity?: Partial<SensitivityConfig>;
|
|
148
|
+
flip?: Partial<FlipConfig>;
|
|
149
|
+
deadZone?: number;
|
|
150
|
+
dominant?: boolean;
|
|
151
|
+
axisRemap?: Partial<AxisMap>;
|
|
152
|
+
actionMap?: ActionMap;
|
|
153
|
+
lockPosition?: boolean;
|
|
154
|
+
lockRotation?: boolean;
|
|
155
|
+
}
|
|
122
156
|
interface InputConfig {
|
|
157
|
+
/** Global defaults applied to all devices */
|
|
123
158
|
sensitivity: SensitivityConfig;
|
|
124
159
|
flip: FlipConfig;
|
|
125
160
|
deadZone: number;
|
|
@@ -127,6 +162,14 @@ interface InputConfig {
|
|
|
127
162
|
axisRemap: AxisMap;
|
|
128
163
|
lockPosition: boolean;
|
|
129
164
|
lockRotation: boolean;
|
|
165
|
+
/** Action map — maps input axes to named output actions. Default: passthrough. */
|
|
166
|
+
actionMap: ActionMap;
|
|
167
|
+
/**
|
|
168
|
+
* Per-device overrides, keyed by device ID (e.g., "spacemouse-c635")
|
|
169
|
+
* or device family pattern (e.g., "spacemouse-*", "hid-054c-*").
|
|
170
|
+
* Values override global defaults for matching devices.
|
|
171
|
+
*/
|
|
172
|
+
devices: Record<string, DeviceConfig>;
|
|
130
173
|
}
|
|
131
174
|
|
|
132
175
|
interface StorageAdapter {
|
|
@@ -135,8 +178,10 @@ interface StorageAdapter {
|
|
|
135
178
|
}
|
|
136
179
|
|
|
137
180
|
interface InputManagerEvents {
|
|
138
|
-
/** Processed spatial data (after all transforms) */
|
|
181
|
+
/** Processed spatial data (after all transforms + action map) */
|
|
139
182
|
spatialData: (data: SpatialData) => void;
|
|
183
|
+
/** Named action values from the action map */
|
|
184
|
+
actionValues: (values: ActionValues) => void;
|
|
140
185
|
/** Raw spatial data (before transforms) */
|
|
141
186
|
rawSpatialData: (data: SpatialData) => void;
|
|
142
187
|
/** Button event (pass-through from connection) */
|
|
@@ -148,18 +193,25 @@ interface InputManagerEvents {
|
|
|
148
193
|
/** Configuration changed */
|
|
149
194
|
configChange: (config: InputConfig) => void;
|
|
150
195
|
}
|
|
196
|
+
/** A connected device paired with its resolved configuration */
|
|
197
|
+
interface DeviceWithConfig {
|
|
198
|
+
device: DeviceInfo$1;
|
|
199
|
+
config: DeviceConfig;
|
|
200
|
+
}
|
|
151
201
|
/**
|
|
152
202
|
* Unified device service that wraps one or more SatMouseConnections
|
|
153
203
|
* and provides a single processed event stream.
|
|
154
204
|
*
|
|
155
|
-
* Applies a configurable transform pipeline
|
|
205
|
+
* Applies a configurable transform pipeline per-device:
|
|
156
206
|
* deadZone → dominant → flip → axisRemap → sensitivity → lock
|
|
157
207
|
*
|
|
158
|
-
*
|
|
208
|
+
* Per-device overrides are resolved from InputConfig.devices using
|
|
209
|
+
* device ID matching (exact or pattern with wildcard "*").
|
|
159
210
|
*/
|
|
160
211
|
declare class InputManager extends TypedEmitter<InputManagerEvents> {
|
|
161
212
|
private connections;
|
|
162
213
|
private storage?;
|
|
214
|
+
private knownDevices;
|
|
163
215
|
private _config;
|
|
164
216
|
get config(): InputConfig;
|
|
165
217
|
constructor(config?: Partial<InputConfig>, storage?: StorageAdapter);
|
|
@@ -173,12 +225,20 @@ declare class InputManager extends TypedEmitter<InputManagerEvents> {
|
|
|
173
225
|
disconnect(): void;
|
|
174
226
|
/** Fetch device info from all connections */
|
|
175
227
|
fetchDeviceInfo(): Promise<DeviceInfo$1[]>;
|
|
176
|
-
/**
|
|
228
|
+
/** Get all known connected devices paired with their resolved config */
|
|
229
|
+
getDevicesWithConfig(): DeviceWithConfig[];
|
|
230
|
+
/** Get the resolved per-device config (global defaults + device overrides) */
|
|
231
|
+
getDeviceConfig(deviceId: string): DeviceConfig;
|
|
232
|
+
/** Update global configuration. Persists by default. */
|
|
177
233
|
updateConfig(partial: Partial<InputConfig>, persist?: boolean): void;
|
|
234
|
+
/** Update configuration for a specific device. Persists by default. */
|
|
235
|
+
updateDeviceConfig(deviceId: string, partial: DeviceConfig, persist?: boolean): void;
|
|
178
236
|
/** Register a callback for processed spatial data. Returns unsubscribe function. */
|
|
179
237
|
onSpatialData(callback: (data: SpatialData) => void): () => void;
|
|
180
238
|
/** Register a callback for button events. Returns unsubscribe function. */
|
|
181
239
|
onButtonEvent(callback: (data: ButtonEvent) => void): () => void;
|
|
240
|
+
/** Register a callback for action values. Returns unsubscribe function. */
|
|
241
|
+
onActionValues(callback: (values: ActionValues) => void): () => void;
|
|
182
242
|
private wireConnection;
|
|
183
243
|
private processSpatialData;
|
|
184
244
|
}
|
package/dist/react/index.js
CHANGED
|
@@ -425,6 +425,57 @@ function launchSatMouse(options) {
|
|
|
425
425
|
});
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
// src/utils/action-map.ts
|
|
429
|
+
var DEFAULT_ACTION_MAP = {
|
|
430
|
+
tx: { source: "tx" },
|
|
431
|
+
ty: { source: "ty" },
|
|
432
|
+
tz: { source: "tz" },
|
|
433
|
+
rx: { source: "rx" },
|
|
434
|
+
ry: { source: "ry" },
|
|
435
|
+
rz: { source: "rz" }
|
|
436
|
+
};
|
|
437
|
+
function readAxis(data, axis) {
|
|
438
|
+
switch (axis) {
|
|
439
|
+
case "tx":
|
|
440
|
+
return data.translation.x;
|
|
441
|
+
case "ty":
|
|
442
|
+
return data.translation.y;
|
|
443
|
+
case "tz":
|
|
444
|
+
return data.translation.z;
|
|
445
|
+
case "rx":
|
|
446
|
+
return data.rotation.x;
|
|
447
|
+
case "ry":
|
|
448
|
+
return data.rotation.y;
|
|
449
|
+
case "rz":
|
|
450
|
+
return data.rotation.z;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function applyActionMap(data, map) {
|
|
454
|
+
const result = {};
|
|
455
|
+
for (const [action, binding] of Object.entries(map)) {
|
|
456
|
+
let value = readAxis(data, binding.source);
|
|
457
|
+
if (binding.invert) value = -value;
|
|
458
|
+
value *= binding.scale ?? 1;
|
|
459
|
+
result[action] = value;
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
function actionValuesToSpatialData(values, timestamp) {
|
|
464
|
+
return {
|
|
465
|
+
translation: {
|
|
466
|
+
x: values.tx ?? 0,
|
|
467
|
+
y: values.ty ?? 0,
|
|
468
|
+
z: values.tz ?? 0
|
|
469
|
+
},
|
|
470
|
+
rotation: {
|
|
471
|
+
x: values.rx ?? 0,
|
|
472
|
+
y: values.ry ?? 0,
|
|
473
|
+
z: values.rz ?? 0
|
|
474
|
+
},
|
|
475
|
+
timestamp
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
428
479
|
// src/utils/config.ts
|
|
429
480
|
var DEFAULT_CONFIG = {
|
|
430
481
|
sensitivity: { translation: 1e-3, rotation: 1e-3 },
|
|
@@ -433,15 +484,60 @@ var DEFAULT_CONFIG = {
|
|
|
433
484
|
dominant: false,
|
|
434
485
|
axisRemap: { tx: "x", ty: "y", tz: "z", rx: "x", ry: "y", rz: "z" },
|
|
435
486
|
lockPosition: false,
|
|
436
|
-
lockRotation: false
|
|
487
|
+
lockRotation: false,
|
|
488
|
+
actionMap: { ...DEFAULT_ACTION_MAP },
|
|
489
|
+
devices: {}
|
|
437
490
|
};
|
|
438
491
|
function mergeConfig(base, partial) {
|
|
439
|
-
|
|
492
|
+
const merged = {
|
|
440
493
|
...base,
|
|
441
494
|
...partial,
|
|
442
495
|
sensitivity: { ...base.sensitivity, ...partial.sensitivity },
|
|
443
496
|
flip: { ...base.flip, ...partial.flip },
|
|
444
|
-
axisRemap: { ...base.axisRemap, ...partial.axisRemap }
|
|
497
|
+
axisRemap: { ...base.axisRemap, ...partial.axisRemap },
|
|
498
|
+
actionMap: partial.actionMap ? { ...base.actionMap, ...partial.actionMap } : { ...base.actionMap },
|
|
499
|
+
devices: { ...base.devices }
|
|
500
|
+
};
|
|
501
|
+
if (partial.devices) {
|
|
502
|
+
for (const [key, devCfg] of Object.entries(partial.devices)) {
|
|
503
|
+
merged.devices[key] = mergeDeviceConfig(merged.devices[key], devCfg);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return merged;
|
|
507
|
+
}
|
|
508
|
+
function mergeDeviceConfig(base, partial) {
|
|
509
|
+
if (!base) return partial;
|
|
510
|
+
return {
|
|
511
|
+
...base,
|
|
512
|
+
...partial,
|
|
513
|
+
sensitivity: partial.sensitivity ? { ...base.sensitivity, ...partial.sensitivity } : base.sensitivity,
|
|
514
|
+
flip: partial.flip ? { ...base.flip, ...partial.flip } : base.flip,
|
|
515
|
+
axisRemap: partial.axisRemap ? { ...base.axisRemap, ...partial.axisRemap } : base.axisRemap
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function resolveDeviceConfig(config, deviceId) {
|
|
519
|
+
let deviceOverride;
|
|
520
|
+
if (config.devices[deviceId]) {
|
|
521
|
+
deviceOverride = config.devices[deviceId];
|
|
522
|
+
} else {
|
|
523
|
+
for (const [pattern, cfg] of Object.entries(config.devices)) {
|
|
524
|
+
if (pattern.endsWith("*") && deviceId.startsWith(pattern.slice(0, -1))) {
|
|
525
|
+
deviceOverride = cfg;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (!deviceOverride) return config;
|
|
531
|
+
return {
|
|
532
|
+
...config,
|
|
533
|
+
sensitivity: { ...config.sensitivity, ...deviceOverride.sensitivity },
|
|
534
|
+
flip: { ...config.flip, ...deviceOverride.flip },
|
|
535
|
+
deadZone: deviceOverride.deadZone ?? config.deadZone,
|
|
536
|
+
dominant: deviceOverride.dominant ?? config.dominant,
|
|
537
|
+
axisRemap: { ...config.axisRemap, ...deviceOverride.axisRemap },
|
|
538
|
+
actionMap: deviceOverride.actionMap ? { ...config.actionMap, ...deviceOverride.actionMap } : config.actionMap,
|
|
539
|
+
lockPosition: deviceOverride.lockPosition ?? config.lockPosition,
|
|
540
|
+
lockRotation: deviceOverride.lockRotation ?? config.lockRotation
|
|
445
541
|
};
|
|
446
542
|
}
|
|
447
543
|
|
|
@@ -553,6 +649,7 @@ function applyAxisRemap(data, map) {
|
|
|
553
649
|
var InputManager = class extends TypedEmitter {
|
|
554
650
|
connections = [];
|
|
555
651
|
storage;
|
|
652
|
+
knownDevices = /* @__PURE__ */ new Map();
|
|
556
653
|
_config;
|
|
557
654
|
get config() {
|
|
558
655
|
return this._config;
|
|
@@ -585,14 +682,46 @@ var InputManager = class extends TypedEmitter {
|
|
|
585
682
|
/** Fetch device info from all connections */
|
|
586
683
|
async fetchDeviceInfo() {
|
|
587
684
|
const results = await Promise.all(this.connections.map((c) => c.fetchDeviceInfo()));
|
|
588
|
-
|
|
685
|
+
const devices = results.flat();
|
|
686
|
+
for (const d of devices) this.knownDevices.set(d.id, d);
|
|
687
|
+
return devices;
|
|
589
688
|
}
|
|
590
|
-
/**
|
|
689
|
+
/** Get all known connected devices paired with their resolved config */
|
|
690
|
+
getDevicesWithConfig() {
|
|
691
|
+
return Array.from(this.knownDevices.values()).map((device) => ({
|
|
692
|
+
device,
|
|
693
|
+
config: this.getDeviceConfig(device.id)
|
|
694
|
+
}));
|
|
695
|
+
}
|
|
696
|
+
/** Get the resolved per-device config (global defaults + device overrides) */
|
|
697
|
+
getDeviceConfig(deviceId) {
|
|
698
|
+
const resolved = resolveDeviceConfig(this._config, deviceId);
|
|
699
|
+
return {
|
|
700
|
+
sensitivity: resolved.sensitivity,
|
|
701
|
+
flip: resolved.flip,
|
|
702
|
+
deadZone: resolved.deadZone,
|
|
703
|
+
dominant: resolved.dominant,
|
|
704
|
+
axisRemap: resolved.axisRemap,
|
|
705
|
+
actionMap: resolved.actionMap,
|
|
706
|
+
lockPosition: resolved.lockPosition,
|
|
707
|
+
lockRotation: resolved.lockRotation
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
/** Update global configuration. Persists by default. */
|
|
591
711
|
updateConfig(partial, persist = true) {
|
|
592
712
|
this._config = mergeConfig(this._config, partial);
|
|
593
713
|
if (persist) saveSettings(this._config, this.storage);
|
|
594
714
|
this.emit("configChange", this._config);
|
|
595
715
|
}
|
|
716
|
+
/** Update configuration for a specific device. Persists by default. */
|
|
717
|
+
updateDeviceConfig(deviceId, partial, persist = true) {
|
|
718
|
+
const existing = this._config.devices[deviceId] ?? {};
|
|
719
|
+
this._config = mergeConfig(this._config, {
|
|
720
|
+
devices: { [deviceId]: { ...existing, ...partial } }
|
|
721
|
+
});
|
|
722
|
+
if (persist) saveSettings(this._config, this.storage);
|
|
723
|
+
this.emit("configChange", this._config);
|
|
724
|
+
}
|
|
596
725
|
/** Register a callback for processed spatial data. Returns unsubscribe function. */
|
|
597
726
|
onSpatialData(callback) {
|
|
598
727
|
this.on("spatialData", callback);
|
|
@@ -603,15 +732,25 @@ var InputManager = class extends TypedEmitter {
|
|
|
603
732
|
this.on("buttonEvent", callback);
|
|
604
733
|
return () => this.off("buttonEvent", callback);
|
|
605
734
|
}
|
|
735
|
+
/** Register a callback for action values. Returns unsubscribe function. */
|
|
736
|
+
onActionValues(callback) {
|
|
737
|
+
this.on("actionValues", callback);
|
|
738
|
+
return () => this.off("actionValues", callback);
|
|
739
|
+
}
|
|
606
740
|
wireConnection(connection) {
|
|
607
741
|
connection.on("spatialData", (raw) => {
|
|
608
742
|
this.emit("rawSpatialData", raw);
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
743
|
+
const { spatial, actions } = this.processSpatialData(raw);
|
|
744
|
+
if (spatial) this.emit("spatialData", spatial);
|
|
745
|
+
if (actions) this.emit("actionValues", actions);
|
|
611
746
|
});
|
|
612
747
|
connection.on("buttonEvent", (event) => this.emit("buttonEvent", event));
|
|
613
748
|
connection.on("stateChange", (state, proto) => this.emit("stateChange", state, proto));
|
|
614
|
-
connection.on("deviceStatus", (event, device) =>
|
|
749
|
+
connection.on("deviceStatus", (event, device) => {
|
|
750
|
+
if (event === "connected") this.knownDevices.set(device.id, device);
|
|
751
|
+
else this.knownDevices.delete(device.id);
|
|
752
|
+
this.emit("deviceStatus", event, device);
|
|
753
|
+
});
|
|
615
754
|
}
|
|
616
755
|
processSpatialData(raw) {
|
|
617
756
|
const cfg = this._config;
|
|
@@ -627,7 +766,9 @@ var InputManager = class extends TypedEmitter {
|
|
|
627
766
|
if (cfg.lockRotation) {
|
|
628
767
|
data = { ...data, rotation: { x: 0, y: 0, z: 0 } };
|
|
629
768
|
}
|
|
630
|
-
|
|
769
|
+
const actions = applyActionMap(data, cfg.actionMap);
|
|
770
|
+
const spatial = actionValuesToSpatialData(actions, data.timestamp);
|
|
771
|
+
return { spatial, actions };
|
|
631
772
|
}
|
|
632
773
|
};
|
|
633
774
|
var SatMouseContext = createContext(null);
|