diff --git a/Cargo.lock b/Cargo.lock index 0f527d0ac..df9c2a40a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,6 +101,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-global-shortcut", "tauri-plugin-log", "tauri-plugin-mcp", "tauri-plugin-os", @@ -1151,13 +1152,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "gethostname" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "rustix", + "rustix 1.0.7", "windows-targets 0.52.6", ] @@ -1286,6 +1297,24 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "global-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.1", + "objc2-app-kit", + "once_cell", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + [[package]] name = "gobject-sys" version = "0.18.0" @@ -1863,6 +1892,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -2976,6 +3011,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.7" @@ -2985,7 +3033,7 @@ dependencies = [ "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -3677,6 +3725,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31919f3c07bcb585afef217c0c33cde80da9ebccf5b8e2c90e0e0a535b14ab47" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + [[package]] name = "tauri-plugin-log" version = "2.4.0" @@ -3719,7 +3782,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f19432397850c2ddd42aa58078630c15287bbce3866eb1d90e7dbee680637" dependencies = [ - "gethostname", + "gethostname 1.0.2", "log", "os_info", "serde", @@ -4729,6 +4792,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4769,6 +4847,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4781,6 +4865,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4793,6 +4883,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4811,6 +4907,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4823,6 +4925,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4835,6 +4943,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4847,6 +4961,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4970,6 +5090,29 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname 0.4.3", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yoke" version = "0.8.0" diff --git a/README.md b/README.md index 6c6a14b64..3edb8f97d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Have you dreamed about having a cyber living being (cyber waifu / husbando, digi With the power of modern large langauge models like [ChatGPT](https://chatgpt.com), and famous [Claude](https://claude.ai), asking a virtual being to have role playing and chat with us is already easy enough for everyone. Platforms like [Character.ai (a.k.a. c.ai)](https://character.ai) and [JanitorAI](https://janitorai.com/), and local playgrounds like [SillyTavern](https://github.com/SillyTavern/SillyTavern) is already a well-enough solution for chat based, or visuali adventure game like experience. -But, what about the abilities to play games? And see what you are coding at? Chatting while playing games, watching videos, and capable of doing many other things Perhaps you know [Neuro-sama](https://www.youtube.com/@Neurosama) already, she is currenly the best companion capable of playing games, chatting, and interacting with you and the participants (in VTuber community), some call this kind of being, "digital human" too. **Sadly, it's not open sourced, you cannot interact with her after she went offline from live stream**. +But, what about the abilities to play games? And see what you are coding at? Chatting while playing games, watching videos, and capable of doing many other things Perhaps you know [Neuro-sama](https://www.youtube.com/@Neurosama) already, she is currenly the best companion capable of playing games, chatting, and interacting with you and the participants (in VTuber community), some call this kind of being, "digital human" too. **Sadly, it's not open sourced, you cannot interact with her after she went offline from live stream**. Therefore, this project, AIRI, offers another possibilty here: **let you own yours, easily, anywhere, anytime**. diff --git a/apps/stage-tamagotchi/locales/en.yml b/apps/stage-tamagotchi/locales/en.yml index e30627daf..6dc5bfca5 100644 --- a/apps/stage-tamagotchi/locales/en.yml +++ b/apps/stage-tamagotchi/locales/en.yml @@ -354,7 +354,16 @@ settings: description: Configure the environment where the character lives title: Scene themes: + color-scheme: + description: Change the color scheme of the stage. + title: Color Scheme description: Customize your stage! + developer: + description: Some developer options. + title: Developers + general: + description: Dark theme, languages, etc. + title: General sections: section: custom-color: @@ -431,7 +440,19 @@ settings: porcelain and paintings title: Chinese Traditional Colors title: Color Scheme Presets + window-shortcuts: + title: Keyboard Shortcuts title: Appearance + window-shortcuts: + description: Modify the window shortcuts. + title: Window Shortcuts + toggle-move: + label: Toggle Move + toggle-resize: + label: Toggle Resize + toggle-ignore-mouse-event: + label: Toggle Ignore Mouse Event + press-keys: Press Keys... sections: section: general: diff --git a/apps/stage-tamagotchi/package.json b/apps/stage-tamagotchi/package.json index 53a79197e..bf2ab9622 100644 --- a/apps/stage-tamagotchi/package.json +++ b/apps/stage-tamagotchi/package.json @@ -27,6 +27,7 @@ "@proj-airi/ui": "workspace:^", "@ricky0123/vad-web": "^0.0.24", "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-global-shortcut": "^2.2.1", "@tauri-apps/plugin-os": ">=2.2.1", "@tresjs/cientos": "^4.3.1", "@tresjs/core": "^4.3.5", diff --git a/apps/stage-tamagotchi/src-tauri/Cargo.toml b/apps/stage-tamagotchi/src-tauri/Cargo.toml index f45e5b4ce..c9a0ec85d 100644 --- a/apps/stage-tamagotchi/src-tauri/Cargo.toml +++ b/apps/stage-tamagotchi/src-tauri/Cargo.toml @@ -50,3 +50,6 @@ objc2-foundation = "0.3.1" [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.61.1", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] } + +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +tauri-plugin-global-shortcut = "2.0.0" diff --git a/apps/stage-tamagotchi/src-tauri/capabilities/default.json b/apps/stage-tamagotchi/src-tauri/capabilities/default.json index 8f98b25f1..60ff6fa7c 100644 --- a/apps/stage-tamagotchi/src-tauri/capabilities/default.json +++ b/apps/stage-tamagotchi/src-tauri/capabilities/default.json @@ -10,6 +10,12 @@ "os:default", "core:window:default", "core:window:allow-start-dragging", - "mcp:default" + "core:window:allow-set-focus", + "core:window:allow-set-minimizable", + "mcp:default", + "global-shortcut:allow-is-registered", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister", + "global-shortcut:allow-unregister-all" ] } diff --git a/apps/stage-tamagotchi/src-tauri/src/app_click_through/native_macos.rs b/apps/stage-tamagotchi/src-tauri/src/app_click_through/native_macos.rs index 8299311de..4f42ca82b 100644 --- a/apps/stage-tamagotchi/src-tauri/src/app_click_through/native_macos.rs +++ b/apps/stage-tamagotchi/src-tauri/src/app_click_through/native_macos.rs @@ -56,17 +56,3 @@ pub async fn is_cursor_in_window(window: &tauri::Window) -> bool { } false } - -/// Check if modifier key is pressed (Option/Alt or Control) -#[cfg(target_os = "macos")] -pub fn is_modifier_pressed() -> bool { - unsafe { - let event_class: &'static objc2::runtime::AnyClass = class!(NSEvent); - let flags: u64 = msg_send![event_class, modifierFlags]; - - // Check for Alt/Option (1 << 19) or Control (1 << 18) - // let ctrl_pressed = (flags & (1 << 18)) != 0; - // alt_pressed || ctrl_pressed - (flags & (1 << 19)) != 0 - } -} diff --git a/apps/stage-tamagotchi/src-tauri/src/lib.rs b/apps/stage-tamagotchi/src-tauri/src/lib.rs index 0ef0adee9..1ba8d3436 100644 --- a/apps/stage-tamagotchi/src-tauri/src/lib.rs +++ b/apps/stage-tamagotchi/src-tauri/src/lib.rs @@ -17,9 +17,9 @@ mod app_windows; mod commands; #[cfg(target_os = "macos")] -use app_click_through::native_macos::{is_cursor_in_window, is_modifier_pressed}; +use app_click_through::native_macos::is_cursor_in_window; #[cfg(target_os = "windows")] -use app_click_through::native_windows::{is_cursor_in_window, is_modifier_pressed}; +use app_click_through::native_windows::is_cursor_in_window; use app_click_through::state::{set_click_through_enabled, set_cursor_inside, WindowClickThroughState}; #[tauri::command] @@ -55,23 +55,21 @@ async fn start_monitor_for_clicking_through(window: tauri::Window) -> Result<(), #[cfg(target_os = "macos")] { let cursor_inside = is_cursor_in_window(&window).await; - let modifier_pressed = is_modifier_pressed(); // Only allow disabling click-through when: // 1. Cursor is OUTSIDE the window AND // 2. Modifier key is pressed - let _ = set_cursor_inside(&window, cursor_inside && !modifier_pressed); + let _ = set_cursor_inside(&window, cursor_inside); } #[cfg(target_os = "windows")] { let cursor_inside = is_cursor_in_window(&window).await; - let modifier_pressed = is_modifier_pressed(); // Only allow disabling click-through when: // 1. Cursor is OUTSIDE the window AND // 2. Modifier key is pressed - let _ = set_cursor_inside(&window, cursor_inside && !modifier_pressed); + let _ = set_cursor_inside(&window, cursor_inside); } } }); @@ -113,6 +111,7 @@ pub fn run() { .plugin(prevent_default_plugin) .plugin(tauri_plugin_mcp::Builder.build()) .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_global_shortcut::Builder::new().build()) .manage(WindowClickThroughState::default()) .setup(|app| { let mut builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()); @@ -140,7 +139,8 @@ pub fn run() { let settings_item = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?; let hide_item = MenuItem::with_id(app, "hide", "Hide", true, None::<&str>)?; let show_item = MenuItem::with_id(app, "show", "Show", true, None::<&str>)?; - let menu = Menu::with_items(app, &[&settings_item, &hide_item, &show_item, &quit_item])?; + let show_devtools_item = MenuItem::with_id(app, "show-devtools", "Show Devtools", true, None::<&str>)?; + let menu = Menu::with_items(app, &[&settings_item, &hide_item, &show_item, &show_devtools_item, &quit_item])?; let _ = TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) // TODO: use custom icon @@ -173,6 +173,12 @@ pub fn run() { let _ = window.show(); } } + "show-devtools" => { + let window = app.get_webview_window("main"); + if let Some(window) = window { + window.open_devtools(); + } + } _ => {} }) .show_menu_on_left_click(true) diff --git a/apps/stage-tamagotchi/src/composables/window-shortcuts.ts b/apps/stage-tamagotchi/src/composables/window-shortcuts.ts index d1a6e7e16..8e063a71a 100644 --- a/apps/stage-tamagotchi/src/composables/window-shortcuts.ts +++ b/apps/stage-tamagotchi/src/composables/window-shortcuts.ts @@ -1,56 +1,88 @@ -import type { EffectScope } from 'vue' +import type { ShortcutEvent } from '@tauri-apps/plugin-global-shortcut' -import { useMagicKeys, whenever } from '@vueuse/core' +import { register, unregisterAll } from '@tauri-apps/plugin-global-shortcut' import { storeToRefs } from 'pinia' -import { computed, effectScope, watch } from 'vue' +import { computed, watch } from 'vue' import { useShortcutsStore } from '../stores/shortcuts' import { useWindowControlStore } from '../stores/window-controls' import { WindowControlMode } from '../types/window-controls' +import { startClickThrough, stopClickThrough } from '../utils/windows' export function useWindowShortcuts() { const windowStore = useWindowControlStore() - const magicKeys = useMagicKeys() const { shortcuts } = storeToRefs(useShortcutsStore()) const handlers = computed(() => [ { - handle: () => { + handle: async (event: ShortcutEvent) => { + if (event.state !== 'Pressed') { + return + } + windowStore.setMode(WindowControlMode.MOVE) windowStore.toggleControl() }, shortcut: shortcuts.value.find(shortcut => shortcut.type === 'move')?.shortcut, }, { - handle: () => { + handle: async (event: ShortcutEvent) => { + if (event.state !== 'Pressed') { + return + } + windowStore.setMode(WindowControlMode.RESIZE) windowStore.toggleControl() }, shortcut: shortcuts.value.find(shortcut => shortcut.type === 'resize')?.shortcut, }, { - handle: () => { + handle: async (event: ShortcutEvent) => { + if (event.state !== 'Pressed') { + return + } + windowStore.setMode(WindowControlMode.DEBUG) windowStore.toggleControl() }, shortcut: shortcuts.value.find(shortcut => shortcut.type === 'debug')?.shortcut, }, - ]) - - let currentScope: EffectScope | null = null - watch(handlers, () => { - if (currentScope) { - currentScope.stop() - } - - currentScope = effectScope() - currentScope.run(() => { - handlers.value.forEach((handler) => { - if (!handler.shortcut) { + { + handle: async (event: ShortcutEvent) => { + if (event.state === 'Pressed') { + stopClickThrough() return } - whenever(magicKeys[handler.shortcut], handler.handle) + + startClickThrough() + }, + shortcut: shortcuts.value.find(shortcut => shortcut.type === 'ignore-mouse-event')?.shortcut, + }, + ]) + + watch(handlers, async () => { + await unregisterAll() + + for (const handler of handlers.value) { + if (!handler.shortcut) { + return + } + + await register(handler.shortcut + .replaceAll('Meta', 'CommandOrControl') + .replaceAll('meta', 'CommandOrControl') + .replaceAll('Ctrl', 'CommandOrControl') + .replaceAll('ctrl', 'CommandOrControl') + .replaceAll('Option', 'OptionOrAlt') + .replaceAll('option', 'OptionOrAlt'), (event: ShortcutEvent) => { + if (event.state !== 'Pressed') { + return + } + + handler.handle(event).catch((error) => { + console.error('Error handling shortcut', error) + }) }) - }) + } }, { immediate: true }) } diff --git a/apps/stage-tamagotchi/src/layouts/settings.vue b/apps/stage-tamagotchi/src/layouts/settings.vue index 45837dbbe..45d85f271 100644 --- a/apps/stage-tamagotchi/src/layouts/settings.vue +++ b/apps/stage-tamagotchi/src/layouts/settings.vue @@ -23,6 +23,22 @@ const routeHeaderMetadataMap = computed(() => { subtitle: t('settings.title'), title: t('settings.pages.themes.title'), }, + '/settings/appearance/general': { + subtitle: t('settings.title'), + title: t('settings.pages.themes.general.title'), + }, + '/settings/appearance/color-scheme': { + subtitle: t('settings.title'), + title: t('settings.pages.themes.color-scheme.title'), + }, + '/settings/appearance/window-shortcuts': { + subtitle: t('settings.title'), + title: t('settings.pages.themes.window-shortcuts.title'), + }, + '/settings/appearance/developer': { + subtitle: t('settings.title'), + title: t('settings.pages.themes.developer.title'), + }, '/settings/memory': { subtitle: t('settings.title'), title: t('settings.pages.memory.title'), diff --git a/apps/stage-tamagotchi/src/pages/index.vue b/apps/stage-tamagotchi/src/pages/index.vue index 2bbb830e8..8ab964804 100644 --- a/apps/stage-tamagotchi/src/pages/index.vue +++ b/apps/stage-tamagotchi/src/pages/index.vue @@ -10,11 +10,12 @@ import { computed, onMounted, onUnmounted, ref } from 'vue' import { useWindowShortcuts } from '../composables/window-shortcuts' import { useWindowControlStore } from '../stores/window-controls' import { WindowControlMode } from '../types/window-controls' +import { startClickThrough, stopClickThrough } from '../utils/windows' const windowStore = useWindowControlStore() useWindowShortcuts() +const isCursorInside = ref(false) -const viewRef = ref() const mcpStore = useMcpStore() const { connected, serverCmd, serverArgs } = storeToRefs(mcpStore) @@ -33,11 +34,11 @@ const modeIndicatorClass = computed(() => { onMounted(async () => { await invoke('start_monitor_for_clicking_through') - await invoke('start_click_through') + await startClickThrough() }) onUnmounted(async () => { - await invoke('stop_click_through') + await stopClickThrough() await invoke('stop_monitor_for_clicking_through') }) @@ -54,15 +55,7 @@ function openChat() { onMounted(async () => { // Listen for click-through state changes unlisten.push(await listen('tauri-app:window-click-through:is-inside', (event: { payload: boolean }) => { - if (!viewRef.value) - return - - if (event.payload) { - viewRef.value.style.opacity = '0' - } - else { - viewRef.value.style.opacity = '1' - } + isCursorInside.value = event.payload })) if (connected.value) @@ -85,8 +78,9 @@ onUnmounted(() => {