mirror of https://github.com/jetkvm/kvm.git
Fix fullscreen video relative mouse movements
This commit is contained in:
parent
8ffe66a1bc
commit
ee6f1d7ef4
|
@ -10,6 +10,7 @@
|
||||||
"dev": "vite dev --mode=development",
|
"dev": "vite dev --mode=development",
|
||||||
"build": "npm run build:prod",
|
"build": "npm run build:prod",
|
||||||
"build:device": "tsc && vite build --mode=device --emptyOutDir",
|
"build:device": "tsc && vite build --mode=device --emptyOutDir",
|
||||||
|
"dev:device": "vite dev --mode=device",
|
||||||
"build:prod": "tsc && vite build --mode=production",
|
"build:prod": "tsc && vite build --mode=production",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
useMountMediaStore,
|
useMountMediaStore,
|
||||||
useUiStore,
|
useUiStore,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
|
useVideoStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { MdOutlineContentPasteGo } from "react-icons/md";
|
import { MdOutlineContentPasteGo } from "react-icons/md";
|
||||||
import Container from "@components/Container";
|
import Container from "@components/Container";
|
||||||
|
@ -33,6 +34,7 @@ export default function Actionbar({
|
||||||
state => state.remoteVirtualMediaState,
|
state => state.remoteVirtualMediaState,
|
||||||
);
|
);
|
||||||
const developerMode = useSettingsStore(state => state.developerMode);
|
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
|
// 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
|
// 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"
|
size="XS"
|
||||||
theme="light"
|
theme="light"
|
||||||
text="Fullscreen"
|
text="Fullscreen"
|
||||||
|
disabled={hdmiState !== 'ready'}
|
||||||
LeadingIcon={LuMaximize}
|
LeadingIcon={LuMaximize}
|
||||||
onClick={() => requestFullscreen()}
|
onClick={() => requestFullscreen()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -30,6 +30,8 @@ export default function WebRTCVideo() {
|
||||||
const {
|
const {
|
||||||
setClientSize: setVideoClientSize,
|
setClientSize: setVideoClientSize,
|
||||||
setSize: setVideoSize,
|
setSize: setVideoSize,
|
||||||
|
width: videoWidth,
|
||||||
|
height: videoHeight,
|
||||||
clientWidth: videoClientWidth,
|
clientWidth: videoClientWidth,
|
||||||
clientHeight: videoClientHeight,
|
clientHeight: videoClientHeight,
|
||||||
} = useVideoStore();
|
} = useVideoStore();
|
||||||
|
@ -102,20 +104,43 @@ export default function WebRTCVideo() {
|
||||||
const mouseMoveHandler = useCallback(
|
const mouseMoveHandler = useCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
if (!videoClientWidth || !videoClientHeight) return;
|
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
|
// Calculate the effective video display area
|
||||||
const currMouseX = Math.min(Math.max(1, e.offsetX), videoClientWidth);
|
let effectiveWidth = videoClientWidth;
|
||||||
const currMouseY = Math.min(Math.max(1, e.offsetY), videoClientHeight);
|
let effectiveHeight = videoClientHeight;
|
||||||
|
let offsetX = 0;
|
||||||
|
let offsetY = 0;
|
||||||
|
|
||||||
// Normalize mouse position to 0-32767 range (HID absolute coordinate system)
|
if (videoElementAspectRatio > videoStreamAspectRatio) {
|
||||||
const x = Math.round((currMouseX / videoClientWidth) * 32767);
|
// Pillarboxing: black bars on the left and right
|
||||||
const y = Math.round((currMouseY / videoClientHeight) * 32767);
|
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
|
// Send mouse movement
|
||||||
|
const { buttons } = e;
|
||||||
sendMouseMovement(x, y, buttons);
|
sendMouseMovement(x, y, buttons);
|
||||||
},
|
},
|
||||||
[sendMouseMovement, videoClientHeight, videoClientWidth],
|
[sendMouseMovement, videoClientHeight, videoClientWidth, videoWidth, videoHeight],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mouseWheelHandler = useCallback(
|
const mouseWheelHandler = useCallback(
|
||||||
|
|
|
@ -2,13 +2,31 @@ import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
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 isCloud = mode === "production";
|
||||||
const onDevice = mode === "device";
|
const onDevice = mode === "device";
|
||||||
|
const { JETKVM_PROXY_URL } = process.env;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [tsconfigPaths(), react()],
|
plugins: [tsconfigPaths(), react()],
|
||||||
build: { outDir: isCloud ? "dist" : "../static" },
|
build: { outDir: isCloud ? "dist" : "../static" },
|
||||||
server: { host: "0.0.0.0" },
|
server: {
|
||||||
base: onDevice ? "/static" : "/",
|
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" : "/",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue