+
+
@@ -168,13 +298,13 @@ const openMobilePaste = () => openFromOverflow(() => {
-
-
+
+
@@ -189,13 +319,13 @@ const openMobilePaste = () => openFromOverflow(() => {
-
-
+
+
@@ -205,15 +335,32 @@ const openMobilePaste = () => openFromOverflow(() => {
-
+
-
-
+
+
+
+
+
+
+
+
+ {{ t('actionbar.statsTip') }}
+
+
+
+
+
+
+
@@ -233,14 +380,14 @@ const openMobilePaste = () => openFromOverflow(() => {
-
-
+
+
@@ -250,26 +397,9 @@ const openMobilePaste = () => openFromOverflow(() => {
-
-
-
-
-
-
-
-
- {{ t('actionbar.statsTip') }}
-
-
-
-
+
-
-
-
+
@@ -309,43 +439,43 @@ const openMobilePaste = () => openFromOverflow(() => {
-
-
+
+
-
-
- msdDialogOpen = true)">
+
+ msdDialogOpen = true)">
{{ t('actionbar.virtualMedia') }}
-
-
+
+
{{ t('actionbar.power') }}
-
-
+
+
{{ t('actionbar.paste') }}
-
+
-
- emit('toggleStats'))">
+
+ emit('toggleStats'))">
{{ t('actionbar.stats') }}
-
+
emit('openTerminal'))"
>
@@ -353,8 +483,8 @@ const openMobilePaste = () => openFromOverflow(() => {
{{ t('extensions.ttyd.title') }}
-
- router.push('/settings'))">
+
+ router.push('/settings'))">
{{ t('actionbar.settings') }}
@@ -403,4 +533,36 @@ const openMobilePaste = () => openFromOverflow(() => {
+
+
+
+
+
+
+
{{ t('actionbar.virtualMedia') }}
+
+
+
{{ t('actionbar.power') }}
+
+
+
{{ t('actionbar.paste') }}
+
+
+
{{ t('actionbar.stats') }}
+
+
+
{{ t('actionbar.extension') }}
+
+
+
{{ t('actionbar.settings') }}
+
+
+
+
+
+
+
+
+
diff --git a/web/src/views/ConsoleView.vue b/web/src/views/ConsoleView.vue
index c4b1c718..1ddb2f0a 100644
--- a/web/src/views/ConsoleView.vue
+++ b/web/src/views/ConsoleView.vue
@@ -137,6 +137,9 @@ const mousePosition = ref({ x: 0, y: 0 })
const lastMousePosition = ref({ x: 0, y: 0 }) // Track last position for relative mode
const isPointerLocked = ref(false) // Track pointer lock state
+/** Local overlay crosshair position (px, relative to video container); HID uses mousePosition separately */
+const localCrosshairPos = ref<{ x: number; y: number } | null>(null)
+
// Mouse move throttling (60 Hz = ~16.67ms interval)
const DEFAULT_MOUSE_MOVE_SEND_INTERVAL_MS = 16
let mouseMoveSendIntervalMs = DEFAULT_MOUSE_MOVE_SEND_INTERVAL_MS
@@ -1982,7 +1985,43 @@ function getAbsoluteMousePosition(e: MouseEvent) {
}
}
+function updateLocalCrosshairFromEvent(e: MouseEvent) {
+ if (!cursorVisible.value) {
+ localCrosshairPos.value = null
+ return
+ }
+ const container = videoContainerRef.value
+ if (!container) return
+
+ const rect = container.getBoundingClientRect()
+ if (rect.width <= 0 || rect.height <= 0) return
+
+ if (mouseMode.value === 'relative' && isPointerLocked.value) {
+ const cur = localCrosshairPos.value
+ const nx = cur ? cur.x + e.movementX : rect.width / 2
+ const ny = cur ? cur.y + e.movementY : rect.height / 2
+ localCrosshairPos.value = {
+ x: Math.max(0, Math.min(rect.width, nx)),
+ y: Math.max(0, Math.min(rect.height, ny)),
+ }
+ return
+ }
+
+ localCrosshairPos.value = {
+ x: Math.max(0, Math.min(rect.width, e.clientX - rect.left)),
+ y: Math.max(0, Math.min(rect.height, e.clientY - rect.top)),
+ }
+}
+
+function handleMouseLeaveVideo() {
+ if (!isPointerLocked.value) {
+ localCrosshairPos.value = null
+ }
+}
+
function handleMouseMove(e: MouseEvent) {
+ updateLocalCrosshairFromEvent(e)
+
const videoElement = getActiveVideoElement()
if (!videoElement) return
@@ -2192,6 +2231,12 @@ function handlePointerLockChange() {
if (isPointerLocked.value) {
// Reset mouse position display when locked
mousePosition.value = { x: 0, y: 0 }
+ if (cursorVisible.value && container) {
+ const r = container.getBoundingClientRect()
+ if (r.width > 0 && r.height > 0) {
+ localCrosshairPos.value = { x: r.width / 2, y: r.height / 2 }
+ }
+ }
toast.info(t('console.pointerLocked'), {
description: t('console.pointerLockedDesc'),
duration: 3000,
@@ -2222,6 +2267,9 @@ function handleBlur() {
function handleCursorVisibilityChange(e: Event) {
const customEvent = e as CustomEvent<{ visible: boolean }>
cursorVisible.value = customEvent.detail.visible
+ if (!cursorVisible.value) {
+ localCrosshairPos.value = null
+ }
}
function clampMouseMoveSendIntervalMs(ms: number): number {
@@ -2654,10 +2702,10 @@ onUnmounted(() => {
}"
:class="{
'opacity-60': videoLoading || videoError,
- 'cursor-crosshair': cursorVisible,
- 'cursor-none': !cursorVisible
+ 'cursor-none': true,
}"
tabindex="0"
+ @mouseleave="handleMouseLeaveVideo"
@mousemove="handleMouseMove"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@@ -2693,6 +2741,59 @@ onUnmounted(() => {
alt=""
/>
+
+
+
+
+