getobsrv 0.7.0 → 0.7.1
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/out/main/index.js
CHANGED
|
@@ -157,7 +157,14 @@ function parseUiState(raw) {
|
|
|
157
157
|
if (typeof profileId !== "string" || profileId.length === 0 || profileId.length > MAX_UI_ID) return null;
|
|
158
158
|
if (viewMode !== "1:1" && viewMode !== "fit") return null;
|
|
159
159
|
if (mode !== "url" && mode !== "image") return null;
|
|
160
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
presetId,
|
|
162
|
+
profileId,
|
|
163
|
+
viewMode,
|
|
164
|
+
mode,
|
|
165
|
+
targetBounds: parseRect(raw.targetBounds),
|
|
166
|
+
canvasBounds: parseRect(raw.canvasBounds)
|
|
167
|
+
};
|
|
161
168
|
}
|
|
162
169
|
function parseScrollPos(raw) {
|
|
163
170
|
if (!isRecord$1(raw)) return null;
|
|
@@ -759,6 +766,7 @@ function registerIpc(ctx) {
|
|
|
759
766
|
});
|
|
760
767
|
const uiState = { presetId: "1080p-24", profileId: "reference", viewMode: "1:1", mode: "url" };
|
|
761
768
|
let targetBounds = null;
|
|
769
|
+
let canvasBounds = null;
|
|
762
770
|
const MAX_PENDING_APPLIES = 32;
|
|
763
771
|
let rendererReported = false;
|
|
764
772
|
let warnedPendingOverflow = false;
|
|
@@ -767,9 +775,10 @@ function registerIpc(ctx) {
|
|
|
767
775
|
if (!fromRenderer(e)) return;
|
|
768
776
|
const s = parseUiState(raw);
|
|
769
777
|
if (!s) return;
|
|
770
|
-
const { targetBounds: bounds, ...state } = s;
|
|
778
|
+
const { targetBounds: bounds, canvasBounds: canvas, ...state } = s;
|
|
771
779
|
Object.assign(uiState, state);
|
|
772
780
|
targetBounds = bounds ?? null;
|
|
781
|
+
canvasBounds = canvas ?? null;
|
|
773
782
|
if (!rendererReported) {
|
|
774
783
|
rendererReported = true;
|
|
775
784
|
for (const patch of pendingApplies.splice(0)) {
|
|
@@ -777,6 +786,46 @@ function registerIpc(ctx) {
|
|
|
777
786
|
}
|
|
778
787
|
}
|
|
779
788
|
});
|
|
789
|
+
const SETTLE_POLL_MS = 80;
|
|
790
|
+
const SETTLE_STABLE_READS = 2;
|
|
791
|
+
const SETTLE_BUDGET_MS = 4e3;
|
|
792
|
+
const SETTLE_DRAW_MS = 120;
|
|
793
|
+
const nextFrame = (budgetMs) => new Promise((resolve) => {
|
|
794
|
+
const timer = setTimeout(() => {
|
|
795
|
+
target.off("frame", onFrame);
|
|
796
|
+
resolve(false);
|
|
797
|
+
}, budgetMs);
|
|
798
|
+
const onFrame = () => {
|
|
799
|
+
clearTimeout(timer);
|
|
800
|
+
target.off("frame", onFrame);
|
|
801
|
+
resolve(true);
|
|
802
|
+
};
|
|
803
|
+
target.on("frame", onFrame);
|
|
804
|
+
target.invalidate();
|
|
805
|
+
});
|
|
806
|
+
const settleTarget = async () => {
|
|
807
|
+
const deadline = Date.now() + SETTLE_BUDGET_MS;
|
|
808
|
+
let last = "";
|
|
809
|
+
let stable = 0;
|
|
810
|
+
while (Date.now() < deadline) {
|
|
811
|
+
const v = target.getViewport();
|
|
812
|
+
const key = `${v.width}x${v.height}`;
|
|
813
|
+
stable = key === last ? stable + 1 : 0;
|
|
814
|
+
last = key;
|
|
815
|
+
if (stable >= SETTLE_STABLE_READS) break;
|
|
816
|
+
await new Promise((r) => setTimeout(r, SETTLE_POLL_MS));
|
|
817
|
+
}
|
|
818
|
+
if (stable < SETTLE_STABLE_READS) return false;
|
|
819
|
+
const painted = await nextFrame(Math.max(0, deadline - Date.now()));
|
|
820
|
+
await new Promise((r) => setTimeout(r, SETTLE_DRAW_MS));
|
|
821
|
+
return painted;
|
|
822
|
+
};
|
|
823
|
+
const roundRect = (r) => ({
|
|
824
|
+
x: Math.round(r.x),
|
|
825
|
+
y: Math.round(r.y),
|
|
826
|
+
width: Math.max(1, Math.round(r.width)),
|
|
827
|
+
height: Math.max(1, Math.round(r.height))
|
|
828
|
+
});
|
|
780
829
|
const appVersion = (() => {
|
|
781
830
|
try {
|
|
782
831
|
const pkg = JSON.parse(node_fs.readFileSync(node_path.join(__dirname, "..", "..", "package.json"), "utf8"));
|
|
@@ -845,20 +894,26 @@ function registerIpc(ctx) {
|
|
|
845
894
|
win.webContents.send(IPC.agentApply, patch);
|
|
846
895
|
},
|
|
847
896
|
captureVisible: async () => {
|
|
897
|
+
await settleTarget();
|
|
848
898
|
const image = await win.webContents.capturePage();
|
|
849
899
|
const size = image.getSize();
|
|
850
900
|
return { data: image.toPNG().toString("base64"), width: size.width, height: size.height };
|
|
851
901
|
},
|
|
852
902
|
captureTarget: async () => {
|
|
853
|
-
const
|
|
903
|
+
const settled = await settleTarget();
|
|
904
|
+
const bounds = canvasBounds ?? targetBounds;
|
|
854
905
|
const known = bounds !== null && bounds.width >= 1 && bounds.height >= 1;
|
|
855
|
-
const image = await win.webContents.capturePage(known ? bounds : void 0);
|
|
906
|
+
const image = await win.webContents.capturePage(known ? roundRect(bounds) : void 0);
|
|
856
907
|
const size = image.getSize();
|
|
908
|
+
const warnings = [];
|
|
909
|
+
if (!known) warnings.push("the renderer has not reported the pane bounds yet; captured the full window instead");
|
|
910
|
+
else if (canvasBounds === null) warnings.push("the renderer has not reported the render bounds yet; captured the whole pane instead");
|
|
911
|
+
if (!settled) warnings.push("the target was still resizing when the capture budget ran out; the PNG may show a transitional frame");
|
|
857
912
|
return {
|
|
858
913
|
data: image.toPNG().toString("base64"),
|
|
859
914
|
width: size.width,
|
|
860
915
|
height: size.height,
|
|
861
|
-
warnings
|
|
916
|
+
warnings
|
|
862
917
|
};
|
|
863
918
|
},
|
|
864
919
|
viewport: () => target.getViewport(),
|
package/out/mcp/server.js
CHANGED
|
@@ -128,8 +128,9 @@ const snapInputShape = {
|
|
|
128
128
|
capture: zod_1.z
|
|
129
129
|
.enum(['window', 'pane'])
|
|
130
130
|
.optional()
|
|
131
|
-
.describe("Live mode only: what the returned PNG shows — 'window' (default) is the whole app window, 'pane' is
|
|
132
|
-
'
|
|
131
|
+
.describe("Live mode only: what the returned PNG shows — 'window' (default) is the whole app window, 'pane' is the " +
|
|
132
|
+
'rendered screen cropped to itself, so a minified mobile preset is phone-shaped rather than a small phone ' +
|
|
133
|
+
'in a large rectangle. Ignored (with a note) when the render is headless.'),
|
|
133
134
|
};
|
|
134
135
|
const snapOutputShape = {
|
|
135
136
|
mode: zod_1.z
|
|
@@ -290,10 +291,11 @@ const driveInputShape = {
|
|
|
290
291
|
capture: zod_1.z
|
|
291
292
|
.enum(['window', 'pane'])
|
|
292
293
|
.optional()
|
|
293
|
-
.describe("Capture the app after the commands run: 'pane' crops to the
|
|
294
|
-
"
|
|
295
|
-
'
|
|
296
|
-
'
|
|
294
|
+
.describe("Capture the app after the commands run: 'pane' crops to the rendered screen itself (the render, not the " +
|
|
295
|
+
"empty pane around it — a minified mobile preset comes back phone-shaped), 'window' takes the whole app " +
|
|
296
|
+
'window. The capture waits for a preset resize to finish first, so the PNG matches the status beside it. ' +
|
|
297
|
+
'This is how you see a scrolled or panned state — unlike obsrv_snap, nothing is navigated, so the scroll ' +
|
|
298
|
+
'position survives. The PNG comes back inline when it is within the 1.5 MiB cap, and always as pngPath.'),
|
|
297
299
|
};
|
|
298
300
|
const driveOutputShape = {
|
|
299
301
|
version: zod_1.z.string().describe('The running app version.'),
|
|
@@ -14499,6 +14499,7 @@ function App() {
|
|
|
14499
14499
|
const dropToken = reactExports.useRef(0);
|
|
14500
14500
|
const targetPaneRef = reactExports.useRef(null);
|
|
14501
14501
|
const [targetBounds, setTargetBounds] = reactExports.useState(null);
|
|
14502
|
+
const [canvasBounds, setCanvasBounds] = reactExports.useState(null);
|
|
14502
14503
|
const toggle = (which) => () => setDrawer((d) => d === which ? "none" : which);
|
|
14503
14504
|
const setHost = useStore((s) => s.setHost);
|
|
14504
14505
|
const setSettings = useStore((s) => s.setSettings);
|
|
@@ -14547,17 +14548,34 @@ function App() {
|
|
|
14547
14548
|
const el = targetPaneRef.current;
|
|
14548
14549
|
if (!el) return;
|
|
14549
14550
|
const measure = () => {
|
|
14550
|
-
const
|
|
14551
|
-
setTargetBounds({ x:
|
|
14551
|
+
const pane = el.getBoundingClientRect();
|
|
14552
|
+
setTargetBounds({ x: pane.x, y: pane.y, width: pane.width, height: pane.height });
|
|
14553
|
+
const canvas2 = el.querySelector("canvas");
|
|
14554
|
+
if (!canvas2) {
|
|
14555
|
+
setCanvasBounds(null);
|
|
14556
|
+
return;
|
|
14557
|
+
}
|
|
14558
|
+
const c = canvas2.getBoundingClientRect();
|
|
14559
|
+
const x = Math.max(pane.x, c.x);
|
|
14560
|
+
const y = Math.max(pane.y, c.y);
|
|
14561
|
+
const right = Math.min(pane.x + pane.width, c.x + c.width);
|
|
14562
|
+
const bottom = Math.min(pane.y + pane.height, c.y + c.height);
|
|
14563
|
+
setCanvasBounds(right > x && bottom > y ? { x, y, width: right - x, height: bottom - y } : null);
|
|
14552
14564
|
};
|
|
14553
14565
|
const ro = new ResizeObserver(measure);
|
|
14554
14566
|
ro.observe(el);
|
|
14567
|
+
const canvas = el.querySelector("canvas");
|
|
14568
|
+
if (canvas) ro.observe(canvas);
|
|
14569
|
+
el.addEventListener("scroll", measure, { passive: true });
|
|
14555
14570
|
measure();
|
|
14556
|
-
return () =>
|
|
14571
|
+
return () => {
|
|
14572
|
+
ro.disconnect();
|
|
14573
|
+
el.removeEventListener("scroll", measure);
|
|
14574
|
+
};
|
|
14557
14575
|
}, []);
|
|
14558
14576
|
reactExports.useEffect(() => {
|
|
14559
|
-
window.obsrv.reportUiState({ presetId, profileId, viewMode, mode, targetBounds });
|
|
14560
|
-
}, [presetId, profileId, viewMode, mode, targetBounds]);
|
|
14577
|
+
window.obsrv.reportUiState({ presetId, profileId, viewMode, mode, targetBounds, canvasBounds });
|
|
14578
|
+
}, [presetId, profileId, viewMode, mode, targetBounds, canvasBounds]);
|
|
14561
14579
|
reactExports.useEffect(() => {
|
|
14562
14580
|
return window.obsrv.onAgentApply((patch) => {
|
|
14563
14581
|
const s = useStore.getState();
|
package/out/renderer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:" />
|
|
6
6
|
<title>Obsrv</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-Cgx8iixu.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-BeroS4wv.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -153,7 +153,14 @@ function parseUiState(raw) {
|
|
|
153
153
|
return null;
|
|
154
154
|
if (mode !== 'url' && mode !== 'image')
|
|
155
155
|
return null;
|
|
156
|
-
return {
|
|
156
|
+
return {
|
|
157
|
+
presetId,
|
|
158
|
+
profileId,
|
|
159
|
+
viewMode,
|
|
160
|
+
mode,
|
|
161
|
+
targetBounds: parseRect(raw.targetBounds),
|
|
162
|
+
canvasBounds: parseRect(raw.canvasBounds),
|
|
163
|
+
};
|
|
157
164
|
}
|
|
158
165
|
/**
|
|
159
166
|
* A scroll offset reported by the sync preload in a page webContents. Both
|