ps-access 0.0.2 → 0.0.3
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/package.json +1 -1
- package/web/xmb.js +65 -25
package/package.json
CHANGED
package/web/xmb.js
CHANGED
|
@@ -619,27 +619,46 @@ async function load() {
|
|
|
619
619
|
async function connectOnce() {
|
|
620
620
|
try {
|
|
621
621
|
const before = controllers.length;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
toast(
|
|
622
|
+
await requestControllers(); // user grants one in the chooser (this gesture)
|
|
623
|
+
await addDevices(await grantedControllers()); // reconcile: add every granted controller not yet present
|
|
624
|
+
const added = controllers.length - before;
|
|
625
|
+
if (added > 0) toast(added > 1 ? `${added} controllers connected` : "Controller connected", 2200);
|
|
626
|
+
else toast("That one's already connected — to add another, pick the *other* “Access Controller” in the chooser.", 5500);
|
|
626
627
|
} catch (e) { toast(String(e.message || e), 4000); }
|
|
627
628
|
}
|
|
628
|
-
|
|
629
|
+
|
|
630
|
+
// Serialize addDevices: granting a device in the chooser fires the `connect` event, whose handler
|
|
631
|
+
// also calls addDevices — running both concurrently on the same new device interleaves its
|
|
632
|
+
// feature-report reads and corrupts the add. Chaining guarantees one batch finishes before the next.
|
|
633
|
+
let _addChain = Promise.resolve();
|
|
634
|
+
function addDevices(devices) {
|
|
635
|
+
const run = _addChain.then(() => _addDevices(devices));
|
|
636
|
+
_addChain = run.catch(() => {}); // keep the chain alive even if a batch throws
|
|
637
|
+
return run;
|
|
638
|
+
}
|
|
639
|
+
async function _addDevices(devices) {
|
|
640
|
+
let added = 0;
|
|
629
641
|
for (const device of devices) {
|
|
630
642
|
if (controllers.some((c) => c.device === device)) continue;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
643
|
+
try {
|
|
644
|
+
await ensureOpen(device);
|
|
645
|
+
const profiles = [];
|
|
646
|
+
for (let s = 1; s <= PROFILE_COUNT; s++) {
|
|
647
|
+
const p = parseProfile(await readProfileRaw(device, s));
|
|
648
|
+
p._physOrient = p.ports[0].kind === "stick" ? p.ports[0].orientation : 3;
|
|
649
|
+
profiles.push(p);
|
|
650
|
+
}
|
|
651
|
+
if (controllers.some((c) => c.device === device)) continue; // defensive: added while awaiting
|
|
652
|
+
device.addEventListener("inputreport", onInputReport); // attach only after a clean read
|
|
653
|
+
controllers.push({ device, name: `Controller ${controllers.length + 1}`, profiles });
|
|
654
|
+
added++;
|
|
655
|
+
} catch (e) {
|
|
656
|
+
toast(`Couldn't read a controller: ${e.message || e}`, 4000);
|
|
638
657
|
}
|
|
639
|
-
controllers.push({ device, name: `Controller ${controllers.length + 1}`, profiles });
|
|
640
658
|
}
|
|
641
659
|
updateDeviceStatus();
|
|
642
660
|
render();
|
|
661
|
+
return added;
|
|
643
662
|
}
|
|
644
663
|
function updateDeviceStatus() {
|
|
645
664
|
const c = controllers[activeCtrl];
|
|
@@ -866,28 +885,48 @@ function closeHelp() {
|
|
|
866
885
|
// Nav scheme (per design): center/stick-click = confirm; any perimeter button = back; stick = directions.
|
|
867
886
|
const inputEdge = {};
|
|
868
887
|
let dirRepeatAt = 0;
|
|
888
|
+
// Latest decoded physical state per connected controller. Navigation/highlighting is driven by the
|
|
889
|
+
// *union* of all of them (either controller can move the cursor, confirm, back), while the
|
|
890
|
+
// active-profile indicator, wave tint and monitor follow the controller being edited (activeCtrl).
|
|
891
|
+
const ctrlState = new Map(); // device -> { buttons:Set, axes:[x,y] }
|
|
892
|
+
function mergedInput() {
|
|
893
|
+
const buttons = new Set();
|
|
894
|
+
let axes = [0, 0], best = 0;
|
|
895
|
+
for (const c of controllers) {
|
|
896
|
+
const st = ctrlState.get(c.device);
|
|
897
|
+
if (!st) continue;
|
|
898
|
+
for (const b of st.buttons) buttons.add(b);
|
|
899
|
+
const mag = Math.abs(st.axes[0]) + Math.abs(st.axes[1]); // whichever stick is pushed furthest steers
|
|
900
|
+
if (mag > best) { best = mag; axes = st.axes; }
|
|
901
|
+
}
|
|
902
|
+
return { buttons, axes };
|
|
903
|
+
}
|
|
869
904
|
function onInputReport(e) {
|
|
870
|
-
if (
|
|
905
|
+
if (!controllers.some((c) => c.device === e.device)) return; // ignore reports from a device mid-add
|
|
871
906
|
const d = new Uint8Array(e.data.buffer.slice(e.data.byteOffset, e.data.byteOffset + e.data.byteLength));
|
|
872
907
|
lastInputAt = performance.now();
|
|
873
908
|
waveConnected = true; // a report is streaming -> the wave is visible
|
|
874
909
|
const { buttons, axes, profile } = decodePhysical(d);
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
if (profile && profile - 1 !== deviceProfile) {
|
|
910
|
+
ctrlState.set(e.device, { buttons, axes });
|
|
911
|
+
const isActive = e.device === controllers[activeCtrl]?.device;
|
|
912
|
+
// Active-profile tracking is per-device — only the *edited* controller's profile drives the
|
|
913
|
+
// indicator/wave/monitor. (Refresh the top bar, wave and "✓ Active" marker when it changes.)
|
|
914
|
+
if (isActive && profile && profile - 1 !== deviceProfile) {
|
|
880
915
|
deviceProfile = profile - 1;
|
|
881
916
|
updateProfileTag();
|
|
882
|
-
setWaveProfile(deviceProfile);
|
|
917
|
+
setWaveProfile(deviceProfile);
|
|
883
918
|
if (monitorMode) $("#mon-render").innerHTML = profileSVG(controllers[activeCtrl].profiles[deviceProfile]);
|
|
884
|
-
else if (!monitorArm && !nav.drill) render();
|
|
919
|
+
else if (!monitorArm && !nav.drill) render();
|
|
885
920
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
921
|
+
// Unified live input (union of every connected controller) drives highlighting + navigation.
|
|
922
|
+
const m = mergedInput();
|
|
923
|
+
liveAxes = m.axes;
|
|
924
|
+
phys = m.buttons;
|
|
890
925
|
setGpStatus(true);
|
|
926
|
+
if (monitorMode) { if (isActive) updateMonitor(buttons, axes, d); return; } // monitor observes the edited controller
|
|
927
|
+
if (monitorArm) { handleArmInput(m.buttons, m.axes); return; }
|
|
928
|
+
handlePhysInput(m.buttons, m.axes);
|
|
929
|
+
updateLive();
|
|
891
930
|
}
|
|
892
931
|
|
|
893
932
|
function handlePhysInput(buttons, axes) {
|
|
@@ -1031,6 +1070,7 @@ function init() {
|
|
|
1031
1070
|
const idx = controllers.findIndex((c) => c.device === e.device);
|
|
1032
1071
|
if (idx !== -1) {
|
|
1033
1072
|
e.device.removeEventListener("inputreport", onInputReport);
|
|
1073
|
+
ctrlState.delete(e.device);
|
|
1034
1074
|
controllers.splice(idx, 1);
|
|
1035
1075
|
if (activeCtrl >= controllers.length) activeCtrl = Math.max(0, controllers.length - 1);
|
|
1036
1076
|
deviceProfile = null;
|