livedesk 0.1.457 → 0.1.459
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/bin/livedesk.js +18 -2
- package/client/package.json +5 -5
- package/diagnostics/livedesk-mac-physical-gate-core.mjs +389 -36
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +323 -21
- package/diagnostics/livedesk-transport-physical-core.mjs +503 -0
- package/diagnostics/livedesk-transport-physical.mjs +508 -0
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +841 -40
- package/hub/src/server.js +155 -5
- package/hub/src/transport/udp-hub-transport.js +110 -8
- package/hub/src/transport/udp-rendezvous.js +6 -2
- package/package.json +7 -7
- package/web/dist/assets/index-JqqQ5DKN.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-Bv9NFUjC.js +0 -25
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
+
import { execFile, spawn, spawnSync } from 'node:child_process';
|
|
5
5
|
import { createHash, randomBytes } from 'node:crypto';
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
@@ -13,13 +13,15 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
14
14
|
import WebSocket from 'ws';
|
|
15
15
|
import {
|
|
16
|
+
buildMacDiagnosticResourceSample,
|
|
16
17
|
buildMacModifierProbeSteps,
|
|
17
18
|
buildMacModifierReleaseSteps,
|
|
18
19
|
classifyMacPhysicalGate,
|
|
20
|
+
createAsyncSingleFlightSampler,
|
|
19
21
|
noteMacModifierAfterAppliedAck,
|
|
20
22
|
noteMacModifierBeforeSend,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
parseMacLiveDeskRuntimeProcesses,
|
|
24
|
+
summarizeMacDiagnosticResourceSamples
|
|
23
25
|
} from './livedesk-mac-physical-gate-core.mjs';
|
|
24
26
|
import {
|
|
25
27
|
acquireMacDiagnosticLease,
|
|
@@ -38,6 +40,7 @@ const publicMacPhysicalDiagnostic = String(rawArguments[0] || '').toLowerCase()
|
|
|
38
40
|
const macPhysicalGate = process.argv.includes('--mac-physical-gate') || publicMacPhysicalDiagnostic;
|
|
39
41
|
const isWindows = process.platform === 'win32';
|
|
40
42
|
const isMacPhysicalGate = process.platform === 'darwin' && macPhysicalGate;
|
|
43
|
+
const MAC_PHYSICAL_RESOURCE_SAMPLE_INTERVAL_MS = 500;
|
|
41
44
|
if (macPhysicalGate && !isMacPhysicalGate) {
|
|
42
45
|
const message = 'Mac physical diagnostic requires macOS hardware; no LiveDesk process was started.';
|
|
43
46
|
if (publicMacPhysicalDiagnostic) {
|
|
@@ -126,7 +129,15 @@ const requestedHeight = heightArgumentIndex >= 0
|
|
|
126
129
|
const transitionToMode5 = process.argv.includes('--transition-mode5');
|
|
127
130
|
const switchMonitor = process.argv.includes('--switch-monitor') || isMacPhysicalGate;
|
|
128
131
|
const verifyMonitorPixels = process.argv.includes('--verify-monitor-pixels') || isMacPhysicalGate;
|
|
132
|
+
const windowsInputAck = process.argv.includes('--windows-input-ack');
|
|
133
|
+
const windowsInputHookLateStart = process.argv.includes('--windows-input-hook-late-start');
|
|
129
134
|
const legacyFfmpegFirst = process.argv.includes('--legacy-ffmpeg-first');
|
|
135
|
+
if (windowsInputAck && !isWindows) {
|
|
136
|
+
throw new Error('The Windows native input ACK diagnostic requires Windows hardware.');
|
|
137
|
+
}
|
|
138
|
+
if (windowsInputHookLateStart && !windowsInputAck) {
|
|
139
|
+
throw new Error('The Windows hook late-start diagnostic requires --windows-input-ack.');
|
|
140
|
+
}
|
|
130
141
|
const reportArgumentIndex = rawArguments.findIndex(
|
|
131
142
|
argument => argument === '--report' || argument === '--gate-report'
|
|
132
143
|
);
|
|
@@ -313,9 +324,28 @@ function readMacProcessSnapshot() {
|
|
|
313
324
|
return String(result.stdout || '');
|
|
314
325
|
}
|
|
315
326
|
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
function readMacProcessResourceSnapshot() {
|
|
328
|
+
return new Promise((resolveSnapshot, rejectSnapshot) => {
|
|
329
|
+
execFile(
|
|
330
|
+
'/bin/ps',
|
|
331
|
+
['-axo', 'pid=,ppid=,comm=,%cpu=,rss=,args='],
|
|
332
|
+
{
|
|
333
|
+
encoding: 'utf8',
|
|
334
|
+
timeout: 2_000,
|
|
335
|
+
windowsHide: true,
|
|
336
|
+
maxBuffer: 4 * 1024 * 1024
|
|
337
|
+
},
|
|
338
|
+
(error, stdout, stderr) => {
|
|
339
|
+
if (error) {
|
|
340
|
+
rejectSnapshot(new Error(
|
|
341
|
+
`resource ps failed: ${String(stderr || error.message || error)}`
|
|
342
|
+
));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
resolveSnapshot(String(stdout || ''));
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
});
|
|
319
349
|
}
|
|
320
350
|
|
|
321
351
|
function readConflictingMacLiveDeskRuntimes() {
|
|
@@ -446,6 +476,150 @@ async function runMacModifierProbe(binding) {
|
|
|
446
476
|
};
|
|
447
477
|
}
|
|
448
478
|
|
|
479
|
+
async function runWindowsInputAckProbe(binding, { expectHookLateStartFailure = false } = {}) {
|
|
480
|
+
const socket = new WebSocket(`ws://127.0.0.1:${httpPort}/api/remote/input/ws`, {
|
|
481
|
+
perMessageDeflate: false
|
|
482
|
+
});
|
|
483
|
+
const sendInput = async (input, timeoutMs = 5_000) => {
|
|
484
|
+
const outcome = waitForWebSocketJson(
|
|
485
|
+
socket,
|
|
486
|
+
message => (
|
|
487
|
+
(message.type === 'RemoteInputApplied' || message.type === 'RemoteInputError')
|
|
488
|
+
&& Number(message.inputSeq) === Number(input.inputSeq)
|
|
489
|
+
),
|
|
490
|
+
timeoutMs
|
|
491
|
+
);
|
|
492
|
+
socket.send(JSON.stringify({
|
|
493
|
+
type: 'input',
|
|
494
|
+
requestId: input.inputEventId,
|
|
495
|
+
deviceId: binding.deviceId,
|
|
496
|
+
input: {
|
|
497
|
+
...input,
|
|
498
|
+
requestAck: true,
|
|
499
|
+
issuedAtEpochMs: Date.now(),
|
|
500
|
+
monitorIndex: binding.monitorIndex,
|
|
501
|
+
controlSessionId: binding.sessionId,
|
|
502
|
+
controlCommandId: binding.commandId,
|
|
503
|
+
captureGeneration: binding.captureGeneration
|
|
504
|
+
}
|
|
505
|
+
}));
|
|
506
|
+
return outcome;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
let resetApplied = false;
|
|
510
|
+
try {
|
|
511
|
+
const ready = waitForWebSocketJson(socket, message => message.type === 'RemoteInputSocketReady');
|
|
512
|
+
await Promise.all([waitForWebSocketOpen(socket), ready]);
|
|
513
|
+
const probe = await sendInput({
|
|
514
|
+
type: 'keyboard.probe',
|
|
515
|
+
inputSeq: 1,
|
|
516
|
+
inputEventId: `windows-physical-probe-${Date.now()}`,
|
|
517
|
+
pressedCodes: [],
|
|
518
|
+
shiftKey: false,
|
|
519
|
+
ctrlKey: false,
|
|
520
|
+
altKey: false,
|
|
521
|
+
metaKey: false,
|
|
522
|
+
repeat: false
|
|
523
|
+
});
|
|
524
|
+
if (expectHookLateStartFailure) {
|
|
525
|
+
assert.equal(
|
|
526
|
+
probe.type,
|
|
527
|
+
'RemoteInputError',
|
|
528
|
+
'The forced late hook start unexpectedly reached native input injection.'
|
|
529
|
+
);
|
|
530
|
+
assert.match(
|
|
531
|
+
String(probe.error || ''),
|
|
532
|
+
/diagnostic keyboard isolation hook did not become ready/i,
|
|
533
|
+
'The forced late hook start did not fail at the bounded ready deadline.'
|
|
534
|
+
);
|
|
535
|
+
const reset = await sendInput({
|
|
536
|
+
type: 'keyboard.reset',
|
|
537
|
+
inputSeq: 2,
|
|
538
|
+
inputEventId: `windows-late-start-reset-${Date.now()}`,
|
|
539
|
+
pressedCodes: [],
|
|
540
|
+
shiftKey: false,
|
|
541
|
+
ctrlKey: false,
|
|
542
|
+
altKey: false,
|
|
543
|
+
metaKey: false,
|
|
544
|
+
repeat: false,
|
|
545
|
+
reason: 'windows-hook-late-start-cleanup'
|
|
546
|
+
});
|
|
547
|
+
assert.equal(reset.type, 'RemoteInputApplied', String(reset.error || 'Windows late-start reset failed.'));
|
|
548
|
+
assert.equal(reset.duplicate, false, 'Windows late-start reset was acknowledged as a duplicate.');
|
|
549
|
+
resetApplied = true;
|
|
550
|
+
return {
|
|
551
|
+
lateStartFailure: true,
|
|
552
|
+
error: String(probe.error || ''),
|
|
553
|
+
resetApplied
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
assert.equal(probe.type, 'RemoteInputApplied', String(probe.error || 'Windows native input probe failed.'));
|
|
557
|
+
assert.equal(probe.duplicate, false, 'Windows native input probe was acknowledged as a duplicate.');
|
|
558
|
+
assert.equal(probe.nativeInputBackend, 'win32-sendinput-hook-isolated');
|
|
559
|
+
assert.equal(Number(probe.nativeEventsRequested), 8);
|
|
560
|
+
assert.equal(Number(probe.nativeEventsAccepted), 8);
|
|
561
|
+
assert.equal(Number(probe.hookEventsObserved), 8);
|
|
562
|
+
assert.equal(Number(probe.hookEventsBlocked), 8);
|
|
563
|
+
assert.equal(probe.focusChanged, false, 'Windows native input probe changed the foreground window.');
|
|
564
|
+
assert.equal(probe.inputDiagnostic, true);
|
|
565
|
+
|
|
566
|
+
const reset = await sendInput({
|
|
567
|
+
type: 'keyboard.reset',
|
|
568
|
+
inputSeq: 2,
|
|
569
|
+
inputEventId: `windows-physical-reset-${Date.now()}`,
|
|
570
|
+
pressedCodes: [],
|
|
571
|
+
shiftKey: false,
|
|
572
|
+
ctrlKey: false,
|
|
573
|
+
altKey: false,
|
|
574
|
+
metaKey: false,
|
|
575
|
+
repeat: false,
|
|
576
|
+
reason: 'windows-native-input-diagnostic-cleanup'
|
|
577
|
+
});
|
|
578
|
+
assert.equal(reset.type, 'RemoteInputApplied', String(reset.error || 'Windows native input reset failed.'));
|
|
579
|
+
assert.equal(reset.duplicate, false, 'Windows native input reset was acknowledged as a duplicate.');
|
|
580
|
+
resetApplied = true;
|
|
581
|
+
return {
|
|
582
|
+
backend: String(probe.nativeInputBackend || ''),
|
|
583
|
+
requested: Number(probe.nativeEventsRequested || 0),
|
|
584
|
+
accepted: Number(probe.nativeEventsAccepted || 0),
|
|
585
|
+
hookObserved: Number(probe.hookEventsObserved || 0),
|
|
586
|
+
hookBlocked: Number(probe.hookEventsBlocked || 0),
|
|
587
|
+
duplicate: probe.duplicate === true,
|
|
588
|
+
focusChanged: probe.focusChanged === true,
|
|
589
|
+
resetApplied
|
|
590
|
+
};
|
|
591
|
+
} finally {
|
|
592
|
+
if (!resetApplied && socket.readyState === WebSocket.OPEN) {
|
|
593
|
+
try {
|
|
594
|
+
socket.send(JSON.stringify({
|
|
595
|
+
type: 'input',
|
|
596
|
+
deviceId: binding.deviceId,
|
|
597
|
+
fireAndForget: true,
|
|
598
|
+
input: {
|
|
599
|
+
type: 'keyboard.reset',
|
|
600
|
+
inputSeq: 0,
|
|
601
|
+
requestAck: false,
|
|
602
|
+
pressedCodes: [],
|
|
603
|
+
shiftKey: false,
|
|
604
|
+
ctrlKey: false,
|
|
605
|
+
altKey: false,
|
|
606
|
+
metaKey: false,
|
|
607
|
+
repeat: false,
|
|
608
|
+
reason: 'windows-native-input-diagnostic-finally',
|
|
609
|
+
monitorIndex: binding.monitorIndex,
|
|
610
|
+
controlSessionId: binding.sessionId,
|
|
611
|
+
controlCommandId: binding.commandId,
|
|
612
|
+
captureGeneration: binding.captureGeneration
|
|
613
|
+
}
|
|
614
|
+
}));
|
|
615
|
+
} catch {
|
|
616
|
+
// Closing the exact input owner also triggers RemoteFast ReleaseAll.
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
try { socket.close(); } catch {}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
449
623
|
async function decodeKeyFrame(accessUnit, label) {
|
|
450
624
|
const directory = await mkdtemp(path.join(tmpdir(), `livedesk-${label}-`));
|
|
451
625
|
const inputPath = path.join(directory, 'frame.h264');
|
|
@@ -469,6 +643,10 @@ async function decodeKeyFrame(accessUnit, label) {
|
|
|
469
643
|
windowsHide: true
|
|
470
644
|
});
|
|
471
645
|
ownedDecoders.set(decoder, { directory, label });
|
|
646
|
+
if (isMacPhysicalGate && Number(decoder?.pid || 0) > 1) {
|
|
647
|
+
macOwnedDecoderPids.add(Number(decoder.pid));
|
|
648
|
+
requestMacResourceSample('steadyCapture');
|
|
649
|
+
}
|
|
472
650
|
let errors = '';
|
|
473
651
|
let settled = false;
|
|
474
652
|
let unsubscribeAbort = () => {};
|
|
@@ -566,12 +744,20 @@ let hub;
|
|
|
566
744
|
let agent;
|
|
567
745
|
let frameSocket;
|
|
568
746
|
const ownedDecoders = new Map();
|
|
747
|
+
const macOwnedDecoderPids = new Set();
|
|
569
748
|
let hubLogs = [];
|
|
570
749
|
let agentLogs = [];
|
|
571
750
|
let helperSampleTimer;
|
|
572
751
|
let helperMaxConcurrent = 0;
|
|
573
752
|
const helperObservedPids = new Set();
|
|
574
753
|
let helperFinalCount = 0;
|
|
754
|
+
let macResourcePhase = '';
|
|
755
|
+
const macResourceSamples = {
|
|
756
|
+
baseline: [],
|
|
757
|
+
steadyCapture: [],
|
|
758
|
+
afterCleanup: []
|
|
759
|
+
};
|
|
760
|
+
const macResourceSamplingErrors = new Set();
|
|
575
761
|
let macModifierProbe = { expectedAcks: 8, appliedAcks: 0, applied: [], errors: [] };
|
|
576
762
|
let macInitialPixelHash = '';
|
|
577
763
|
let macNextPixelHash = '';
|
|
@@ -590,12 +776,62 @@ let macRunError = '';
|
|
|
590
776
|
const macCleanupErrors = [];
|
|
591
777
|
const macSignalHandlers = new Map();
|
|
592
778
|
|
|
593
|
-
const
|
|
779
|
+
const collectMacResourceSample = async phase => {
|
|
780
|
+
if (!isMacPhysicalGate) {
|
|
781
|
+
return {
|
|
782
|
+
totalLiveDeskProcessCount: 0,
|
|
783
|
+
captureHelperCount: 0,
|
|
784
|
+
unownedLiveDeskPids: [],
|
|
785
|
+
processes: []
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
let sample;
|
|
789
|
+
try {
|
|
790
|
+
sample = buildMacDiagnosticResourceSample(
|
|
791
|
+
await readMacProcessResourceSnapshot(),
|
|
792
|
+
{
|
|
793
|
+
diagnosticPid: process.pid,
|
|
794
|
+
hubPid: hub?.pid,
|
|
795
|
+
agentPid: agent?.pid,
|
|
796
|
+
decoderPids: [...ownedDecoders.keys()]
|
|
797
|
+
.filter(decoder => decoder?.exitCode === null && decoder?.signalCode === null)
|
|
798
|
+
.map(decoder => decoder?.pid)
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
} catch {
|
|
802
|
+
const safePhase = String(phase || 'unassigned').replace(/[^a-z-]/gi, '') || 'unassigned';
|
|
803
|
+
macResourceSamplingErrors.add(`${safePhase}:unavailable`);
|
|
804
|
+
throw new Error(`Mac ${safePhase} process resource telemetry is unavailable.`);
|
|
805
|
+
}
|
|
806
|
+
helperMaxConcurrent = Math.max(helperMaxConcurrent, sample.captureHelperCount);
|
|
807
|
+
for (const helper of sample.processes.filter(record => record.role === 'capture-helper')) {
|
|
808
|
+
helperObservedPids.add(helper.pid);
|
|
809
|
+
}
|
|
810
|
+
if (Object.prototype.hasOwnProperty.call(macResourceSamples, phase)) {
|
|
811
|
+
const phaseSamples = macResourceSamples[phase];
|
|
812
|
+
phaseSamples.push(sample);
|
|
813
|
+
while (phaseSamples.length > 240) phaseSamples.shift();
|
|
814
|
+
}
|
|
815
|
+
return sample;
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const macResourceSampler = createAsyncSingleFlightSampler(collectMacResourceSample);
|
|
819
|
+
|
|
820
|
+
const sampleMacResources = phase => macResourceSampler.run(phase);
|
|
821
|
+
|
|
822
|
+
const requestMacResourceSample = phase => {
|
|
823
|
+
const pending = macResourceSampler.request(phase);
|
|
824
|
+
pending?.catch(() => {
|
|
825
|
+
// A later required sample reports persistent process telemetry failure.
|
|
826
|
+
});
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const sampleMacHelpers = async () => {
|
|
594
830
|
if (!isMacPhysicalGate) return [];
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
831
|
+
const sample = await sampleMacResources(macResourcePhase);
|
|
832
|
+
return sample.processes
|
|
833
|
+
.filter(record => record.role === 'capture-helper')
|
|
834
|
+
.map(record => ({ pid: record.pid }));
|
|
599
835
|
};
|
|
600
836
|
|
|
601
837
|
async function stopMacAgentAndSampleDrain() {
|
|
@@ -614,7 +850,7 @@ async function stopMacAgentAndSampleDrain() {
|
|
|
614
850
|
} finally {
|
|
615
851
|
const drainDeadline = Date.now() + 5_000;
|
|
616
852
|
do {
|
|
617
|
-
const helpers = sampleMacHelpers();
|
|
853
|
+
const helpers = await sampleMacHelpers();
|
|
618
854
|
helperFinalCount = helpers.length;
|
|
619
855
|
if (helperFinalCount === 0) break;
|
|
620
856
|
await delay(100);
|
|
@@ -641,8 +877,27 @@ async function writeMacPhysicalGateReport() {
|
|
|
641
877
|
while (true) {
|
|
642
878
|
const diagnostic = readDiagnosticState();
|
|
643
879
|
const stateToken = diagnosticStateToken(diagnostic);
|
|
880
|
+
const expectedSteadyProcesses = [
|
|
881
|
+
{ role: 'hub', pid: hub?.pid, ownedByDiagnostic: true },
|
|
882
|
+
{ role: 'remote-fast', pid: agent?.pid, ownedByDiagnostic: true },
|
|
883
|
+
...[...macOwnedDecoderPids].map(pid => ({
|
|
884
|
+
role: 'decoder',
|
|
885
|
+
pid,
|
|
886
|
+
ownedByDiagnostic: true
|
|
887
|
+
}))
|
|
888
|
+
];
|
|
889
|
+
const resources = {
|
|
890
|
+
sampleIntervalMs: MAC_PHYSICAL_RESOURCE_SAMPLE_INTERVAL_MS,
|
|
891
|
+
samplingErrors: [...macResourceSamplingErrors],
|
|
892
|
+
baseline: summarizeMacDiagnosticResourceSamples(macResourceSamples.baseline),
|
|
893
|
+
steadyCapture: summarizeMacDiagnosticResourceSamples(
|
|
894
|
+
macResourceSamples.steadyCapture,
|
|
895
|
+
{ expectedProcesses: expectedSteadyProcesses }
|
|
896
|
+
),
|
|
897
|
+
afterCleanup: summarizeMacDiagnosticResourceSamples(macResourceSamples.afterCleanup)
|
|
898
|
+
};
|
|
644
899
|
snapshot = {
|
|
645
|
-
schemaVersion:
|
|
900
|
+
schemaVersion: 2,
|
|
646
901
|
generatedAt,
|
|
647
902
|
runtimeId: clientRuntimeId,
|
|
648
903
|
requestedFps: 30,
|
|
@@ -665,6 +920,7 @@ async function writeMacPhysicalGateReport() {
|
|
|
665
920
|
},
|
|
666
921
|
modifier: macModifierProbe,
|
|
667
922
|
transport: macTransport,
|
|
923
|
+
resources,
|
|
668
924
|
diagnostic,
|
|
669
925
|
error: diagnostic.runError
|
|
670
926
|
};
|
|
@@ -688,6 +944,13 @@ async function writeMacPhysicalGateReport() {
|
|
|
688
944
|
`Mac physical gate helper: max=${helperMaxConcurrent}, final=${helperFinalCount}, `
|
|
689
945
|
+ `pids=${[...helperObservedPids].join(',') || '-'}.`
|
|
690
946
|
);
|
|
947
|
+
const resourceEvidence = snapshot.resources;
|
|
948
|
+
console.log(
|
|
949
|
+
`Mac physical gate resources: baseline=${resourceEvidence.baseline.totalLiveDeskProcessCount}, `
|
|
950
|
+
+ `steady-peak=${resourceEvidence.steadyCapture.totalLiveDeskProcessCountPeak}, `
|
|
951
|
+
+ `after-cleanup=${resourceEvidence.afterCleanup.totalLiveDeskProcessCount}, `
|
|
952
|
+
+ `samples=${resourceEvidence.steadyCapture.sampleCount}.`
|
|
953
|
+
);
|
|
691
954
|
console.log(
|
|
692
955
|
`Mac physical gate monitor: count=${macMonitorCount}, generation=${macInitialGeneration}->${macNextGeneration}, `
|
|
693
956
|
+ `pixels=${macInitialPixelHash.slice(0, 12) || '-'}->${macNextPixelHash.slice(0, 12) || '-'}.`
|
|
@@ -766,6 +1029,12 @@ async function cleanupOwnedDiagnosticResources() {
|
|
|
766
1029
|
}
|
|
767
1030
|
}
|
|
768
1031
|
if (!isMacPhysicalGate) return;
|
|
1032
|
+
macResourcePhase = 'afterCleanup';
|
|
1033
|
+
try {
|
|
1034
|
+
await sampleMacResources(macResourcePhase);
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
recordMacCleanupError('resource-snapshot-after-cleanup', error);
|
|
1037
|
+
}
|
|
769
1038
|
const teardownPlan = buildMacDiagnosticTeardownPlan({
|
|
770
1039
|
agentStopped,
|
|
771
1040
|
hubStopped,
|
|
@@ -843,14 +1112,12 @@ try {
|
|
|
843
1112
|
macIsolation = await prepareMacDiagnosticIsolation();
|
|
844
1113
|
if (isMacPhysicalGate) {
|
|
845
1114
|
macDiagnosticAbort.throwIfRequested();
|
|
846
|
-
|
|
1115
|
+
macResourcePhase = 'baseline';
|
|
1116
|
+
await sampleMacResources(macResourcePhase);
|
|
1117
|
+
macResourcePhase = '';
|
|
847
1118
|
helperSampleTimer = setInterval(() => {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
} catch {
|
|
851
|
-
// The final synchronous sample reports a persistent ps failure.
|
|
852
|
-
}
|
|
853
|
-
}, 100);
|
|
1119
|
+
requestMacResourceSample(macResourcePhase);
|
|
1120
|
+
}, MAC_PHYSICAL_RESOURCE_SAMPLE_INTERVAL_MS);
|
|
854
1121
|
helperSampleTimer.unref?.();
|
|
855
1122
|
}
|
|
856
1123
|
const isolatedHubEnvironment = isMacPhysicalGate
|
|
@@ -929,6 +1196,10 @@ try {
|
|
|
929
1196
|
LIVEDESK_UDP_PREFER_P2P: '0',
|
|
930
1197
|
LIVEDESK_RELAY_ENABLED: '0'
|
|
931
1198
|
} : {}),
|
|
1199
|
+
...(windowsInputAck ? { LIVEDESK_INPUT_DIAGNOSTIC: '1' } : {}),
|
|
1200
|
+
...(windowsInputHookLateStart ? {
|
|
1201
|
+
LIVEDESK_INPUT_DIAGNOSTIC_HOOK_START_DELAY_MS: '2500'
|
|
1202
|
+
} : {}),
|
|
932
1203
|
LIVEDESK_FFMPEG: legacyFfmpegFirst ? ffmpegPath : ffmpegStaticPath,
|
|
933
1204
|
LIVEDESK_FFMPEG_PATHS: (legacyFfmpegFirst
|
|
934
1205
|
? [ffmpegPath, ffmpegStaticPath]
|
|
@@ -989,7 +1260,11 @@ try {
|
|
|
989
1260
|
frameSocket.on('message', handleMessage);
|
|
990
1261
|
});
|
|
991
1262
|
|
|
992
|
-
|
|
1263
|
+
if (isMacPhysicalGate) {
|
|
1264
|
+
macResourcePhase = 'steadyCapture';
|
|
1265
|
+
await sampleMacResources(macResourcePhase);
|
|
1266
|
+
}
|
|
1267
|
+
frameSocket.send(JSON.stringify({
|
|
993
1268
|
type: 'subscribe',
|
|
994
1269
|
deviceIds: [connectedDeviceId],
|
|
995
1270
|
autoStartLive: true,
|
|
@@ -1007,6 +1282,7 @@ try {
|
|
|
1007
1282
|
}));
|
|
1008
1283
|
|
|
1009
1284
|
const frames = await withMacDiagnosticAbort(framePromise, cancelFrameWait);
|
|
1285
|
+
let activeInputBindingFrame = frames.at(-1);
|
|
1010
1286
|
const initialCaptureIdentity = switchMonitor && isWindows
|
|
1011
1287
|
? await waitFor(
|
|
1012
1288
|
() => findWindowsCaptureIdentity(agentLogs, requestedMonitorIndex),
|
|
@@ -1175,6 +1451,7 @@ try {
|
|
|
1175
1451
|
)
|
|
1176
1452
|
: null;
|
|
1177
1453
|
const firstSwitchedFrame = switchedFrames[0];
|
|
1454
|
+
activeInputBindingFrame = switchedFrames.at(-1);
|
|
1178
1455
|
const switchedFirstFrameMs = firstSwitchedFrame.receivedAt - switchedAt;
|
|
1179
1456
|
assert.equal(firstSwitchedFrame.metadata.isKeyFrame, true, 'Mode 3 monitor transition did not start on a key frame.');
|
|
1180
1457
|
assert.notEqual(String(firstSwitchedFrame.metadata.commandId || ''), previousCommandId, 'Mode 3 monitor transition reused the previous command binding.');
|
|
@@ -1286,6 +1563,31 @@ try {
|
|
|
1286
1563
|
console.log(`Mode 3 monitor transition OK: ${requestedMonitorIndex} -> ${nextMonitorIndex}, first frame ${switchedFirstFrameMs.toFixed(0)}ms, generation ${previousCaptureGeneration} -> ${firstSwitchedFrame.metadata.captureGeneration}.`);
|
|
1287
1564
|
}
|
|
1288
1565
|
|
|
1566
|
+
if (windowsInputAck) {
|
|
1567
|
+
const inputProbe = await runWindowsInputAckProbe({
|
|
1568
|
+
deviceId: connectedDeviceId,
|
|
1569
|
+
sessionId: String(activeInputBindingFrame.metadata.sessionId || ''),
|
|
1570
|
+
commandId: String(activeInputBindingFrame.metadata.commandId || ''),
|
|
1571
|
+
captureGeneration: Number(activeInputBindingFrame.metadata.captureGeneration || 0),
|
|
1572
|
+
monitorIndex: Number(activeInputBindingFrame.metadata.monitorIndex || 0)
|
|
1573
|
+
}, {
|
|
1574
|
+
expectHookLateStartFailure: windowsInputHookLateStart
|
|
1575
|
+
});
|
|
1576
|
+
if (windowsInputHookLateStart) {
|
|
1577
|
+
console.log(
|
|
1578
|
+
`Windows diagnostic hook late-start cleanup OK: expected timeout, `
|
|
1579
|
+
+ `Agent responsive, reset=${inputProbe.resetApplied}.`
|
|
1580
|
+
);
|
|
1581
|
+
} else {
|
|
1582
|
+
console.log(
|
|
1583
|
+
`Windows native modifier input ACK: ${inputProbe.accepted}/${inputProbe.requested} `
|
|
1584
|
+
+ `via ${inputProbe.backend}, hook=${inputProbe.hookObserved}/${inputProbe.hookBlocked}, `
|
|
1585
|
+
+ `duplicate=${inputProbe.duplicate}, `
|
|
1586
|
+
+ `focusChanged=${inputProbe.focusChanged}, reset=${inputProbe.resetApplied}.`
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1289
1591
|
if (transitionToMode5) {
|
|
1290
1592
|
const previousFrameSocket = frameSocket;
|
|
1291
1593
|
frameSocket = new WebSocket(`ws://127.0.0.1:${httpPort}/api/remote/frames/ws?devices=${encodeURIComponent(connectedDeviceId)}`);
|