diff --git a/Cargo.lock b/Cargo.lock index cf47cccea..0f527d0ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,12 +95,18 @@ name = "app" version = "0.4.27" dependencies = [ "log", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "serde", + "serde_json", "tauri", "tauri-build", "tauri-plugin-log", "tauri-plugin-mcp", "tauri-plugin-os", "tauri-plugin-prevent-default", + "tokio", + "windows", ] [[package]] @@ -487,9 +493,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -735,9 +741,9 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", @@ -788,9 +794,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +checksum = "e8fe7d068ca6b3a5782ca5ec9afc244acd99dd441e4686a83b1c3973aba1d489" dependencies = [ "cc", "memchr", @@ -1451,17 +1457,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1665,6 +1675,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1857,9 +1877,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -2323,9 +2343,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2333,9 +2353,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -2828,9 +2848,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" dependencies = [ "base64 0.22.1", "bytes", @@ -2855,13 +2875,13 @@ dependencies = [ "tokio", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", ] [[package]] @@ -3273,9 +3293,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4059,6 +4079,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -4579,7 +4617,7 @@ dependencies = [ "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -4631,17 +4669,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -4651,15 +4678,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -4678,15 +4696,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -4720,21 +4729,6 @@ 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" @@ -4744,29 +4738,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -4791,180 +4769,90 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" version = "0.5.40" @@ -4985,12 +4873,12 @@ dependencies = [ [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/apps/stage-tamagotchi/src-tauri/Cargo.toml b/apps/stage-tamagotchi/src-tauri/Cargo.toml index 989b76198..f45e5b4ce 100644 --- a/apps/stage-tamagotchi/src-tauri/Cargo.toml +++ b/apps/stage-tamagotchi/src-tauri/Cargo.toml @@ -31,6 +31,9 @@ tauri-plugin-log = "2.0.0-rc" tauri-plugin-os = "2" tauri-plugin-mcp = { workspace = true } tauri-plugin-prevent-default = "1.3" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +tokio = "1.45.1" [profile.dev] incremental = true @@ -40,3 +43,10 @@ debug = true [profile.dev.package."*"] opt-level = 1 debug = false + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6.1" +objc2-foundation = "0.3.1" + +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.61.1", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] } diff --git a/apps/stage-tamagotchi/src-tauri/src/lib.rs b/apps/stage-tamagotchi/src-tauri/src/lib.rs index c9c6a9a76..821cd299d 100644 --- a/apps/stage-tamagotchi/src-tauri/src/lib.rs +++ b/apps/stage-tamagotchi/src-tauri/src/lib.rs @@ -3,14 +3,234 @@ use tauri::tray::TrayIconBuilder; use tauri::Emitter; use tauri::RunEvent; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use tokio::time::sleep; + +#[cfg(target_os = "macos")] +use objc2::{class, msg_send}; + +#[cfg(target_os = "macos")] +use objc2_foundation::{NSPoint, NSRect}; + #[cfg(target_os = "macos")] use tauri::{ActivationPolicy, TitleBarStyle}; use tauri::{Manager, WebviewUrl, WebviewWindowBuilder}; use tauri_plugin_prevent_default::Flags; +#[cfg(target_os = "windows")] +use windows::Win32::UI::WindowsAndMessaging::{ + GetWindowLongW, SetWindowLongW, GWL_EXSTYLE, WS_EX_TRANSPARENT, +}; + mod commands; mod windows; +#[derive(Default)] +struct WindowClickThroughState { + is_click_through_monitoring_enabled: Arc, + is_click_through_enabled: Arc, + is_click_through_cursor_inside: Arc, +} + +fn set_cursor_inside(window: &tauri::Window, is_inside: bool) -> Result<(), String> { + let state = window.state::(); + + state + .is_click_through_cursor_inside + .store(is_inside, Ordering::Relaxed); + + window + .set_ignore_cursor_events(is_inside) + .map_err(|e| format!("Failed to set click-through state: {}", e))?; + + let _ = window.emit("tauri-app:window-click-through:is-inside", is_inside); + + Ok(()) +} + +fn set_click_through_enabled(window: &tauri::Window, enabled: bool) -> Result<(), String> { + let state = window.state::(); + + state + .is_click_through_enabled + .store(enabled, Ordering::Relaxed); + + window + .set_ignore_cursor_events(enabled) + .map_err(|e| format!("Failed to set click-through state: {}", e))?; + + let _ = window.emit("tauri-app:window-click-through:enabled", enabled); + + Ok(()) +} + +/// Get cursor position relative to the window +#[cfg(target_os = "macos")] +async fn is_cursor_in_window(window: &tauri::Window) -> bool { + unsafe { + // Get cursor position in screen coordinates (macOS coordinates - origin at bottom left) + let mouse_location: NSPoint = msg_send![class!(NSEvent), mouseLocation]; + + // Get all screens + // + // We need screens count because for multiple-display users, + // in macOS, the Native API returns the mouse coordinates relative to the primary + // display, for example, if we say the primary display is 1920x1080, another two lies + // on both sides of the primary display with the size of 1920x1080 too, mouse coordinates + // will be 0 at the left edge of p-display, and 1920 at the right edge of the p-display, + // -1080 at the left edge of the left-side display, and 2160 at the right edge of the right-side + // display. + let screens: *const objc2::runtime::AnyObject = msg_send![class!(NSScreen), screens]; + let screens_count: usize = msg_send![screens, count]; + + // Get window position and size from Tauri + // Get the NSWindow from Tauri window to access native properties + // + // While Tauri does provide us window.outer_position() and window.outer_size() + // it's not enough to determine if the cursor is inside the window. + // The value of those two functions returns the in-compatible coordinates (top-left origin) + // with OBJ-C Native API values. This produces a lot of issues when calculating the collision + // of the cursor relative to the window. + // + // We need to get the window's frame in macOS coordinates (bottom-left origin) + // and check if the cursor is inside that frame. + let ns_window: *mut objc2::runtime::AnyObject = window.ns_window().unwrap() as *mut _; + let window_frame: NSRect = msg_send![ns_window, frame]; + + // Log all screens information + for _ in 0..screens_count { + // For debugging purpose, screen object, frame size of the screen, visible frame size of the screen, + // and the scale factor of the screen can be obtained as follows: + // + // let screen: *const objc2::runtime::AnyObject = msg_send![screens, objectAtIndex: i]; + // let frame: NSRect = msg_send![screen, frame]; + // let visible_frame: NSRect = msg_send![screen, visibleFrame]; + // let scale_factor: f64 = msg_send![screen, backingScaleFactor]; + + // Check if mouse is inside our window's frame + let is_inside = mouse_location.x >= window_frame.origin.x + && mouse_location.x <= (window_frame.origin.x + window_frame.size.width) + && mouse_location.y >= window_frame.origin.y + && mouse_location.y <= (window_frame.origin.y + window_frame.size.height); + + if is_inside { + return true; + } + } + } + false +} + +/// Check if modifier key is pressed (Option/Alt or Control) +#[cfg(target_os = "macos")] +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 alt_pressed = (flags & (1 << 19)) != 0; + // let ctrl_pressed = (flags & (1 << 18)) != 0; + + // alt_pressed || ctrl_pressed + alt_pressed + } +} + +#[tauri::command] +async fn start_monitor_for_clicking_through(window: tauri::Window) -> Result<(), String> { + log::info!("Starting monitor for clicking through"); + + let window = window.clone(); + let state = window.state::(); + let is_click_through_enabled = state.is_click_through_enabled.clone(); + let is_click_through_monitoring_enabled = state.is_click_through_monitoring_enabled.clone(); + + // Already monitoring? + if is_click_through_monitoring_enabled.load(Ordering::Relaxed) { + return Ok(()); + } else { + // Set to true + state + .is_click_through_monitoring_enabled + .store(true, Ordering::Relaxed); + } + + // Then start interval timer for monitoring + tauri::async_runtime::spawn(async move { + loop { + sleep(Duration::from_millis(32)).await; // ~30FPS check rate + + // If monitoring is already stopped, break the loop + if !is_click_through_monitoring_enabled.load(Ordering::Relaxed) { + log::info!("Monitoring is stopped, breaking loop"); + break; + } + + // If is disabled already, skip until next check + if !is_click_through_enabled.load(Ordering::Relaxed) { + log::info!("Click-through is disabled, skipping check"); + continue; + } + + #[cfg(target_os = "macos")] + { + let cursor_inside = is_cursor_in_window(&window).await; + let modifier_pressed = is_modifier_pressed(); + + log::info!( + "Cursor inside: {}, Modifier pressed: {}", + cursor_inside, + modifier_pressed + ); + + // Only allow disabling click-through when: + // 1. Cursor is OUTSIDE the window AND + // 2. Modifier key is pressed + let should_be_click_through = cursor_inside && !modifier_pressed; + + if should_be_click_through { + let _ = set_cursor_inside(&window, true); + } else { + let _ = set_cursor_inside(&window, false); + } + } + } + }); + + return Ok(()); +} + +#[tauri::command] +async fn stop_monitor_for_clicking_through(window: tauri::Window) -> Result<(), String> { + log::info!("Stopping monitor for clicking through"); + + let window = window.clone(); + let state = window.state::(); + + // Set to false + // Termination will be triggered in the next interval check (tick) + state + .is_click_through_monitoring_enabled + .store(false, Ordering::Relaxed); + + Ok(()) +} + +#[tauri::command] +async fn start_click_through(window: tauri::Window) -> Result<(), String> { + set_click_through_enabled(&window, true)?; + Ok(()) +} + +#[tauri::command] +async fn stop_click_through(window: tauri::Window) -> Result<(), String> { + set_click_through_enabled(&window, false)?; + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let prevent_default_plugin = tauri_plugin_prevent_default::Builder::new() @@ -21,6 +241,7 @@ pub fn run() { .plugin(prevent_default_plugin) .plugin(tauri_plugin_mcp::Builder.build()) .plugin(tauri_plugin_os::init()) + .manage(WindowClickThroughState::default()) .setup(|app| { let mut builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()); @@ -98,6 +319,10 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ commands::open_settings_window, commands::open_chat_window, + start_monitor_for_clicking_through, + stop_monitor_for_clicking_through, + start_click_through, + stop_click_through, ]) .build(tauri::generate_context!()) .expect("error while building tauri application") diff --git a/apps/stage-tamagotchi/src/pages/index.vue b/apps/stage-tamagotchi/src/pages/index.vue index f66898b8e..2bbb830e8 100644 --- a/apps/stage-tamagotchi/src/pages/index.vue +++ b/apps/stage-tamagotchi/src/pages/index.vue @@ -3,8 +3,9 @@ import { WidgetStage } from '@proj-airi/stage-ui/components' import { useMcpStore } from '@proj-airi/stage-ui/stores' import { connectServer } from '@proj-airi/tauri-plugin-mcp' import { invoke } from '@tauri-apps/api/core' +import { listen } from '@tauri-apps/api/event' import { storeToRefs } from 'pinia' -import { computed, onMounted } from 'vue' +import { computed, onMounted, onUnmounted, ref } from 'vue' import { useWindowShortcuts } from '../composables/window-shortcuts' import { useWindowControlStore } from '../stores/window-controls' @@ -13,6 +14,7 @@ import { WindowControlMode } from '../types/window-controls' const windowStore = useWindowControlStore() useWindowShortcuts() +const viewRef = ref() const mcpStore = useMcpStore() const { connected, serverCmd, serverArgs } = storeToRefs(mcpStore) @@ -29,6 +31,18 @@ const modeIndicatorClass = computed(() => { } }) +onMounted(async () => { + await invoke('start_monitor_for_clicking_through') + await invoke('start_click_through') +}) + +onUnmounted(async () => { + await invoke('stop_click_through') + await invoke('stop_monitor_for_clicking_through') +}) + +const unlisten: (() => void)[] = [] + function openSettings() { invoke('open_settings_window') } @@ -38,6 +52,19 @@ 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' + } + })) + if (connected.value) return if (!serverCmd.value || !serverArgs.value) @@ -50,10 +77,15 @@ onMounted(async () => { console.error(error) } }) + +onUnmounted(() => { + unlisten.forEach(fn => fn?.()) +})