fix: PGXL meters/auth/fan, TCI spots+click, PTT-over-CAT, CW <LOGQSO>
- PGXL: remote AUTH password; direct-link meter fallback (VITA-first) with 250 ms poll + peak-hold; fan mode uses bare "fanmode=" (was ignored). - TCI: spot colour sent as decimal ARGB (ExpertSDR dropped the hex string); spot click handled via CLICKED_ON_SPOT / RX_CLICKED_ON_SPOT to fill the entry. - FlexRadio: clicking an OpsLog spot on the panadapter now applies the mode too. - Filter presets: the trash icon deletes again (Radix pointer-down intercept). - PTT hotkey: keys over CAT when a backend is active (was hijacked by a stale Audio-tab RTS/DTR serial setting); AltGr keys allowed; presses logged. - CW macro <LOGQSO>: logs after the preceding CW is sent, not on the first letter.
This commit is contained in:
+36
-30
@@ -1945,15 +1945,23 @@ export default function App() {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
const cfg = pttHotkeyRef.current;
|
||||
if (!cfg.enabled || !cfg.code || e.code !== cfg.code) return;
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) return; // plain key only
|
||||
// Allow AltGr (Ctrl+Alt together) — French/other layouts need it to reach
|
||||
// keys like "\" — but never hijack a real Ctrl- or Win-shortcut.
|
||||
if (e.metaKey || (e.ctrlKey && !e.altKey)) return;
|
||||
e.preventDefault();
|
||||
if (e.repeat) return;
|
||||
// Surface the result: this feature was impossible to diagnose because every
|
||||
// error was swallowed. The line names the key, the action and any failure.
|
||||
const fail = (err: any) => { UILog(`ptt hotkey: ${e.code} FAILED: ${err}`).catch(() => {}); };
|
||||
if (cfg.toggle) {
|
||||
pttToggledRef.current = !pttToggledRef.current;
|
||||
(pttToggledRef.current ? PTTHotkeyDown() : PTTHotkeyUp())?.catch?.(() => {});
|
||||
const on = pttToggledRef.current;
|
||||
UILog(`ptt hotkey: ${e.code} toggle → ${on ? 'KEY' : 'UNKEY'}`).catch(() => {});
|
||||
(on ? PTTHotkeyDown() : PTTHotkeyUp())?.catch?.(fail);
|
||||
} else if (!pttHeldRef.current) {
|
||||
pttHeldRef.current = true;
|
||||
PTTHotkeyDown().catch(() => {});
|
||||
UILog(`ptt hotkey: ${e.code} down → KEY`).catch(() => {});
|
||||
PTTHotkeyDown().catch(fail);
|
||||
}
|
||||
};
|
||||
const up = (e: KeyboardEvent) => {
|
||||
@@ -2837,36 +2845,34 @@ export default function App() {
|
||||
const sendFn = cwSourceRef.current === 'flex' ? FlexSendCW : cwSourceRef.current === 'icom' ? IcomSendCW : cwSourceRef.current === 'yaesu' ? YaesuSendCW : null;
|
||||
if (sendFn) await sendFn(keyed).catch((e) => setError(String(e?.message ?? e)));
|
||||
else await WinkeyerSend(keyed).catch((e) => setError(String(e?.message ?? e)));
|
||||
// Only WAIT for the CW to finish on the FINAL segment — auto-call needs the
|
||||
// send done before its gap+resend. A segment a <LOGQSO> follows is NOT waited
|
||||
// on: the keyer has already buffered the CW, so logging happens AT ONCE and
|
||||
// never interrupts what's being sent. (Gating the log on a CW-length ESTIMATE
|
||||
// made <LOGQSO> log up to ~10s late when the estimate ran long.)
|
||||
if (isLast) {
|
||||
if (isRig) {
|
||||
await waitMs(Math.round(estimateCwMs(resolved, wkWpm)) + 600);
|
||||
} else {
|
||||
// WinKeyer / serial keyer: the message takes AT LEAST its own length to
|
||||
// send, so the estimate is a floor and the busy signal only extends it.
|
||||
//
|
||||
// Waiting on busy alone made the auto-call gap start early: busy travels
|
||||
// from the keyer to this window as an event, and if it had not arrived
|
||||
// and cleared within the one-second start window the wait simply ended.
|
||||
// A 6 s gap then ran from the START of the CQ — about 2 s of silence
|
||||
// after a 4 s call, which is what an operator hears as "clearly not 6".
|
||||
const estMs = Math.round(estimateCwMs(resolved, wkWpm));
|
||||
const started = Date.now();
|
||||
const capMs = Math.round(estMs * 1.4) + 2500;
|
||||
const deadline = started + capMs;
|
||||
// The message's own duration, first — this is the part that must not be skipped.
|
||||
while (Date.now() - started < estMs && !aborted()) await sleep(50);
|
||||
// Then let a still-sending keyer finish (slow link, long buffer), capped.
|
||||
while (wkBusyRef.current && !aborted() && Date.now() < deadline) await sleep(50);
|
||||
}
|
||||
// WAIT for THIS segment's CW to finish before moving on — so a <LOGQSO>
|
||||
// that follows logs AFTER the text before it (e.g. "TU") has actually been
|
||||
// sent, not the instant it's buffered into the keyer. Buffering then logging
|
||||
// "at once" made a macro ending in "TU <LOGQSO>" log on the very first
|
||||
// letter. Auto-call also needs the send finished before its gap+resend.
|
||||
if (isRig) {
|
||||
await waitMs(Math.round(estimateCwMs(resolved, wkWpm)) + 600);
|
||||
} else {
|
||||
// WinKeyer / serial keyer: the message takes AT LEAST its own length to
|
||||
// send, so the estimate is a floor and the busy signal only extends it.
|
||||
//
|
||||
// Waiting on busy alone made the auto-call gap start early: busy travels
|
||||
// from the keyer to this window as an event, and if it had not arrived
|
||||
// and cleared within the one-second start window the wait simply ended.
|
||||
// A 6 s gap then ran from the START of the CQ — about 2 s of silence
|
||||
// after a 4 s call, which is what an operator hears as "clearly not 6".
|
||||
const estMs = Math.round(estimateCwMs(resolved, wkWpm));
|
||||
const started = Date.now();
|
||||
const capMs = Math.round(estMs * 1.4) + 2500;
|
||||
const deadline = started + capMs;
|
||||
// The message's own duration, first — this is the part that must not be skipped.
|
||||
while (Date.now() - started < estMs && !aborted()) await sleep(50);
|
||||
// Then let a still-sending keyer finish (slow link, long buffer), capped.
|
||||
while (wkBusyRef.current && !aborted() && Date.now() < deadline) await sleep(50);
|
||||
}
|
||||
}
|
||||
if (aborted()) return; // aborted while this segment was sending → don't log
|
||||
if (logAfter) void save(); // log NOW — the buffered CW keeps sending
|
||||
if (logAfter) void save(); // the preceding CW has finished → log at the token's spot
|
||||
}
|
||||
}
|
||||
// stopAutoCall cancels any running auto-call loop.
|
||||
|
||||
Reference in New Issue
Block a user