148 lines
3.4 KiB
TypeScript
148 lines
3.4 KiB
TypeScript
import { invoke } from '@tauri-apps/api';
|
|
import { convertFileSrc } from '@tauri-apps/api/tauri';
|
|
import {
|
|
Component,
|
|
createEffect,
|
|
createSignal,
|
|
JSX,
|
|
onCleanup,
|
|
onMount,
|
|
splitProps,
|
|
} from 'solid-js';
|
|
|
|
type ScreenViewProps = {
|
|
displayId: number;
|
|
} & JSX.HTMLAttributes<HTMLDivElement>;
|
|
|
|
async function subscribeScreenshotUpdate(displayId: number) {
|
|
await invoke('subscribe_encoded_screenshot_updated', {
|
|
displayId,
|
|
});
|
|
}
|
|
|
|
export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|
const [localProps, rootProps] = splitProps(props, ['displayId']);
|
|
let canvas: HTMLCanvasElement;
|
|
let root: HTMLDivElement;
|
|
const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
|
|
const [drawInfo, setDrawInfo] = createSignal({
|
|
drawX: 0,
|
|
drawY: 0,
|
|
drawWidth: 0,
|
|
drawHeight: 0,
|
|
});
|
|
const [imageData, setImageData] = createSignal<{
|
|
buffer: Uint8ClampedArray;
|
|
width: number;
|
|
height: number;
|
|
} | null>(null);
|
|
|
|
const resetSize = () => {
|
|
const aspectRatio = canvas.width / canvas.height;
|
|
|
|
const drawWidth = Math.round(
|
|
Math.min(root.clientWidth, root.clientHeight * aspectRatio),
|
|
);
|
|
const drawHeight = Math.round(
|
|
Math.min(root.clientHeight, root.clientWidth / aspectRatio),
|
|
);
|
|
|
|
const drawX = Math.round((root.clientWidth - drawWidth) / 2);
|
|
const drawY = Math.round((root.clientHeight - drawHeight) / 2);
|
|
|
|
setDrawInfo({
|
|
drawX,
|
|
drawY,
|
|
drawWidth,
|
|
drawHeight,
|
|
});
|
|
|
|
canvas.width = root.clientWidth;
|
|
canvas.height = root.clientHeight;
|
|
|
|
draw(true);
|
|
};
|
|
|
|
const draw = (cached: boolean = false) => {
|
|
const { drawX, drawY } = drawInfo();
|
|
|
|
let _ctx = ctx();
|
|
let raw = imageData();
|
|
if (_ctx && raw) {
|
|
_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
if (cached) {
|
|
for (let i = 3; i < raw.buffer.length; i += 8) {
|
|
raw.buffer[i] = Math.floor(raw.buffer[i] * 0.7);
|
|
}
|
|
}
|
|
const img = new ImageData(raw.buffer, raw.width, raw.height);
|
|
_ctx.putImageData(img, drawX, drawY);
|
|
}
|
|
};
|
|
|
|
// get screenshot
|
|
createEffect(() => {
|
|
let stopped = false;
|
|
const frame = async () => {
|
|
const { drawWidth, drawHeight } = drawInfo();
|
|
const url = convertFileSrc(
|
|
`displays/${localProps.displayId}?width=${drawWidth}&height=${drawHeight}`,
|
|
'ambient-light',
|
|
);
|
|
await fetch(url, {
|
|
mode: 'cors',
|
|
})
|
|
.then((res) => res.body?.getReader().read())
|
|
.then((buffer) => {
|
|
if (buffer?.value) {
|
|
setImageData({
|
|
buffer: new Uint8ClampedArray(buffer?.value),
|
|
width: drawWidth,
|
|
height: drawHeight,
|
|
});
|
|
} else {
|
|
setImageData(null);
|
|
}
|
|
draw();
|
|
});
|
|
};
|
|
|
|
(async () => {
|
|
while (!stopped) {
|
|
await frame();
|
|
}
|
|
})();
|
|
|
|
onCleanup(() => {
|
|
stopped = true;
|
|
});
|
|
});
|
|
|
|
// resize
|
|
createEffect(() => {
|
|
let resizeObserver: ResizeObserver;
|
|
|
|
onMount(() => {
|
|
setCtx(canvas.getContext('2d'));
|
|
new ResizeObserver(() => {
|
|
resetSize();
|
|
}).observe(root);
|
|
});
|
|
|
|
onCleanup(() => {
|
|
resizeObserver?.unobserve(root);
|
|
});
|
|
});
|
|
|
|
return (
|
|
<div
|
|
ref={root!}
|
|
{...rootProps}
|
|
class={'overflow-hidden h-full w-full ' + rootProps.class}
|
|
>
|
|
<canvas ref={canvas!} />
|
|
{rootProps.children}
|
|
</div>
|
|
);
|
|
};
|