livedesk 0.1.459 → 0.1.461
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/README.md +28 -5
- package/bin/livedesk.js +223 -24
- package/bootstrap/runtime-lock.js +6 -0
- package/client/package.json +9 -8
- package/client/src/runtime/windows-owned-process-manifest.js +181 -14
- package/diagnostics/livedesk-mac-physical-gate-core.mjs +214 -26
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +406 -4
- package/diagnostics/livedesk-transport-physical-core.mjs +200 -1
- package/diagnostics/livedesk-transport-physical.mjs +60 -1
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +364 -49
- package/hub/src/server.js +4 -0
- package/hub/src/transport/udp-hub-transport.js +29 -6
- package/package.json +10 -9
- package/scripts/sync-web-dist.js +127 -0
- package/web/dist/assets/index-l4YzRK9d.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-JqqQ5DKN.js +0 -25
|
@@ -132,6 +132,7 @@ const verifyMonitorPixels = process.argv.includes('--verify-monitor-pixels') ||
|
|
|
132
132
|
const windowsInputAck = process.argv.includes('--windows-input-ack');
|
|
133
133
|
const windowsInputHookLateStart = process.argv.includes('--windows-input-hook-late-start');
|
|
134
134
|
const legacyFfmpegFirst = process.argv.includes('--legacy-ffmpeg-first');
|
|
135
|
+
const captureLifecycleProbe = process.argv.includes('--capture-lifecycle') || isMacPhysicalGate;
|
|
135
136
|
if (windowsInputAck && !isWindows) {
|
|
136
137
|
throw new Error('The Windows native input ACK diagnostic requires Windows hardware.');
|
|
137
138
|
}
|
|
@@ -192,6 +193,46 @@ async function waitFor(getValue, timeoutMs, label) {
|
|
|
192
193
|
throw new Error(`Timed out waiting for ${label}.`);
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
async function requestLocalHubJson(route, options = {}) {
|
|
197
|
+
return runMacDiagnosticAbortableAttempt(
|
|
198
|
+
async signal => {
|
|
199
|
+
const response = await fetch(`${baseUrl}${route}`, {
|
|
200
|
+
...options,
|
|
201
|
+
cache: 'no-store',
|
|
202
|
+
signal
|
|
203
|
+
});
|
|
204
|
+
const text = await response.text();
|
|
205
|
+
let payload = {};
|
|
206
|
+
try {
|
|
207
|
+
payload = text ? JSON.parse(text) : {};
|
|
208
|
+
} catch {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`${route} returned non-JSON HTTP ${response.status}: ${text.slice(0, 300)}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`${route} returned HTTP ${response.status}: ${JSON.stringify(payload)}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return payload;
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
abortState: isMacPhysicalGate ? macDiagnosticAbort : undefined,
|
|
222
|
+
timeoutMs: 20_000,
|
|
223
|
+
label: `Hub request ${route}`
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function postLocalHubJson(route, payload) {
|
|
229
|
+
return requestLocalHubJson(route, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
232
|
+
body: JSON.stringify(payload)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
195
236
|
function waitForWebSocketJson(
|
|
196
237
|
socket,
|
|
197
238
|
predicate,
|
|
@@ -768,8 +809,35 @@ let macDeliveredFps = 0;
|
|
|
768
809
|
let macFirstFrameMs = 0;
|
|
769
810
|
let macSwitchFirstFrameMs = 0;
|
|
770
811
|
let macTelemetry = {};
|
|
812
|
+
let macCaptureStages = { initial: null, switched: null };
|
|
771
813
|
let macTransport = {};
|
|
772
814
|
let macPlatformProfile = '';
|
|
815
|
+
let macInitialDisplayId = 0;
|
|
816
|
+
let macNextDisplayId = 0;
|
|
817
|
+
let macCaptureLifecycle = {
|
|
818
|
+
order: [],
|
|
819
|
+
wallGeneration: 0,
|
|
820
|
+
controlGeneration: 0,
|
|
821
|
+
resumedControlGeneration: 0,
|
|
822
|
+
restoredWallGeneration: 0,
|
|
823
|
+
wallHelperCount: -1,
|
|
824
|
+
controlHelperCount: -1,
|
|
825
|
+
pausedHelperCount: -1,
|
|
826
|
+
resumedControlHelperCount: -1,
|
|
827
|
+
controlStoppedHelperCount: -1,
|
|
828
|
+
restoredWallHelperCount: -1,
|
|
829
|
+
finalHelperCount: -1,
|
|
830
|
+
registryMaxConcurrent: -1,
|
|
831
|
+
registryFinalHelperCount: -1,
|
|
832
|
+
completedAt: '',
|
|
833
|
+
registrySampledAt: '',
|
|
834
|
+
pauseStopConfirmed: false,
|
|
835
|
+
pauseStopAcknowledged: false,
|
|
836
|
+
blockedWhilePaused: false,
|
|
837
|
+
resumeAccepted: false,
|
|
838
|
+
controlStopConfirmed: false,
|
|
839
|
+
finalStopConfirmed: false
|
|
840
|
+
};
|
|
773
841
|
let macReportPath = '';
|
|
774
842
|
let macReportClassification;
|
|
775
843
|
let macRunError = '';
|
|
@@ -834,6 +902,287 @@ const sampleMacHelpers = async () => {
|
|
|
834
902
|
.map(record => ({ pid: record.pid }));
|
|
835
903
|
};
|
|
836
904
|
|
|
905
|
+
async function waitForCaptureHelperCount(expectedCount, label) {
|
|
906
|
+
if (!isMacPhysicalGate) return -1;
|
|
907
|
+
const helpers = await waitFor(async () => {
|
|
908
|
+
const current = await sampleMacHelpers();
|
|
909
|
+
return current.length === expectedCount ? current : null;
|
|
910
|
+
}, 10_000, label);
|
|
911
|
+
return helpers.length;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async function waitForFreshCaptureRegistry(connectedDeviceId, completedAtEpochMs) {
|
|
915
|
+
if (!isMacPhysicalGate) return -1;
|
|
916
|
+
return waitFor(async signal => {
|
|
917
|
+
const response = await fetch(`${baseUrl}/api/remote/devices`, {
|
|
918
|
+
cache: 'no-store',
|
|
919
|
+
signal
|
|
920
|
+
});
|
|
921
|
+
if (!response.ok) return null;
|
|
922
|
+
const payload = await response.json();
|
|
923
|
+
const device = payload.devices?.find(item =>
|
|
924
|
+
item.deviceId === connectedDeviceId || item.id === connectedDeviceId
|
|
925
|
+
);
|
|
926
|
+
const capture = device?.status?.capture;
|
|
927
|
+
const maximum = Number(capture?.helperMaxConcurrent);
|
|
928
|
+
const helperCount = Number(capture?.helperCount);
|
|
929
|
+
const sampledAt = String(capture?.sampledAt || '');
|
|
930
|
+
const sampledAtEpochMs = Date.parse(sampledAt);
|
|
931
|
+
if (
|
|
932
|
+
!Number.isInteger(maximum)
|
|
933
|
+
|| maximum < 1
|
|
934
|
+
|| !Number.isInteger(helperCount)
|
|
935
|
+
|| helperCount < 0
|
|
936
|
+
|| !Number.isFinite(sampledAtEpochMs)
|
|
937
|
+
|| sampledAtEpochMs < completedAtEpochMs
|
|
938
|
+
) {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
maximum,
|
|
943
|
+
helperCount,
|
|
944
|
+
sampledAt
|
|
945
|
+
};
|
|
946
|
+
}, 20_000, 'fresh RemoteFast capture registry after lifecycle completion');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
async function waitForLifecycleFrame(connectedDeviceId, captureGeneration, streamPurpose) {
|
|
950
|
+
return waitFor(async signal => {
|
|
951
|
+
const response = await fetch(`${baseUrl}/api/remote/devices`, {
|
|
952
|
+
cache: 'no-store',
|
|
953
|
+
signal
|
|
954
|
+
});
|
|
955
|
+
if (!response.ok) return null;
|
|
956
|
+
const payload = await response.json();
|
|
957
|
+
const device = payload.devices?.find(item =>
|
|
958
|
+
item.deviceId === connectedDeviceId || item.id === connectedDeviceId
|
|
959
|
+
);
|
|
960
|
+
const frame = device?.latestLiveFrame;
|
|
961
|
+
const stream = device?.activeLiveStream;
|
|
962
|
+
if (
|
|
963
|
+
Number(frame?.captureGeneration || 0) !== Number(captureGeneration)
|
|
964
|
+
|| String(frame?.streamPurpose || '') !== streamPurpose
|
|
965
|
+
|| String(frame?.frameMode || '') !== 'mode3-h264-hw'
|
|
966
|
+
|| frame?.currentGenerationVerified !== true
|
|
967
|
+
|| Number(stream?.captureGeneration || 0) !== Number(captureGeneration)
|
|
968
|
+
|| String(stream?.streamPurpose || '') !== streamPurpose
|
|
969
|
+
|| stream?.readyFrameReceived !== true
|
|
970
|
+
) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
return { frame, stream, device };
|
|
974
|
+
}, 20_000, `${streamPurpose} lifecycle generation ${captureGeneration}`);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function lifecycleStartPayload(streamPurpose, reason) {
|
|
978
|
+
const control = streamPurpose === 'control';
|
|
979
|
+
return {
|
|
980
|
+
mode: 'mode3-h264-hw',
|
|
981
|
+
frameMode: 'mode3-h264-hw',
|
|
982
|
+
fps: 30,
|
|
983
|
+
maxWidth: control ? requestedWidth : Math.min(requestedWidth, 1280),
|
|
984
|
+
maxHeight: control ? requestedHeight : Math.min(requestedHeight, 720),
|
|
985
|
+
quality: 75,
|
|
986
|
+
monitorIndex: requestedMonitorIndex,
|
|
987
|
+
streamPurpose,
|
|
988
|
+
forceRestart: true,
|
|
989
|
+
restartToken: `capture-lifecycle-${streamPurpose}-${Date.now()}`,
|
|
990
|
+
restartReason: reason
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function runCaptureLifecycleProbe(connectedDeviceId) {
|
|
995
|
+
const route = `/api/remote/devices/${encodeURIComponent(connectedDeviceId)}/live`;
|
|
996
|
+
const evidence = {
|
|
997
|
+
order: [],
|
|
998
|
+
wallGeneration: 0,
|
|
999
|
+
controlGeneration: 0,
|
|
1000
|
+
resumedControlGeneration: 0,
|
|
1001
|
+
restoredWallGeneration: 0,
|
|
1002
|
+
wallHelperCount: -1,
|
|
1003
|
+
controlHelperCount: -1,
|
|
1004
|
+
pausedHelperCount: -1,
|
|
1005
|
+
resumedControlHelperCount: -1,
|
|
1006
|
+
controlStoppedHelperCount: -1,
|
|
1007
|
+
restoredWallHelperCount: -1,
|
|
1008
|
+
finalHelperCount: -1,
|
|
1009
|
+
registryMaxConcurrent: -1,
|
|
1010
|
+
registryFinalHelperCount: -1,
|
|
1011
|
+
completedAt: '',
|
|
1012
|
+
registrySampledAt: '',
|
|
1013
|
+
pauseStopConfirmed: false,
|
|
1014
|
+
pauseStopAcknowledged: false,
|
|
1015
|
+
blockedWhilePaused: false,
|
|
1016
|
+
resumeAccepted: false,
|
|
1017
|
+
controlStopConfirmed: false,
|
|
1018
|
+
finalStopConfirmed: false
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
const wallStart = await postLocalHubJson(
|
|
1022
|
+
`${route}/start`,
|
|
1023
|
+
lifecycleStartPayload('wall', 'physical-lifecycle-wall')
|
|
1024
|
+
);
|
|
1025
|
+
assert.equal(wallStart.ok, true, `Wall lifecycle start failed: ${JSON.stringify(wallStart)}`);
|
|
1026
|
+
await waitForLifecycleFrame(connectedDeviceId, wallStart.captureGeneration, 'wall');
|
|
1027
|
+
evidence.wallGeneration = Number(wallStart.captureGeneration || 0);
|
|
1028
|
+
evidence.wallHelperCount = await waitForCaptureHelperCount(1, 'one Wall capture helper');
|
|
1029
|
+
evidence.order.push('wall');
|
|
1030
|
+
|
|
1031
|
+
const controlStart = await postLocalHubJson(
|
|
1032
|
+
`${route}/start`,
|
|
1033
|
+
lifecycleStartPayload('control', 'physical-lifecycle-control')
|
|
1034
|
+
);
|
|
1035
|
+
assert.equal(
|
|
1036
|
+
controlStart.ok,
|
|
1037
|
+
true,
|
|
1038
|
+
`Control lifecycle start failed: ${JSON.stringify(controlStart)}`
|
|
1039
|
+
);
|
|
1040
|
+
assert.ok(
|
|
1041
|
+
Number(controlStart.captureGeneration) > evidence.wallGeneration,
|
|
1042
|
+
'Control must replace Wall with a newer capture generation.'
|
|
1043
|
+
);
|
|
1044
|
+
await waitForLifecycleFrame(connectedDeviceId, controlStart.captureGeneration, 'control');
|
|
1045
|
+
evidence.controlGeneration = Number(controlStart.captureGeneration || 0);
|
|
1046
|
+
evidence.controlHelperCount = await waitForCaptureHelperCount(
|
|
1047
|
+
1,
|
|
1048
|
+
'one Control capture helper'
|
|
1049
|
+
);
|
|
1050
|
+
evidence.order.push('control');
|
|
1051
|
+
|
|
1052
|
+
const paused = await postLocalHubJson(`${route}/pause`, {
|
|
1053
|
+
reason: 'physical-lifecycle-user-pause'
|
|
1054
|
+
});
|
|
1055
|
+
assert.equal(paused.ok, true, `Capture lifecycle pause failed: ${JSON.stringify(paused)}`);
|
|
1056
|
+
assert.ok(paused.pauseToken, 'Capture lifecycle pause did not return an owner token.');
|
|
1057
|
+
assert.equal(paused.phase, 'paused');
|
|
1058
|
+
assert.equal(paused.captureStopConfirmed, true);
|
|
1059
|
+
assert.ok(paused.stopCommandId, 'Pause did not identify its acknowledged stop command.');
|
|
1060
|
+
evidence.pauseStopConfirmed = true;
|
|
1061
|
+
evidence.pauseStopAcknowledged = true;
|
|
1062
|
+
evidence.pausedHelperCount = await waitForCaptureHelperCount(
|
|
1063
|
+
0,
|
|
1064
|
+
'capture helper drain during pause'
|
|
1065
|
+
);
|
|
1066
|
+
evidence.order.push('paused');
|
|
1067
|
+
|
|
1068
|
+
const blocked = await postLocalHubJson(
|
|
1069
|
+
`${route}/start`,
|
|
1070
|
+
lifecycleStartPayload('control', 'physical-lifecycle-paused-watchdog')
|
|
1071
|
+
);
|
|
1072
|
+
assert.equal(blocked.ok, false, 'Paused capture unexpectedly restarted.');
|
|
1073
|
+
assert.equal(blocked.error, 'LIVE_CAPTURE_PAUSED');
|
|
1074
|
+
evidence.blockedWhilePaused = true;
|
|
1075
|
+
if (isMacPhysicalGate) {
|
|
1076
|
+
await delay(200);
|
|
1077
|
+
evidence.pausedHelperCount = await waitForCaptureHelperCount(
|
|
1078
|
+
0,
|
|
1079
|
+
'paused watchdog helper suppression'
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
evidence.order.push('blocked');
|
|
1083
|
+
|
|
1084
|
+
const resumed = await postLocalHubJson(`${route}/resume`, {
|
|
1085
|
+
pauseToken: paused.pauseToken
|
|
1086
|
+
});
|
|
1087
|
+
assert.equal(resumed.ok, true, `Capture lifecycle resume failed: ${JSON.stringify(resumed)}`);
|
|
1088
|
+
evidence.resumeAccepted = true;
|
|
1089
|
+
evidence.order.push('resumed');
|
|
1090
|
+
|
|
1091
|
+
const resumedControl = await postLocalHubJson(
|
|
1092
|
+
`${route}/start`,
|
|
1093
|
+
lifecycleStartPayload('control', 'physical-lifecycle-control-resume')
|
|
1094
|
+
);
|
|
1095
|
+
assert.equal(
|
|
1096
|
+
resumedControl.ok,
|
|
1097
|
+
true,
|
|
1098
|
+
`Resumed Control lifecycle start failed: ${JSON.stringify(resumedControl)}`
|
|
1099
|
+
);
|
|
1100
|
+
assert.ok(
|
|
1101
|
+
Number(resumedControl.captureGeneration) > evidence.controlGeneration,
|
|
1102
|
+
'Resumed Control must own a newer capture generation.'
|
|
1103
|
+
);
|
|
1104
|
+
await waitForLifecycleFrame(
|
|
1105
|
+
connectedDeviceId,
|
|
1106
|
+
resumedControl.captureGeneration,
|
|
1107
|
+
'control'
|
|
1108
|
+
);
|
|
1109
|
+
evidence.resumedControlGeneration = Number(resumedControl.captureGeneration || 0);
|
|
1110
|
+
evidence.resumedControlHelperCount = await waitForCaptureHelperCount(
|
|
1111
|
+
1,
|
|
1112
|
+
'one resumed Control capture helper'
|
|
1113
|
+
);
|
|
1114
|
+
evidence.order.push('control-resumed');
|
|
1115
|
+
|
|
1116
|
+
const controlStopped = await postLocalHubJson(`${route}/stop`, {
|
|
1117
|
+
streamId: resumedControl.streamId,
|
|
1118
|
+
streamPurpose: 'control',
|
|
1119
|
+
reason: 'physical-lifecycle-control-leave'
|
|
1120
|
+
});
|
|
1121
|
+
assert.equal(
|
|
1122
|
+
controlStopped.ok,
|
|
1123
|
+
true,
|
|
1124
|
+
`Resumed Control stop failed: ${JSON.stringify(controlStopped)}`
|
|
1125
|
+
);
|
|
1126
|
+
assert.equal(
|
|
1127
|
+
controlStopped.captureStopConfirmed,
|
|
1128
|
+
true,
|
|
1129
|
+
'Control leave did not confirm native capture shutdown.'
|
|
1130
|
+
);
|
|
1131
|
+
evidence.controlStopConfirmed = true;
|
|
1132
|
+
evidence.controlStoppedHelperCount = await waitForCaptureHelperCount(
|
|
1133
|
+
0,
|
|
1134
|
+
'capture helper drain after Control leave'
|
|
1135
|
+
);
|
|
1136
|
+
evidence.order.push('control-stopped');
|
|
1137
|
+
|
|
1138
|
+
const restoredWall = await postLocalHubJson(
|
|
1139
|
+
`${route}/start`,
|
|
1140
|
+
lifecycleStartPayload('wall', 'physical-lifecycle-wall-restore')
|
|
1141
|
+
);
|
|
1142
|
+
assert.equal(
|
|
1143
|
+
restoredWall.ok,
|
|
1144
|
+
true,
|
|
1145
|
+
`Restored Wall lifecycle start failed: ${JSON.stringify(restoredWall)}`
|
|
1146
|
+
);
|
|
1147
|
+
assert.ok(
|
|
1148
|
+
Number(restoredWall.captureGeneration) > evidence.resumedControlGeneration,
|
|
1149
|
+
'Restored Wall must own a newer capture generation than resumed Control.'
|
|
1150
|
+
);
|
|
1151
|
+
await waitForLifecycleFrame(connectedDeviceId, restoredWall.captureGeneration, 'wall');
|
|
1152
|
+
evidence.restoredWallGeneration = Number(restoredWall.captureGeneration || 0);
|
|
1153
|
+
evidence.restoredWallHelperCount = await waitForCaptureHelperCount(
|
|
1154
|
+
1,
|
|
1155
|
+
'one restored Wall capture helper'
|
|
1156
|
+
);
|
|
1157
|
+
evidence.order.push('wall-restored');
|
|
1158
|
+
|
|
1159
|
+
const stopped = await postLocalHubJson(`${route}/stop`, {
|
|
1160
|
+
streamId: restoredWall.streamId,
|
|
1161
|
+
streamPurpose: 'wall',
|
|
1162
|
+
reason: 'physical-lifecycle-complete'
|
|
1163
|
+
});
|
|
1164
|
+
assert.equal(stopped.ok, true, `Restored Wall stop failed: ${JSON.stringify(stopped)}`);
|
|
1165
|
+
assert.equal(stopped.captureStopConfirmed, true, 'Final Wall stop was not confirmed.');
|
|
1166
|
+
evidence.finalStopConfirmed = true;
|
|
1167
|
+
evidence.finalHelperCount = await waitForCaptureHelperCount(
|
|
1168
|
+
0,
|
|
1169
|
+
'capture helper drain after lifecycle'
|
|
1170
|
+
);
|
|
1171
|
+
evidence.order.push('stopped');
|
|
1172
|
+
if (isMacPhysicalGate) {
|
|
1173
|
+
const completedAtEpochMs = Date.now();
|
|
1174
|
+
evidence.completedAt = new Date(completedAtEpochMs).toISOString();
|
|
1175
|
+
const registry = await waitForFreshCaptureRegistry(
|
|
1176
|
+
connectedDeviceId,
|
|
1177
|
+
completedAtEpochMs
|
|
1178
|
+
);
|
|
1179
|
+
evidence.registryMaxConcurrent = registry.maximum;
|
|
1180
|
+
evidence.registryFinalHelperCount = registry.helperCount;
|
|
1181
|
+
evidence.registrySampledAt = registry.sampledAt;
|
|
1182
|
+
}
|
|
1183
|
+
return evidence;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
837
1186
|
async function stopMacAgentAndSampleDrain() {
|
|
838
1187
|
if (!isMacPhysicalGate) return;
|
|
839
1188
|
if (helperSampleTimer) {
|
|
@@ -897,7 +1246,7 @@ async function writeMacPhysicalGateReport() {
|
|
|
897
1246
|
afterCleanup: summarizeMacDiagnosticResourceSamples(macResourceSamples.afterCleanup)
|
|
898
1247
|
};
|
|
899
1248
|
snapshot = {
|
|
900
|
-
schemaVersion:
|
|
1249
|
+
schemaVersion: 3,
|
|
901
1250
|
generatedAt,
|
|
902
1251
|
runtimeId: clientRuntimeId,
|
|
903
1252
|
requestedFps: 30,
|
|
@@ -915,10 +1264,14 @@ async function writeMacPhysicalGateReport() {
|
|
|
915
1264
|
count: macMonitorCount,
|
|
916
1265
|
initialGeneration: macInitialGeneration,
|
|
917
1266
|
nextGeneration: macNextGeneration,
|
|
1267
|
+
initialDisplayId: macInitialDisplayId,
|
|
1268
|
+
nextDisplayId: macNextDisplayId,
|
|
918
1269
|
initialHash: macInitialPixelHash,
|
|
919
1270
|
nextHash: macNextPixelHash
|
|
920
1271
|
},
|
|
921
1272
|
modifier: macModifierProbe,
|
|
1273
|
+
lifecycle: macCaptureLifecycle,
|
|
1274
|
+
stages: macCaptureStages,
|
|
922
1275
|
transport: macTransport,
|
|
923
1276
|
resources,
|
|
924
1277
|
diagnostic,
|
|
@@ -953,12 +1306,26 @@ async function writeMacPhysicalGateReport() {
|
|
|
953
1306
|
);
|
|
954
1307
|
console.log(
|
|
955
1308
|
`Mac physical gate monitor: count=${macMonitorCount}, generation=${macInitialGeneration}->${macNextGeneration}, `
|
|
1309
|
+
+ `display=${macInitialDisplayId || '-'}->${macNextDisplayId || '-'}, `
|
|
956
1310
|
+ `pixels=${macInitialPixelHash.slice(0, 12) || '-'}->${macNextPixelHash.slice(0, 12) || '-'}.`
|
|
957
1311
|
);
|
|
958
1312
|
console.log(
|
|
959
1313
|
`Mac physical gate input/transport: modifiers=${macModifierProbe.appliedAcks}/${macModifierProbe.expectedAcks}, `
|
|
960
1314
|
+ `transport=${macTransport.active || '-'}/${macTransport.protocol || '-'}/${macTransport.state || '-'}.`
|
|
961
1315
|
);
|
|
1316
|
+
console.log(
|
|
1317
|
+
`Mac physical gate lifecycle: ${macCaptureLifecycle.order.join(' -> ') || '-'}, `
|
|
1318
|
+
+ `helpers=${macCaptureLifecycle.wallHelperCount}`
|
|
1319
|
+
+ `/${macCaptureLifecycle.controlHelperCount}`
|
|
1320
|
+
+ `/${macCaptureLifecycle.pausedHelperCount}`
|
|
1321
|
+
+ `/${macCaptureLifecycle.resumedControlHelperCount}`
|
|
1322
|
+
+ `/${macCaptureLifecycle.controlStoppedHelperCount}`
|
|
1323
|
+
+ `/${macCaptureLifecycle.restoredWallHelperCount}`
|
|
1324
|
+
+ `/${macCaptureLifecycle.finalHelperCount}, `
|
|
1325
|
+
+ `registry-max=${macCaptureLifecycle.registryMaxConcurrent}, `
|
|
1326
|
+
+ `registry-final=${macCaptureLifecycle.registryFinalHelperCount}, `
|
|
1327
|
+
+ `registry-sampled=${macCaptureLifecycle.registrySampledAt || '-'}.`
|
|
1328
|
+
);
|
|
962
1329
|
for (const gateCheck of macReportClassification.checks) {
|
|
963
1330
|
console.log(`[${gateCheck.ok ? 'PASS' : 'FAIL'}] ${gateCheck.id}: ${gateCheck.detail}`);
|
|
964
1331
|
}
|
|
@@ -1218,9 +1585,20 @@ try {
|
|
|
1218
1585
|
});
|
|
1219
1586
|
const payload = await response.json();
|
|
1220
1587
|
const device = payload.devices?.find(item => item.deviceId === deviceId || item.id === deviceId);
|
|
1221
|
-
return device?.deviceId || device?.id || '';
|
|
1222
|
-
}, 15_000, 'Mode 3 smoke client');
|
|
1223
|
-
|
|
1588
|
+
return device?.deviceId || device?.id || '';
|
|
1589
|
+
}, 15_000, 'Mode 3 smoke client');
|
|
1590
|
+
|
|
1591
|
+
if (captureLifecycleProbe) {
|
|
1592
|
+
macCaptureLifecycle = await runCaptureLifecycleProbe(connectedDeviceId);
|
|
1593
|
+
console.log(
|
|
1594
|
+
`Capture lifecycle OK: ${macCaptureLifecycle.order.join(' -> ')}, `
|
|
1595
|
+
+ `generation ${macCaptureLifecycle.wallGeneration}`
|
|
1596
|
+
+ ` -> ${macCaptureLifecycle.controlGeneration}`
|
|
1597
|
+
+ ` -> ${macCaptureLifecycle.resumedControlGeneration}`
|
|
1598
|
+
+ ` -> ${macCaptureLifecycle.restoredWallGeneration}.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1224
1602
|
frameSocket = new WebSocket(`ws://127.0.0.1:${httpPort}/api/remote/frames/ws?devices=${encodeURIComponent(connectedDeviceId)}`);
|
|
1225
1603
|
await waitForWebSocketOpen(frameSocket);
|
|
1226
1604
|
|
|
@@ -1345,6 +1723,7 @@ try {
|
|
|
1345
1723
|
version: Number(telemetryFrame.metadata.nativeCaptureTelemetryVersion || 0),
|
|
1346
1724
|
ageMs: Number(telemetryFrame.metadata.nativeCaptureTelemetryAgeMs),
|
|
1347
1725
|
hardwareAccelerated: telemetryFrame.metadata.nativeEncoderHardwareAccelerated === true,
|
|
1726
|
+
displayId: Number(telemetryFrame.metadata.nativeCaptureDisplayId || 0),
|
|
1348
1727
|
captureFps: Number(telemetryFrame.metadata.nativeCaptureFps || 0),
|
|
1349
1728
|
compressionFps: Number(telemetryFrame.metadata.nativeCompressionFps || 0),
|
|
1350
1729
|
stdoutFps: Number(telemetryFrame.metadata.nativeEncodedFps || 0),
|
|
@@ -1361,6 +1740,12 @@ try {
|
|
|
1361
1740
|
reason: String(telemetryFrame.metadata.frameTransportReason || ''),
|
|
1362
1741
|
p2pReady: telemetryFrame.metadata.frameTransportP2pReady === true
|
|
1363
1742
|
};
|
|
1743
|
+
macInitialDisplayId = Number(macTelemetry.displayId || 0);
|
|
1744
|
+
macCaptureStages.initial = {
|
|
1745
|
+
deliveredFps,
|
|
1746
|
+
telemetry: { ...macTelemetry },
|
|
1747
|
+
transport: { ...macTransport }
|
|
1748
|
+
};
|
|
1364
1749
|
}
|
|
1365
1750
|
console.log(`Mode 3 control capture OK: first ${firstFrameMs.toFixed(0)}ms, ${deliveredFps.toFixed(1)} fps, native age ${averageFrameAgeMs.toFixed(1)}ms avg/${maxFrameAgeMs.toFixed(1)}ms max, ${frames[0].metadata.width}x${frames[0].metadata.height}, ${frames[0].metadata.hardwareEncoder}/${frames[0].metadata.platformProfile}.`);
|
|
1366
1751
|
if (process.argv.includes('--logs')) {
|
|
@@ -1453,6 +1838,15 @@ try {
|
|
|
1453
1838
|
const firstSwitchedFrame = switchedFrames[0];
|
|
1454
1839
|
activeInputBindingFrame = switchedFrames.at(-1);
|
|
1455
1840
|
const switchedFirstFrameMs = firstSwitchedFrame.receivedAt - switchedAt;
|
|
1841
|
+
const switchedSteadyFrames = switchedFrames.slice(
|
|
1842
|
+
Math.min(10, Math.max(0, switchedFrames.length - 2))
|
|
1843
|
+
);
|
|
1844
|
+
const switchedElapsedMs = switchedSteadyFrames.length > 1
|
|
1845
|
+
? switchedSteadyFrames.at(-1).receivedAt - switchedSteadyFrames[0].receivedAt
|
|
1846
|
+
: 0;
|
|
1847
|
+
const switchedDeliveredFps = switchedSteadyFrames.length > 1
|
|
1848
|
+
? (switchedSteadyFrames.length - 1) * 1000 / Math.max(1, switchedElapsedMs)
|
|
1849
|
+
: 0;
|
|
1456
1850
|
assert.equal(firstSwitchedFrame.metadata.isKeyFrame, true, 'Mode 3 monitor transition did not start on a key frame.');
|
|
1457
1851
|
assert.notEqual(String(firstSwitchedFrame.metadata.commandId || ''), previousCommandId, 'Mode 3 monitor transition reused the previous command binding.');
|
|
1458
1852
|
if (!isMacPhysicalGate) {
|
|
@@ -1531,6 +1925,7 @@ try {
|
|
|
1531
1925
|
version: Number(switchedTelemetryFrame.metadata.nativeCaptureTelemetryVersion || 0),
|
|
1532
1926
|
ageMs: Number(switchedTelemetryFrame.metadata.nativeCaptureTelemetryAgeMs),
|
|
1533
1927
|
hardwareAccelerated: switchedTelemetryFrame.metadata.nativeEncoderHardwareAccelerated === true,
|
|
1928
|
+
displayId: Number(switchedTelemetryFrame.metadata.nativeCaptureDisplayId || 0),
|
|
1534
1929
|
captureFps: Number(switchedTelemetryFrame.metadata.nativeCaptureFps || 0),
|
|
1535
1930
|
compressionFps: Number(switchedTelemetryFrame.metadata.nativeCompressionFps || 0),
|
|
1536
1931
|
stdoutFps: Number(switchedTelemetryFrame.metadata.nativeEncodedFps || 0),
|
|
@@ -1547,6 +1942,13 @@ try {
|
|
|
1547
1942
|
reason: String(switchedTelemetryFrame.metadata.frameTransportReason || ''),
|
|
1548
1943
|
p2pReady: switchedTelemetryFrame.metadata.frameTransportP2pReady === true
|
|
1549
1944
|
};
|
|
1945
|
+
macDeliveredFps = switchedDeliveredFps;
|
|
1946
|
+
macNextDisplayId = Number(macTelemetry.displayId || 0);
|
|
1947
|
+
macCaptureStages.switched = {
|
|
1948
|
+
deliveredFps: switchedDeliveredFps,
|
|
1949
|
+
telemetry: { ...macTelemetry },
|
|
1950
|
+
transport: { ...macTransport }
|
|
1951
|
+
};
|
|
1550
1952
|
}
|
|
1551
1953
|
macModifierProbe = await runMacModifierProbe({
|
|
1552
1954
|
deviceId: connectedDeviceId,
|