|
|
@@ -591,7 +591,13 @@ let lizardBindings = []; // [{outType, od:[7], trig, hold}]
|
|
|
let lizardBusy = false;
|
|
|
let lizardLoaded = false; // one-shot: the lizard map is fetched lazily, only once the connected puck proves it speaks v16+
|
|
|
const BACK_LABELS = ["L4 (back upper-left)","R4 (back upper-right)","L5 (back lower-left)","R5 (back lower-right)"];
|
|
|
-const typeEls = []; // one entry per TYPE_DEFS: {sec, tab, activeDot, back[], qam, abSwap, pad, led, ledV, rumble}
|
|
|
+const typeEls = []; // one entry per TYPE_DEFS: {sec, tab, activeDot, back[], qam, abSwap, pad, led, ledV, rumble, padStick[]}
|
|
|
+// Trackpad -> joystick mapping (firmware PS_OFF/PS_LEFT/PS_RIGHT). Field id = PAD_STICK_FIELD0 + et*2 + pad
|
|
|
+// (pad 0 = left trackpad, 1 = right). While mapped and touched the pad drives that stick; on release the
|
|
|
+// stick re-centers, and an untouched mapped pad leaves the physical stick in control.
|
|
|
+const PAD_STICK_FIELD0 = 80;
|
|
|
+const PAD_STICK_OPTS = [[0,"Off (touchpad)"],[1,"Left stick"],[2,"Right stick"]];
|
|
|
+const PAD_STICK_LABELS = ["Left trackpad \u2192 stick","Right trackpad \u2192 stick"];
|
|
|
function mkSelect(def, includeNone){
|
|
|
const sel=document.createElement("select");
|
|
|
if(includeNone){ const o=document.createElement("option"); o.value=0; o.textContent="— none —"; sel.appendChild(o); }
|
|
|
@@ -619,7 +625,7 @@ function setTab(et){
|
|
|
tab.onclick=()=>setTab(et); bar.appendChild(tab);
|
|
|
// panel
|
|
|
const sec=document.createElement("div"); sec.style.cssText="background:#0e1017;border:1px solid #232a3a;border-radius:8px;padding:12px 14px;display:none";
|
|
|
- const rec={sec,tab,activeDot:tab.querySelector(".active-dot"),back:[],qam:null,abSwap:null,pad:null,led:null,ledV:null,rumble:null};
|
|
|
+ const rec={sec,tab,activeDot:tab.querySelector(".active-dot"),back:[],qam:null,abSwap:null,pad:null,led:null,ledV:null,rumble:null,padStick:[]};
|
|
|
// A/B swap + trackpad haptics + rumble toggles
|
|
|
const tog=document.createElement("div"); tog.className="row";
|
|
|
tog.innerHTML='<label>A/B + X/Y swap</label>';
|
|
|
@@ -657,6 +663,16 @@ function setTab(et){
|
|
|
sl.addEventListener("change",()=>setField(40+et*9+7, +sl.value));
|
|
|
sec.appendChild(row);
|
|
|
rec.led=sl; rec.ledV=vspan; }
|
|
|
+ // trackpad -> joystick mapping (one select per pad)
|
|
|
+ for(let pad=0; pad<2; pad++){
|
|
|
+ const row=document.createElement("div"); row.className="row";
|
|
|
+ const lab=document.createElement("label"); lab.textContent=PAD_STICK_LABELS[pad]; row.appendChild(lab);
|
|
|
+ const sel=document.createElement("select");
|
|
|
+ for(const [v,lbl] of PAD_STICK_OPTS){ const o=document.createElement("option"); o.value=v; o.textContent=lbl; sel.appendChild(o); }
|
|
|
+ row.appendChild(sel); sec.appendChild(row);
|
|
|
+ sel.addEventListener("change",()=>setField(PAD_STICK_FIELD0+et*2+pad, +sel.value));
|
|
|
+ rec.padStick.push(sel);
|
|
|
+ }
|
|
|
host.appendChild(sec); typeEls.push(rec);
|
|
|
});
|
|
|
setTab(0);
|
|
|
@@ -1041,6 +1057,8 @@ function applyBlob(p){
|
|
|
if (RR_FAULT.has(code) && !window._flightAuto){ window._flightAuto=true; window._autoFlight=true; }
|
|
|
}
|
|
|
|
|
|
+ // per-type trackpad->stick mapping (blob v20, firmware p[187..194] -> JS p[185..192])
|
|
|
+ const padStickCap=(p[0]>=20 && p.length>192);
|
|
|
const gitDirty=(p.length>38?p[38]:0); let buildId="";
|
|
|
for(let i=39;i<51 && i<p.length && p[i];i++) buildId+=String.fromCharCode(p[i]);
|
|
|
$("#stBuild").innerHTML = buildId
|
|
|
@@ -1067,6 +1085,11 @@ function applyBlob(p){
|
|
|
rec.pad.textContent=pad?"on":"off"; rec.pad.classList.toggle("active",!!pad);
|
|
|
rec.rumble.textContent=rum?"on":"off"; rec.rumble.classList.toggle("active",!!rum);
|
|
|
if(document.activeElement!==rec.led){ rec.led.value=led; rec.ledV.textContent=(led===0)?"Auto":led+"%"; }
|
|
|
+ rec.padStick.forEach((sel,pad)=>{
|
|
|
+ // hidden entirely on firmware too old to speak the mapping, so the panel never shows a dead control
|
|
|
+ sel.parentNode.classList.toggle("hide", !padStickCap);
|
|
|
+ if(padStickCap && document.activeElement!==sel) sel.value=p[185+et*2+pad];
|
|
|
+ });
|
|
|
// dot marks the type matching the current puck mode
|
|
|
rec.activeDot.style.display = (et===activeEt) ? "" : "none";
|
|
|
});
|
|
|
@@ -1178,7 +1201,10 @@ function buildBackup(p, bp){
|
|
|
bonds.push({slot:s, used:!!((mask>>s)&1), rec:hexEnc(rec)}); }
|
|
|
const types=[];
|
|
|
for(let et=0; et<TYPE_DEFS.length; et++){ const q=73+et*9;
|
|
|
- types.push({back:[p[q],p[q+1],p[q+2],p[q+3]], qam:p[q+4], abSwap:p[q+5], pad:p[q+6], led:p[q+7], rumble:p[q+8]}); }
|
|
|
+ const t={back:[p[q],p[q+1],p[q+2],p[q+3]], qam:p[q+4], abSwap:p[q+5], pad:p[q+6], led:p[q+7], rumble:p[q+8]};
|
|
|
+ // pad->stick mapping only exists from protocol v20; omit on older pucks so a restore can't replay defaults
|
|
|
+ if(p[0]>=21 && p.length>192) t.padStick=[p[185+et*2],p[186+et*2]];
|
|
|
+ types.push(t); }
|
|
|
const cfg={ mode:p[1], mDiv:p[2], mFric:p[3], persistMode:p[22], chord:[p[23],p[24],p[25]], types };
|
|
|
// D-pad chords only exist from protocol v18, the Switch gyro mapping from v19; omit the keys entirely on
|
|
|
// older pucks so a restore doesn't replay defaults over a newer puck's real settings. (Backups from before
|
|
|
@@ -1237,7 +1263,8 @@ async function importBackup(file){
|
|
|
const typeN=Array.isArray(c.types)?Math.min(c.types.length,4):0;
|
|
|
const lizardN=(Array.isArray(c.lizardMap) && lizardCapable())?Math.min(c.lizardMap.length,LZ_MAX):0;
|
|
|
const baseN=6 + (Array.isArray(c.chordD)?4:0) + (c.swGyroLegacy!==undefined?1:0);
|
|
|
- const total=baseN + typeN*9 + lizardN + 4 + 1;
|
|
|
+ const padStickN=Array.isArray(c.types)?c.types.slice(0,typeN).filter(t=>Array.isArray(t.padStick)).length*2:0;
|
|
|
+ const total=baseN + typeN*9 + padStickN + lizardN + 4 + 1;
|
|
|
let step=0, stage="";
|
|
|
const tick=()=>modalStage(stage, Math.min(99,Math.floor(step*100/total)));
|
|
|
try{
|
|
|
@@ -1273,7 +1300,8 @@ async function importBackup(file){
|
|
|
for(let k=0;k<4;k++) await sf(40+et*9+k, t.back[k]);
|
|
|
await sf(40+et*9+4, t.qam); await sf(40+et*9+5, t.abSwap?1:0);
|
|
|
await sf(40+et*9+6, t.pad?1:0); await sf(40+et*9+7, t.led);
|
|
|
- await sf(40+et*9+8, t.rumble!==undefined?t.rumble:1); }
|
|
|
+ await sf(40+et*9+8, t.rumble!==undefined?t.rumble:1);
|
|
|
+ if(Array.isArray(t.padStick)){ await sf(PAD_STICK_FIELD0+et*2, t.padStick[0]); await sf(PAD_STICK_FIELD0+et*2+1, t.padStick[1]); } }
|
|
|
log("settings replayed ("+(baseN+typeN*9)+" fields)");
|
|
|
} else { log("settings replayed ("+baseN+" fields — no per-type config in this backup)"); }
|
|
|
// restore the lizard map if the backup includes it (version 2+) and the puck speaks v16+.
|