websocket cursor data broadcast

This commit is contained in:
2025-11-29 22:35:20 +08:00
parent 723c31afa3
commit 03934c4a03
18 changed files with 594 additions and 74 deletions

View File

@@ -1,19 +0,0 @@
import { Channel, invoke } from "@tauri-apps/api/core";
import { writable } from "svelte/store";
export type CursorPositions = {
raw: { x: number; y: number };
mapped: { x: number; y: number };
};
export let cursorPositionOnScreen = writable<CursorPositions>({
raw: { x: 0, y: 0 },
mapped: { x: 0, y: 0 },
});
export function initChannelCursorPosition() {
const channel = new Channel<CursorPositions>();
channel.onmessage = (pos) => {
cursorPositionOnScreen.set(pos);
};
invoke("channel_cursor_positions", { onEvent: channel });
}

61
src/events/cursor.ts Normal file
View File

@@ -0,0 +1,61 @@
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { writable } from "svelte/store";
export type CursorPositions = {
raw: { x: number; y: number };
mapped: { x: number; y: number };
};
export let cursorPositionOnScreen = writable<CursorPositions>({
raw: { x: 0, y: 0 },
mapped: { x: 0, y: 0 },
});
let unlisten: UnlistenFn | null = null;
let isListening = false;
/**
* Initialize cursor tracking for this window.
* Can be called from multiple windows - only the first call starts tracking on the Rust side,
* but all windows can independently listen to the broadcast events.
*/
export async function initCursorTracking() {
if (isListening) {
return;
}
try {
// Start tracking
await invoke("start_cursor_tracking");
// Listen to cursor position events (each window subscribes independently)
unlisten = await listen<CursorPositions>("cursor-position", (event) => {
cursorPositionOnScreen.set(event.payload);
});
isListening = true;
} catch (err) {
console.error("[Cursor] Failed to initialize cursor tracking:", err);
throw err;
}
}
/**
* Stop listening to cursor events in this window.
* Note: This doesn't stop the Rust-side tracking, just stops this window from receiving events.
*/
export function stopCursorTracking() {
if (unlisten) {
unlisten();
unlisten = null;
isListening = false;
}
}
// Handle HMR (Hot Module Replacement) cleanup
if (import.meta.hot) {
import.meta.hot.dispose(() => {
stopCursorTracking();
});
}

View File

@@ -1,5 +1,23 @@
<script>
import { browser } from '$app/environment';
import { onMount, onDestroy } from 'svelte';
import { initCursorTracking, stopCursorTracking } from '../events/cursor';
let { children } = $props();
if (browser) {
onMount(async () => {
try {
await initCursorTracking();
} catch (err) {
console.error("[Scene] Failed to initialize cursor tracking:", err);
}
});
onDestroy(() => {
stopCursorTracking();
});
}
</script>
<div class="size-full bg-transparent">

View File

@@ -4,6 +4,3 @@
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
export const ssr = false;
import "../app.css";
import { initChannelCursorPosition } from "../channels/cursor";
initChannelCursorPosition();

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import {
cursorPositionOnScreen,
} from "../../events/cursor";
</script>
<div class="p-4">
<div class="flex flex-col gap-4">
<div>
<p class="text-xl font-semibold">Preferences</p>
<p class="text-sm opacity-50">Settings and configuration</p>
</div>
<div class="card bg-base-200 p-4">
<h3 class="font-semibold mb-2">Cursor Position (Multi-Window Test)</h3>
<div class="flex flex-col gap-1">
<span class="font-mono text-sm">
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y})
</span>
<span class="font-mono text-sm">
Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen.mapped.y})
</span>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { cursorPositionOnScreen } from "../../channels/cursor";
import {
cursorPositionOnScreen,
} from "../../events/cursor";
</script>
<div class="w-svw h-svh p-4 relative">
@@ -9,13 +11,12 @@
<div class="flex flex-col text-center">
<p class="text-xl">Friendolls</p>
<p class="text-sm opacity-50">Scene Screen</p>
<div class="mt-4">
<span class="font-mono">
Cursor: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw
.y})
<div class="mt-4 flex flex-col gap-1">
<span class="font-mono text-sm">
Raw: ({$cursorPositionOnScreen.raw.x}, {$cursorPositionOnScreen.raw.y})
</span>
<span class="font-mono">
Cursor: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen
<span class="font-mono text-sm">
Mapped: ({$cursorPositionOnScreen.mapped.x}, {$cursorPositionOnScreen
.mapped.y})
</span>
</div>