From ee6f1d7ef470898d185476a57d417f7432988504 Mon Sep 17 00:00:00 2001 From: tutman96 <11356668+tutman96@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:34:30 +0000 Subject: [PATCH] Fix fullscreen video relative mouse movements --- ui/package.json | 1 + ui/src/components/ActionBar.tsx | 3 +++ ui/src/components/WebRTCVideo.tsx | 41 +++++++++++++++++++++++++------ ui/vite.config.ts | 24 +++++++++++++++--- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/ui/package.json b/ui/package.json index 592a300..9a7fae5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,6 +10,7 @@ "dev": "vite dev --mode=development", "build": "npm run build:prod", "build:device": "tsc && vite build --mode=device --emptyOutDir", + "dev:device": "vite dev --mode=device", "build:prod": "tsc && vite build --mode=production", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index cd5432c..13ab896 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -4,6 +4,7 @@ import { useMountMediaStore, useUiStore, useSettingsStore, + useVideoStore, } from "@/hooks/stores"; import { MdOutlineContentPasteGo } from "react-icons/md"; import Container from "@components/Container"; @@ -33,6 +34,7 @@ export default function Actionbar({ state => state.remoteVirtualMediaState, ); const developerMode = useSettingsStore(state => state.developerMode); + const hdmiState = useVideoStore(state => state.hdmiState); // This is the only way to get a reliable state change for the popover // at time of writing this there is no mount, or unmount event for the popover @@ -247,6 +249,7 @@ export default function Actionbar({ size="XS" theme="light" text="Fullscreen" + disabled={hdmiState !== 'ready'} LeadingIcon={LuMaximize} onClick={() => requestFullscreen()} /> diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index f5f083b..85bbcac 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -30,6 +30,8 @@ export default function WebRTCVideo() { const { setClientSize: setVideoClientSize, setSize: setVideoSize, + width: videoWidth, + height: videoHeight, clientWidth: videoClientWidth, clientHeight: videoClientHeight, } = useVideoStore(); @@ -102,20 +104,43 @@ export default function WebRTCVideo() { const mouseMoveHandler = useCallback( (e: MouseEvent) => { if (!videoClientWidth || !videoClientHeight) return; - const { buttons } = e; + // Get the aspect ratios of the video element and the video stream + const videoElementAspectRatio = videoClientWidth / videoClientHeight; + const videoStreamAspectRatio = videoWidth / videoHeight; - // Clamp mouse position within the video boundaries - const currMouseX = Math.min(Math.max(1, e.offsetX), videoClientWidth); - const currMouseY = Math.min(Math.max(1, e.offsetY), videoClientHeight); + // Calculate the effective video display area + let effectiveWidth = videoClientWidth; + let effectiveHeight = videoClientHeight; + let offsetX = 0; + let offsetY = 0; - // Normalize mouse position to 0-32767 range (HID absolute coordinate system) - const x = Math.round((currMouseX / videoClientWidth) * 32767); - const y = Math.round((currMouseY / videoClientHeight) * 32767); + if (videoElementAspectRatio > videoStreamAspectRatio) { + // Pillarboxing: black bars on the left and right + effectiveWidth = videoClientHeight * videoStreamAspectRatio; + offsetX = (videoClientWidth - effectiveWidth) / 2; + } else if (videoElementAspectRatio < videoStreamAspectRatio) { + // Letterboxing: black bars on the top and bottom + effectiveHeight = videoClientWidth / videoStreamAspectRatio; + offsetY = (videoClientHeight - effectiveHeight) / 2; + } + + // Clamp mouse position within the effective video boundaries + const clampedX = Math.min(Math.max(offsetX, e.offsetX), offsetX + effectiveWidth); + const clampedY = Math.min(Math.max(offsetY, e.offsetY), offsetY + effectiveHeight); + + // Map clamped mouse position to the video stream's coordinate system + const relativeX = (clampedX - offsetX) / effectiveWidth; + const relativeY = (clampedY - offsetY) / effectiveHeight; + + // Convert to HID absolute coordinate system (0-32767 range) + const x = Math.round(relativeX * 32767); + const y = Math.round(relativeY * 32767); // Send mouse movement + const { buttons } = e; sendMouseMovement(x, y, buttons); }, - [sendMouseMovement, videoClientHeight, videoClientWidth], + [sendMouseMovement, videoClientHeight, videoClientWidth, videoWidth, videoHeight], ); const mouseWheelHandler = useCallback( diff --git a/ui/vite.config.ts b/ui/vite.config.ts index e9c7fe5..b6d26f6 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -2,13 +2,31 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react-swc"; import tsconfigPaths from "vite-tsconfig-paths"; -export default defineConfig(({ mode }) => { +declare const process: { + env: { + JETKVM_PROXY_URL: string; + }; +}; + +export default defineConfig(({ mode, command }) => { const isCloud = mode === "production"; const onDevice = mode === "device"; + const { JETKVM_PROXY_URL } = process.env; + return { plugins: [tsconfigPaths(), react()], build: { outDir: isCloud ? "dist" : "../static" }, - server: { host: "0.0.0.0" }, - base: onDevice ? "/static" : "/", + server: { + host: "0.0.0.0", + proxy: JETKVM_PROXY_URL ? { + '/me': JETKVM_PROXY_URL, + '/device': JETKVM_PROXY_URL, + '/webrtc': JETKVM_PROXY_URL, + '/auth': JETKVM_PROXY_URL, + '/storage': JETKVM_PROXY_URL, + '/cloud': JETKVM_PROXY_URL, + } : undefined + }, + base: onDevice && command === 'build' ? "/static" : "/", }; });