From df4f994e00f2429bd9d2205189fddcb2c2f86deb Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:48:11 +0000 Subject: [PATCH 01/14] research: vendor tao 0.37.1 unmodified Pristine copy of the crates.io release, without its standalone lockfile, so the following commits show Maple's patches as reviewable diffs. Co-Authored-By: Claude Opus 5.5 --- .../src-tauri/patches/tao-0.37.1/Cargo.toml | 412 +++ .../patches/tao-0.37.1/Cargo.toml.orig | 174 ++ .../src-tauri/patches/tao-0.37.1/LICENSE | 201 ++ .../src-tauri/patches/tao-0.37.1/LICENSE.spdx | 18 + .../src-tauri/patches/tao-0.37.1/README.md | 80 + .../tao-0.37.1/examples/background_color.rs | 49 + .../tao-0.37.1/examples/control_flow.rs | 120 + .../patches/tao-0.37.1/examples/cursor.rs | 89 + .../tao-0.37.1/examples/cursor_grab.rs | 66 + .../tao-0.37.1/examples/custom_events.rs | 51 + .../tao-0.37.1/examples/decorations.rs | 51 + .../tao-0.37.1/examples/drag_window.rs | 78 + .../patches/tao-0.37.1/examples/fullscreen.rs | 129 + .../tao-0.37.1/examples/handling_close.rs | 87 + .../tao-0.37.1/examples/min_max_size.rs | 87 + .../patches/tao-0.37.1/examples/minimize.rs | 47 + .../tao-0.37.1/examples/monitor_list.rs | 14 + .../tao-0.37.1/examples/mouse_wheel.rs | 54 + .../tao-0.37.1/examples/multithreaded.rs | 187 ++ .../tao-0.37.1/examples/multiwindow.rs | 55 + .../patches/tao-0.37.1/examples/overlay.rs | 128 + .../tao-0.37.1/examples/parentwindow.rs | 70 + .../tao-0.37.1/examples/progress_bar.rs | 89 + .../tao-0.37.1/examples/reopen_event.rs | 45 + .../tao-0.37.1/examples/request_redraw.rs | 43 + .../examples/request_redraw_threaded.rs | 45 + .../patches/tao-0.37.1/examples/resizable.rs | 51 + .../tao-0.37.1/examples/set_ime_position.rs | 56 + .../patches/tao-0.37.1/examples/theme.rs | 50 + .../patches/tao-0.37.1/examples/timer.rs | 43 + .../tao-0.37.1/examples/transparent.rs | 66 + .../tao-0.37.1/examples/video_modes.rs | 24 + .../patches/tao-0.37.1/examples/window.rs | 52 + .../tao-0.37.1/examples/window_debug.rs | 176 ++ .../tao-0.37.1/examples/window_icon.rs | 64 + .../tao-0.37.1/examples/window_run_return.rs | 59 + .../src-tauri/patches/tao-0.37.1/src/error.rs | 88 + .../src-tauri/patches/tao-0.37.1/src/event.rs | 982 +++++++ .../patches/tao-0.37.1/src/event_loop.rs | 416 +++ .../src-tauri/patches/tao-0.37.1/src/icon.rs | 169 ++ .../patches/tao-0.37.1/src/keyboard.rs | 1596 ++++++++++ .../src-tauri/patches/tao-0.37.1/src/lib.rs | 196 ++ .../patches/tao-0.37.1/src/monitor.rs | 163 ++ .../tao-0.37.1/src/platform/android.rs | 72 + .../patches/tao-0.37.1/src/platform/ios.rs | 378 +++ .../patches/tao-0.37.1/src/platform/linux.rs | 5 + .../patches/tao-0.37.1/src/platform/macos.rs | 458 +++ .../patches/tao-0.37.1/src/platform/mod.rs | 28 + .../tao-0.37.1/src/platform/run_return.rs | 50 + .../patches/tao-0.37.1/src/platform/unix.rs | 308 ++ .../tao-0.37.1/src/platform/windows.rs | 449 +++ .../src/platform_impl/android/mod.rs | 1498 ++++++++++ .../src/platform_impl/android/ndk_glue.rs | 802 +++++ .../src/platform_impl/ios/app_state.rs | 1146 ++++++++ .../tao-0.37.1/src/platform_impl/ios/badge.rs | 15 + .../src/platform_impl/ios/event_loop.rs | 385 +++ .../tao-0.37.1/src/platform_impl/ios/ffi.rs | 433 +++ .../src/platform_impl/ios/keycode.rs | 13 + .../tao-0.37.1/src/platform_impl/ios/mod.rs | 126 + .../src/platform_impl/ios/monitor.rs | 300 ++ .../tao-0.37.1/src/platform_impl/ios/scene.rs | 221 ++ .../tao-0.37.1/src/platform_impl/ios/view.rs | 802 +++++ .../src/platform_impl/ios/window.rs | 877 ++++++ .../src/platform_impl/linux/device.rs | 77 + .../src/platform_impl/linux/event_loop.rs | 1322 +++++++++ .../src/platform_impl/linux/icon.rs | 47 + .../src/platform_impl/linux/keyboard.rs | 293 ++ .../src/platform_impl/linux/keycode.rs | 325 +++ .../tao-0.37.1/src/platform_impl/linux/mod.rs | 93 + .../src/platform_impl/linux/monitor.rs | 96 + .../src/platform_impl/linux/portal.rs | 78 + .../src/platform_impl/linux/taskbar.rs | 161 + .../src/platform_impl/linux/util.rs | 111 + .../src/platform_impl/linux/window.rs | 1097 +++++++ .../src/platform_impl/linux/x11/ffi.rs | 8 + .../src/platform_impl/linux/x11/mod.rs | 8 + .../src/platform_impl/linux/x11/xdisplay.rs | 169 ++ .../tao-0.37.1/src/platform_impl/macos/app.rs | 125 + .../src/platform_impl/macos/app_delegate.rs | 221 ++ .../src/platform_impl/macos/app_state.rs | 469 +++ .../src/platform_impl/macos/badge.rs | 13 + .../src/platform_impl/macos/dock.rs | 91 + .../src/platform_impl/macos/event.rs | 335 +++ .../src/platform_impl/macos/event_loop.rs | 356 +++ .../tao-0.37.1/src/platform_impl/macos/ffi.rs | 273 ++ .../src/platform_impl/macos/icon.rs | 14 + .../src/platform_impl/macos/keycode.rs | 264 ++ .../tao-0.37.1/src/platform_impl/macos/mod.rs | 97 + .../src/platform_impl/macos/monitor.rs | 338 +++ .../src/platform_impl/macos/observer.rs | 305 ++ .../src/platform_impl/macos/progress_bar.rs | 161 + .../src/platform_impl/macos/util/async.rs | 263 ++ .../src/platform_impl/macos/util/cursor.rs | 177 ++ .../src/platform_impl/macos/util/mod.rs | 145 + .../src/platform_impl/macos/view.rs | 1197 ++++++++ .../src/platform_impl/macos/window.rs | 1791 ++++++++++++ .../platform_impl/macos/window_delegate.rs | 681 +++++ .../tao-0.37.1/src/platform_impl/mod.rs | 40 + .../src/platform_impl/windows/dark_mode.rs | 274 ++ .../src/platform_impl/windows/dpi.rs | 111 + .../src/platform_impl/windows/drop_handler.rs | 181 ++ .../src/platform_impl/windows/event.rs | 421 +++ .../src/platform_impl/windows/event_loop.rs | 2586 +++++++++++++++++ .../windows/event_loop/runner.rs | 450 +++ .../src/platform_impl/windows/icon.rs | 149 + .../src/platform_impl/windows/ime.rs | 163 ++ .../src/platform_impl/windows/keyboard.rs | 951 ++++++ .../platform_impl/windows/keyboard_layout.rs | 983 +++++++ .../src/platform_impl/windows/keycode.rs | 338 +++ .../src/platform_impl/windows/mod.rs | 157 + .../src/platform_impl/windows/monitor.rs | 280 ++ .../src/platform_impl/windows/raw_input.rs | 217 ++ .../src/platform_impl/windows/util.rs | 492 ++++ .../src/platform_impl/windows/window.rs | 1568 ++++++++++ .../src/platform_impl/windows/window_state.rs | 518 ++++ .../patches/tao-0.37.1/src/window.rs | 1699 +++++++++++ 116 files changed, 37865 insertions(+) create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml new file mode 100644 index 000000000..e894d86b8 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml @@ -0,0 +1,412 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.85" +name = "tao" +version = "0.37.1" +authors = [ + "Tauri Programme within The Commons Conservancy", + "The winit contributors", +] +build = false +include = [ + "/README.md", + "src/**/*.rs", + "examples/**/*.rs", + "LICENSE*", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Cross-platform window manager library." +documentation = "https://docs.rs/tao" +readme = "README.md" +keywords = ["windowing"] +categories = ["gui"] +license = "Apache-2.0" +repository = "https://github.com/tauri-apps/tao" + +[package.metadata.docs.rs] +features = [ + "rwh_04", + "rwh_05", + "rwh_06", + "serde", + "x11", + "dbus", +] +default-target = "x86_64-unknown-linux-gnu" +targets = [ + "i686-pc-windows-msvc", + "x86_64-pc-windows-msvc", + "i686-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", +] + +[features] +dbus = ["dep:dbus"] +default = [ + "rwh_06", + "x11", + "dbus", +] +rwh_04 = ["dep:rwh_04"] +rwh_05 = ["dep:rwh_05"] +rwh_06 = ["dep:rwh_06"] +serde = [ + "dep:serde", + "dpi/serde", +] +x11 = [ + "dep:gdkx11-sys", + "dep:x11-dl", +] + +[lib] +name = "tao" +path = "src/lib.rs" + +[[example]] +name = "background_color" +path = "examples/background_color.rs" + +[[example]] +name = "control_flow" +path = "examples/control_flow.rs" + +[[example]] +name = "cursor" +path = "examples/cursor.rs" + +[[example]] +name = "cursor_grab" +path = "examples/cursor_grab.rs" + +[[example]] +name = "custom_events" +path = "examples/custom_events.rs" + +[[example]] +name = "decorations" +path = "examples/decorations.rs" + +[[example]] +name = "drag_window" +path = "examples/drag_window.rs" + +[[example]] +name = "fullscreen" +path = "examples/fullscreen.rs" + +[[example]] +name = "handling_close" +path = "examples/handling_close.rs" + +[[example]] +name = "min_max_size" +path = "examples/min_max_size.rs" + +[[example]] +name = "minimize" +path = "examples/minimize.rs" + +[[example]] +name = "monitor_list" +path = "examples/monitor_list.rs" + +[[example]] +name = "mouse_wheel" +path = "examples/mouse_wheel.rs" + +[[example]] +name = "multithreaded" +path = "examples/multithreaded.rs" + +[[example]] +name = "multiwindow" +path = "examples/multiwindow.rs" + +[[example]] +name = "overlay" +path = "examples/overlay.rs" + +[[example]] +name = "parentwindow" +path = "examples/parentwindow.rs" + +[[example]] +name = "progress_bar" +path = "examples/progress_bar.rs" + +[[example]] +name = "reopen_event" +path = "examples/reopen_event.rs" + +[[example]] +name = "request_redraw" +path = "examples/request_redraw.rs" + +[[example]] +name = "request_redraw_threaded" +path = "examples/request_redraw_threaded.rs" + +[[example]] +name = "resizable" +path = "examples/resizable.rs" + +[[example]] +name = "set_ime_position" +path = "examples/set_ime_position.rs" + +[[example]] +name = "theme" +path = "examples/theme.rs" + +[[example]] +name = "timer" +path = "examples/timer.rs" + +[[example]] +name = "transparent" +path = "examples/transparent.rs" + +[[example]] +name = "video_modes" +path = "examples/video_modes.rs" + +[[example]] +name = "window" +path = "examples/window.rs" + +[[example]] +name = "window_debug" +path = "examples/window_debug.rs" + +[[example]] +name = "window_icon" +path = "examples/window_icon.rs" + +[[example]] +name = "window_run_return" +path = "examples/window_run_return.rs" + +[dependencies.bitflags] +version = "2" + +[dependencies.crossbeam-channel] +version = "0.5" + +[dependencies.dpi] +version = "0.1" + +[dependencies.libc] +version = "0.2" + +[dependencies.log] +version = "0.4" + +[dependencies.rwh_04] +version = "0.4" +optional = true +package = "raw-window-handle" + +[dependencies.rwh_05] +version = "0.5" +features = ["std"] +optional = true +package = "raw-window-handle" + +[dependencies.rwh_06] +version = "0.6" +features = ["std"] +optional = true +package = "raw-window-handle" + +[dependencies.serde] +version = "1" +features = ["serde_derive"] +optional = true + +[dependencies.url] +version = "2" + +[dev-dependencies.env_logger] +version = "0.11" + +[dev-dependencies.image] +version = "0.25" + +[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.block2] +version = "0.6" + +[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.objc2] +version = "0.6" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.dbus] +version = "0.9" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.dlopen2] +version = "0.8.0" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gdkwayland-sys] +version = "0.18.0" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gdkx11-sys] +version = "0.18" +optional = true + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gtk] +version = "0.18" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.parking_lot] +version = "0.12" + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.x11-dl] +version = "2.21" +optional = true + +[target.'cfg(target_os = "android")'.dependencies.jni] +version = "0.21" + +[target.'cfg(target_os = "android")'.dependencies.ndk] +version = "0.9" + +[target.'cfg(target_os = "android")'.dependencies.ndk-context] +version = "0.1" + +[target.'cfg(target_os = "android")'.dependencies.ndk-sys] +version = "0.6" + +[target.'cfg(target_os = "android")'.dependencies.percent-encoding] +version = "2.3" + +[target.'cfg(target_os = "android")'.dependencies.tao-macros] +version = "0.1.4" + +[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] +version = "0.3" +features = ["NSProcessInfo"] +default-features = false + +[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit] +version = "0.3" +features = [ + "UIResponder", + "UIScene", + "UISceneOptions", + "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", +] + +[target.'cfg(target_os = "macos")'.dependencies.core-foundation] +version = "0.10" + +[target.'cfg(target_os = "macos")'.dependencies.core-graphics] +version = "0.25" + +[target.'cfg(target_os = "macos")'.dependencies.dispatch2] +version = "0.3" + +[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] +version = "0.3" +features = [ + "std", + "objc2-core-foundation", + "NSApplication", + "NSButton", + "NSColor", + "NSControl", + "NSEvent", + "NSGraphics", + "NSImage", + "NSOpenGLView", + "NSPasteboard", + "NSResponder", + "NSRunningApplication", + "NSScreen", + "NSView", + "NSWindow", + "NSUserActivity", +] +default-features = false + +[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] +version = "0.3" +features = [ + "std", + "NSArray", + "NSAttributedString", + "NSAutoreleasePool", + "NSDate", + "NSDictionary", + "NSEnumerator", + "NSGeometry", + "NSObjCRuntime", + "NSRange", + "NSString", + "NSThread", + "NSURL", +] +default-features = false + +[target.'cfg(target_os = "windows")'.dependencies.parking_lot] +version = "0.12" + +[target.'cfg(target_os = "windows")'.dependencies.unicode-segmentation] +version = "1.11" + +[target.'cfg(target_os = "windows")'.dependencies.windows] +version = "0.62" +features = [ + "Win32_Devices_HumanInterfaceDevice", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Graphics_Dwm", + "Win32_Graphics_Gdi", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", + "Win32_System_DataExchange", + "Win32_System_Diagnostics_Debug", + "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Ole", + "Win32_System_Registry", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_System_SystemInformation", + "Win32_UI_Accessibility", + "Win32_UI_Controls", + "Win32_UI_HiDpi", + "Win32_UI_Input_Ime", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Input_Pointer", + "Win32_UI_Input_Touch", + "Win32_UI_Shell", + "Win32_UI_TextServices", + "Win32_UI_WindowsAndMessaging", +] + +[target.'cfg(target_os = "windows")'.dependencies.windows-core] +version = "0.62" + +[target.'cfg(target_os = "windows")'.dependencies.windows-version] +version = "0.1" + +[target.'cfg(target_os = "windows")'.dev-dependencies.softbuffer] +version = "0.4" diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig new file mode 100644 index 000000000..5aa24dfdb --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig @@ -0,0 +1,174 @@ +[package] +name = "tao" +version = "0.37.1" +description = "Cross-platform window manager library." +authors = [ + "Tauri Programme within The Commons Conservancy", + "The winit contributors", +] +edition = "2021" +rust-version = "1.85" +keywords = ["windowing"] +license = "Apache-2.0" +readme = "README.md" +repository = "https://github.com/tauri-apps/tao" +documentation = "https://docs.rs/tao" +categories = ["gui"] +include = ["/README.md", "src/**/*.rs", "examples/**/*.rs", "LICENSE*"] + +[package.metadata.docs.rs] +features = ["rwh_04", "rwh_05", "rwh_06", "serde", "x11", "dbus"] +default-target = "x86_64-unknown-linux-gnu" +targets = [ + "i686-pc-windows-msvc", + "x86_64-pc-windows-msvc", + "i686-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", +] + +[features] +default = ["rwh_06", "x11", "dbus"] +serde = ["dep:serde", "dpi/serde"] +rwh_04 = ["dep:rwh_04"] +rwh_05 = ["dep:rwh_05"] +rwh_06 = ["dep:rwh_06"] +x11 = ["dep:gdkx11-sys", "dep:x11-dl"] +dbus = ["dep:dbus"] + +[workspace] +members = ["tao-macros"] + +[dependencies] +libc = "0.2" +log = "0.4" +serde = { version = "1", optional = true, features = ["serde_derive"] } +rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } +rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ + "std", +], optional = true } +rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ + "std", +], optional = true } +bitflags = "2" +crossbeam-channel = "0.5" +url = "2" +dpi = "0.1" + +[dev-dependencies] +image = "0.25" +env_logger = "0.11" + +[target."cfg(target_os = \"windows\")".dev-dependencies] +softbuffer = "0.4" + +[target."cfg(target_os = \"windows\")".dependencies] +parking_lot = "0.12" +unicode-segmentation = "1.11" +windows-version = "0.1" +windows-core = "0.62" + +[target."cfg(target_os = \"windows\")".dependencies.windows] +version = "0.62" +features = [ + "Win32_Devices_HumanInterfaceDevice", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Graphics_Dwm", + "Win32_Graphics_Gdi", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", + "Win32_System_DataExchange", + "Win32_System_Diagnostics_Debug", + "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Ole", + "Win32_System_Registry", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_System_SystemInformation", + "Win32_UI_Accessibility", + "Win32_UI_Controls", + "Win32_UI_HiDpi", + "Win32_UI_Input_Ime", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Input_Pointer", + "Win32_UI_Input_Touch", + "Win32_UI_Shell", + "Win32_UI_TextServices", + "Win32_UI_WindowsAndMessaging", +] + +[target."cfg(target_os = \"android\")".dependencies] +jni = "0.21" +ndk = "0.9" +ndk-sys = "0.6" +ndk-context = "0.1" +percent-encoding = "2.3" +tao-macros = { version = "0.1.4", path = "./tao-macros" } + +[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] +objc2 = "0.6" +block2 = "0.6" + +[target."cfg(target_os = \"macos\")".dependencies] +objc2-foundation = { version = "0.3", default-features = false, features = [ + "std", + "NSArray", + "NSAttributedString", + "NSAutoreleasePool", + "NSDate", + "NSDictionary", + "NSEnumerator", + "NSGeometry", + "NSObjCRuntime", + "NSRange", + "NSString", + "NSThread", + "NSURL", +] } +objc2-app-kit = { version = "0.3", default-features = false, features = [ + "std", + "objc2-core-foundation", + "NSApplication", + "NSButton", + "NSColor", + "NSControl", + "NSEvent", + "NSGraphics", + "NSImage", + "NSOpenGLView", + "NSPasteboard", + "NSResponder", + "NSRunningApplication", + "NSScreen", + "NSView", + "NSWindow", + "NSUserActivity", +] } +core-foundation = "0.10" +core-graphics = "0.25" +dispatch2 = "0.3" + +[target."cfg(target_os = \"ios\")".dependencies] +objc2-ui-kit = { version = "0.3", features = [ + "UIResponder", + "UIScene", + "UISceneOptions", + "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", +] } +objc2-foundation = { version = "0.3", default-features = false, features = [ + "NSProcessInfo", +] } + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +gtk = "0.18" +gdkx11-sys = { version = "0.18", optional = true } +gdkwayland-sys = "0.18.0" +x11-dl = { version = "2.21", optional = true } +parking_lot = "0.12" +dlopen2 = "0.8.0" +dbus = { version = "0.9", optional = true } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE new file mode 100644 index 000000000..ad410e113 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx new file mode 100644 index 000000000..ac886c6d6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx @@ -0,0 +1,18 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tao +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageCopyrightText: 2021-2023, The Tauri Programme in the Commons Conservancy +PackageSummary: Tao is the official, rust-based window manager for Wry. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2020-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tao +PackageDownloadLocation: git+https://github.com/tauri-apps/tao.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tao.git +Creator: Person: Daniel Thompson-Yvetot diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md new file mode 100644 index 000000000..a7d504619 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md @@ -0,0 +1,80 @@ +TAO - Window Creation Library + +[![](https://img.shields.io/crates/v/tao?style=flat-square)](https://crates.io/crates/tao) +[![](https://img.shields.io/docsrs/tao?style=flat-square)](https://docs.rs/tao/) +[![License](https://img.shields.io/badge/License-Apache%202-green.svg)](https://opencollective.com/tauri) +[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) +[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) +[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) +[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) + +Cross-platform application window creation library in Rust that supports all major platforms like +Windows, macOS, Linux, iOS and Android. Built for you, maintained for Tauri. + +### Cargo Features + +TAO provides the following features, which can be enabled in your `Cargo.toml` file: + +- `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). + +## Platform-specific notes + +### Android + +This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. + +Running on an Android device needs a dynamic system library, add this to Cargo.toml: + +```toml +[[example]] +name = "request_redraw_threaded" +crate-type = ["cdylib"] +``` + +And add this to the example file to add the native activity glue: + +```rust +#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] +fn main() { + ... +} +``` + +And run the application with `cargo apk run --example request_redraw_threaded` + +### Linux + +Gtk and its related libraries are used to build the support of Linux. Be sure to install following packages before building: + +#### Arch Linux / Manjaro: + +```bash +sudo pacman -S gtk3 +``` + +#### Debian / Ubuntu: + +```bash +sudo apt install libgtk-3-dev +``` + +### Acknowledgement + +This is a fork of [winit](https://crates.io/crates/winit) which replaces Linux's port to Gtk. +In the future, we want to make these features more modular as separate crates. So we can switch back to winit and also benefit the whole community. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs new file mode 100644 index 000000000..9dff72f93 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs @@ -0,0 +1,49 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use dpi::LogicalSize; +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, + window::WindowBuilder, +}; + +fn main() { + let event_loop = EventLoop::new(); + + let mut background_color = (100, 100, 100, 255); + + let window = WindowBuilder::new() + .with_title("Hit space to change background color!") + .with_inner_size(LogicalSize::new(400.0, 300.0)) + .with_background_color(background_color) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::Space, + state: ElementState::Released, + .. + }, + .. + } => { + background_color.1 = background_color.1.wrapping_add(20); + println!("Setting background color to: {background_color:?}"); + window.set_background_color(Some(background_color)); + } + _ => (), + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs new file mode 100644 index 000000000..45148bfd2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs @@ -0,0 +1,120 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{thread, time}; + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::WindowBuilder, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Wait, + WaitUntil, + Poll, +} + +const WAIT_TIME: time::Duration = time::Duration::from_millis(100); +const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + + println!("Press '1' to switch to Wait mode."); + println!("Press '2' to switch to WaitUntil mode."); + println!("Press '3' to switch to Poll mode."); + println!("Press 'R' to toggle request_redraw() calls."); + println!("Press 'Esc' to close the window."); + + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") + .build(&event_loop) + .unwrap(); + + let mut mode = Mode::Wait; + let mut request_redraw = false; + let mut wait_cancelled = false; + let mut close_requested = false; + + event_loop.run(move |event, _, control_flow| { + use tao::event::StartCause; + println!("{event:?}"); + match event { + Event::NewEvents(start_cause) => { + wait_cancelled = match start_cause { + StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, + _ => false, + } + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => { + close_requested = true; + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, + state: ElementState::Pressed, + .. + }, + .. + } => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + if Key::Character("1") == logical_key { + mode = Mode::Wait; + println!("\nmode: {mode:?}\n"); + } + if Key::Character("2") == logical_key { + mode = Mode::WaitUntil; + println!("\nmode: {mode:?}\n"); + } + if Key::Character("3") == logical_key { + mode = Mode::Poll; + println!("\nmode: {mode:?}\n"); + } + if Key::Character("r") == logical_key { + request_redraw = !request_redraw; + println!("\nrequest_redraw: {request_redraw}\n"); + } + if Key::Escape == logical_key { + close_requested = true; + } + } + _ => {} + }, + Event::MainEventsCleared => { + if request_redraw && !wait_cancelled && !close_requested { + window.request_redraw(); + } + if close_requested { + *control_flow = ControlFlow::Exit; + } + } + Event::RedrawRequested(_window_id) => {} + Event::RedrawEventsCleared => { + *control_flow = match mode { + Mode::Wait => ControlFlow::Wait, + Mode::WaitUntil => { + if wait_cancelled { + *control_flow + } else { + ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME) + } + } + Mode::Poll => { + thread::sleep(POLL_SLEEP_TIME); + ControlFlow::Poll + } + }; + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs new file mode 100644 index 000000000..48a91d6a9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs @@ -0,0 +1,89 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{CursorIcon, WindowBuilder}, +}; + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title("A fantastic window!"); + + let mut cursor_idx = 0; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => { + println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); + window.set_cursor_icon(CURSORS[cursor_idx]); + if cursor_idx < CURSORS.len() - 1 { + cursor_idx += 1; + } else { + cursor_idx = 0; + } + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} + +const CURSORS: &[CursorIcon] = &[ + CursorIcon::Default, + CursorIcon::Crosshair, + CursorIcon::Hand, + CursorIcon::Arrow, + CursorIcon::Move, + CursorIcon::Text, + CursorIcon::Wait, + CursorIcon::Help, + CursorIcon::Progress, + CursorIcon::NotAllowed, + CursorIcon::ContextMenu, + CursorIcon::Cell, + CursorIcon::VerticalText, + CursorIcon::Alias, + CursorIcon::Copy, + CursorIcon::NoDrop, + CursorIcon::Grab, + CursorIcon::Grabbing, + CursorIcon::AllScroll, + CursorIcon::ZoomIn, + CursorIcon::ZoomOut, + CursorIcon::EResize, + CursorIcon::NResize, + CursorIcon::NeResize, + CursorIcon::NwResize, + CursorIcon::SResize, + CursorIcon::SeResize, + CursorIcon::SwResize, + CursorIcon::WResize, + CursorIcon::EwResize, + CursorIcon::NsResize, + CursorIcon::NeswResize, + CursorIcon::NwseResize, + CursorIcon::ColResize, + CursorIcon::RowResize, +]; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs new file mode 100644 index 000000000..a939d8201 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs @@ -0,0 +1,66 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("Super Cursor Grab'n'Hide Simulator 9000") + .build(&event_loop) + .unwrap(); + + let mut modifiers = ModifiersState::default(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: key, + state: ElementState::Released, + .. + }, + .. + } => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + match key { + Key::Escape => *control_flow = ControlFlow::Exit, + Key::Character(ch) => match ch.to_lowercase().as_str() { + "g" => window.set_cursor_grab(!modifiers.shift_key()).unwrap(), + "h" => window.set_cursor_visible(modifiers.shift_key()), + _ => (), + }, + _ => (), + } + } + WindowEvent::ModifiersChanged(m) => modifiers = m, + _ => (), + }, + Event::DeviceEvent { event, .. } => match event { + DeviceEvent::MouseMotion { delta, .. } => println!("mouse moved: {delta:?}"), + DeviceEvent::Button { button, state, .. } => match state { + ElementState::Pressed => println!("mouse button {button} pressed"), + ElementState::Released => println!("mouse button {button} released"), + _ => (), + }, + _ => (), + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs new file mode 100644 index 000000000..f94d00113 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs @@ -0,0 +1,51 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoopBuilder}, + window::WindowBuilder, + }; + + #[derive(Debug, Clone, Copy)] + enum CustomEvent { + Timer, + } + + let event_loop = EventLoopBuilder::::with_user_event().build(); + + let _window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + // `EventLoopProxy` allows you to dispatch custom events to the main Tao event + // loop from any thread. + let event_loop_proxy = event_loop.create_proxy(); + + std::thread::spawn(move || { + // Wake up the `event_loop` once every second and dispatch a custom event + // from a different thread. + loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + event_loop_proxy.send_event(CustomEvent::Timer).ok(); + } + }); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::UserEvent(event) => println!("user event: {event:?}"), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs new file mode 100644 index 000000000..d9025e2b4 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs @@ -0,0 +1,51 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let mut decorations = true; + + let window = WindowBuilder::new() + .with_title("Hit space to toggle decorations.") + .with_inner_size(LogicalSize::new(400.0, 200.0)) + .with_decorations(decorations) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::Space, + state: ElementState::Released, + .. + }, + .. + } => { + decorations = !decorations; + println!("Decorations: {decorations}"); + window.set_decorations(decorations); + } + _ => (), + }, + _ => (), + }; + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs new file mode 100644 index 000000000..895c360d3 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs @@ -0,0 +1,78 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::{Window, WindowBuilder, WindowId}, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window_1 = WindowBuilder::new().build(&event_loop).unwrap(); + let window_2 = WindowBuilder::new().build(&event_loop).unwrap(); + + let mut switched = false; + let mut entered_id = window_2.id(); + + event_loop.run(move |event, _, control_flow| match event { + Event::NewEvents(StartCause::Init) => { + eprintln!("Switch which window is to be dragged by pressing \"x\".") + } + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::MouseInput { + state: ElementState::Pressed, + button: MouseButton::Left, + .. + } => { + let window = if (window_id == window_1.id() && switched) + || (window_id == window_2.id() && !switched) + { + &window_2 + } else { + &window_1 + }; + + window.drag_window().unwrap() + } + WindowEvent::CursorEntered { .. } => { + entered_id = window_id; + name_windows(entered_id, switched, &window_1, &window_2) + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Released, + logical_key: Key::Character("x"), + .. + }, + .. + } => { + switched = !switched; + name_windows(entered_id, switched, &window_1, &window_2); + println!("Switched!") + } + _ => (), + }, + _ => (), + }); +} + +fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) { + let (drag_target, other) = + if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { + (&window_2, &window_1) + } else { + (&window_1, &window_2) + }; + drag_target.set_title("drag target"); + other.set_title("tao window"); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs new file mode 100644 index 000000000..c5d8bfacd --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs @@ -0,0 +1,129 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::io::{stdin, stdout, Write}; + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + monitor::{MonitorHandle, VideoMode}, + window::{Fullscreen, WindowBuilder}, +}; +#[allow(clippy::single_match)] +#[allow(clippy::ok_expect)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless, (3) borderless on current monitor: "); + stdout().flush().unwrap(); + + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().ok().expect("Please enter a number"); + + let fullscreen = Some(match num { + 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), + 2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))), + 3 => Fullscreen::Borderless(None), + _ => panic!("Please enter a valid number"), + }); + + let mut decorations = true; + + let window = WindowBuilder::new() + .with_title("Hello world!") + .with_fullscreen(fullscreen.clone()) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, + state: ElementState::Pressed, + .. + }, + .. + } => { + if Key::Escape == logical_key { + *control_flow = ControlFlow::Exit + } + + if Key::Character("f") == logical_key { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(fullscreen.clone()); + } + } + + if Key::Character("s") == logical_key { + println!("window.fullscreen {:?}", window.fullscreen()); + } + if Key::Character("m") == logical_key { + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); + } + if Key::Character("d") == logical_key { + decorations = !decorations; + window.set_decorations(decorations); + } + } + _ => (), + }, + _ => {} + } + }); +} + +// Enumerate monitors and prompt user to choose one +fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { + for (num, monitor) in event_loop.available_monitors().enumerate() { + println!("Monitor #{}: {:?}", num, monitor.name()); + } + + print!("Please write the number of the monitor to use: "); + stdout().flush().unwrap(); + + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().expect("Please enter a number"); + let monitor = event_loop + .available_monitors() + .nth(num) + .expect("Please enter a valid ID"); + + println!("Using {:?}", monitor.name()); + + monitor +} + +fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { + for (i, video_mode) in monitor.video_modes().enumerate() { + println!("Video mode #{i}: {video_mode}"); + } + + print!("Please write the number of the video mode to use: "); + stdout().flush().unwrap(); + + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().expect("Please enter a number"); + let video_mode = monitor + .video_modes() + .nth(num) + .expect("Please enter a valid ID"); + + println!("Using {video_mode}"); + + video_mode +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs new file mode 100644 index 000000000..278a646fc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs @@ -0,0 +1,87 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("Your faithful window") + .build(&event_loop) + .unwrap(); + + let mut close_requested = false; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::CloseRequested => { + // `CloseRequested` is sent when the close button on the window is pressed (or + // through whatever other mechanisms the window manager provides for closing a + // window). If you don't handle this event, the close button won't actually do + // anything. + + // A common thing to do here is prompt the user if they have unsaved work. + // Creating a proper dialog box for that is far beyond the scope of this + // example, so here we'll just respond to the Y and N keys. + println!("Are you ready to bid your window farewell? [Y/N]"); + close_requested = true; + + // In applications where you can safely close the window without further + // action from the user, this is generally where you'd handle cleanup before + // closing the window. How to close the window is detailed in the handler for + // the Y key. + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(char), + state: ElementState::Released, + .. + }, + .. + } => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + match char { + "y" => { + if close_requested { + // This is where you'll want to do any cleanup you need. + println!("Buh-bye!"); + + // For a single-window application like this, you'd normally just + // break out of the event loop here. If you wanted to keep running the + // event loop (i.e. if it's a multi-window application), you need to + // drop the window. That closes it, and results in `Destroyed` being + // sent. + *control_flow = ControlFlow::Exit; + } + } + "n" => { + if close_requested { + println!("Your window will continue to stay by your side."); + close_requested = false; + } + } + _ => (), + } + } + _ => (), + } + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs new file mode 100644 index 000000000..6ad631faa --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs @@ -0,0 +1,87 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + dpi::LogicalUnit, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::{WindowBuilder, WindowSizeConstraints}, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let min_width = 400.0; + let max_width = 800.0; + let min_height = 200.0; + let max_height = 400.0; + let mut size_constraints = WindowSizeConstraints::default(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + eprintln!("constraint keys:"); + eprintln!(" (E) Toggle the min width"); + eprintln!(" (F) Toggle the max width"); + eprintln!(" (P) Toggle the min height"); + eprintln!(" (V) Toggle the max height"); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + }, + .. + } => match key_str { + "e" => { + size_constraints.min_width = size_constraints + .min_width + .is_none() + .then_some(LogicalUnit::new(min_width).into()); + window.set_inner_size_constraints(size_constraints); + } + "f" => { + size_constraints.max_width = size_constraints + .max_width + .is_none() + .then_some(LogicalUnit::new(max_width).into()); + window.set_inner_size_constraints(size_constraints); + } + "p" => { + size_constraints.min_height = size_constraints + .min_height + .is_none() + .then_some(LogicalUnit::new(min_height).into()); + window.set_inner_size_constraints(size_constraints); + } + "v" => { + size_constraints.max_height = size_constraints + .max_height + .is_none() + .then_some(LogicalUnit::new(max_height).into()); + window.set_inner_size_constraints(size_constraints); + } + _ => {} + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs new file mode 100644 index 000000000..76ceeb7b5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs @@ -0,0 +1,47 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +extern crate tao; + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + // Keyboard input event to handle minimize via a hotkey + Event::WindowEvent { + event: WindowEvent::KeyboardInput { event, .. }, + window_id, + .. + } if window_id == window.id() && Key::Character("m") == event.logical_key => { + // Pressing the 'm' key will minimize the window + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + window.set_minimized(true); + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs new file mode 100644 index 000000000..02ea917d6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs @@ -0,0 +1,14 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{event_loop::EventLoop, window::WindowBuilder}; + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + dbg!(window.available_monitors().collect::>()); + dbg!(window.primary_monitor()); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs new file mode 100644 index 000000000..8997a5e6f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs @@ -0,0 +1,54 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{DeviceEvent, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::collapsible_match)] +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("Mouse Wheel events") + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + _ => (), + }, + Event::DeviceEvent { event, .. } => match event { + DeviceEvent::MouseWheel { delta, .. } => match delta { + tao::event::MouseScrollDelta::LineDelta(x, y) => { + println!("mouse wheel Line Delta: ({x},{y})"); + let pixels_per_line = 120.0; + let mut pos = window.outer_position().unwrap(); + pos.x -= (x * pixels_per_line) as i32; + pos.y -= (y * pixels_per_line) as i32; + window.set_outer_position(pos) + } + tao::event::MouseScrollDelta::PixelDelta(p) => { + println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y); + let mut pos = window.outer_position().unwrap(); + pos.x -= p.x as i32; + pos.y -= p.y as i32; + window.set_outer_position(pos) + } + _ => (), + }, + _ => (), + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs new file mode 100644 index 000000000..7dfe99632 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs @@ -0,0 +1,187 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[allow(clippy::single_match)] +#[allow(clippy::iter_nth)] +fn main() { + use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + + use tao::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::{CursorIcon, Fullscreen, WindowBuilder}, + }; + + const WINDOW_COUNT: usize = 3; + const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); + + env_logger::init(); + let event_loop = EventLoop::new(); + let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); + for _ in 0..WINDOW_COUNT { + let window = WindowBuilder::new() + .with_inner_size(WINDOW_SIZE) + .build(&event_loop) + .unwrap(); + + let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect(); + let mut video_mode_id = 0usize; + + let (tx, rx) = mpsc::channel(); + window_senders.insert(window.id(), tx); + let mut modifiers = ModifiersState::default(); + thread::spawn(move || { + while let Ok(event) = rx.recv() { + match event { + WindowEvent::Moved { .. } => { + // We need to update our chosen video mode if the window + // was moved to an another monitor, so that the window + // appears on this monitor instead when we go fullscreen + let previous_video_mode = video_modes.iter().nth(video_mode_id).cloned(); + video_modes = window.current_monitor().unwrap().video_modes().collect(); + video_mode_id = video_mode_id.min(video_modes.len()); + let video_mode = video_modes.iter().nth(video_mode_id); + + // Different monitors may support different video modes, + // and the index we chose previously may now point to a + // completely different video mode, so notify the user + if video_mode != previous_video_mode.as_ref() { + println!( + "Window moved to another monitor, picked video mode: {}", + video_modes.iter().nth(video_mode_id).unwrap() + ); + } + } + WindowEvent::ModifiersChanged(mod_state) => { + modifiers = mod_state; + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Released, + logical_key: key, + .. + }, + .. + } => { + use Key::{ArrowLeft, ArrowRight, Character}; + window.set_title(&format!("{key:?}")); + let state = !modifiers.shift_key(); + match &key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Character(string) => match string.to_lowercase().as_str() { + "a" => window.set_always_on_top(state), + "c" => window.set_cursor_icon(match state { + true => CursorIcon::Progress, + false => CursorIcon::Default, + }), + "d" => window.set_decorations(!state), + "f" => window.set_fullscreen(match (state, modifiers.alt_key()) { + (true, false) => Some(Fullscreen::Borderless(None)), + (true, true) => Some(Fullscreen::Exclusive( + video_modes.iter().nth(video_mode_id).unwrap().clone(), + )), + (false, _) => None, + }), + "g" => window.set_cursor_grab(state).unwrap(), + "h" => window.set_cursor_visible(!state), + "i" => { + println!("Info:"); + println!("-> outer_position : {:?}", window.outer_position()); + println!("-> inner_position : {:?}", window.inner_position()); + println!("-> outer_size : {:?}", window.outer_size()); + println!("-> inner_size : {:?}", window.inner_size()); + println!("-> fullscreen : {:?}", window.fullscreen()); + } + "l" => window.set_min_inner_size(match state { + true => Some(WINDOW_SIZE), + false => None, + }), + "m" => window.set_maximized(state), + "p" => window.set_outer_position({ + let mut position = window.outer_position().unwrap(); + let sign = if state { 1 } else { -1 }; + position.x += 10 * sign; + position.y += 10 * sign; + position + }), + "q" => window.request_redraw(), + "r" => window.set_resizable(state), + "s" => window.set_inner_size(match state { + true => PhysicalSize::new(WINDOW_SIZE.width + 100, WINDOW_SIZE.height + 100), + false => WINDOW_SIZE, + }), + "w" => { + if let Size::Physical(size) = WINDOW_SIZE.into() { + window + .set_cursor_position(Position::Physical(PhysicalPosition::new( + size.width as i32 / 2, + size.height as i32 / 2, + ))) + .unwrap() + } + } + "z" => { + window.set_visible(false); + thread::sleep(Duration::from_secs(1)); + window.set_visible(true); + } + _ => (), + }, + ArrowRight | ArrowLeft => { + video_mode_id = match &key { + ArrowLeft => video_mode_id.saturating_sub(1), + ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1), + _ => unreachable!(), + }; + println!( + "Picking video mode: {}", + video_modes.iter().nth(video_mode_id).unwrap() + ); + } + _ => (), + } + } + _ => (), + } + } + }); + } + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = match !window_senders.is_empty() { + true => ControlFlow::Wait, + false => ControlFlow::Exit, + }; + match event { + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested + | WindowEvent::Destroyed + | WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Released, + logical_key: Key::Escape, + .. + }, + .. + } => { + window_senders.remove(&window_id); + } + _ => { + if let Some(tx) = window_senders.get(&window_id) { + if let Some(event) = event.to_static() { + tx.send(event).unwrap(); + } + } + } + }, + _ => (), + } + }) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs new file mode 100644 index 000000000..32973057a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs @@ -0,0 +1,55 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, +}; + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let mut windows = HashMap::new(); + for _ in 0..3 { + let window = Window::new(&event_loop).unwrap(); + windows.insert(window.id(), window); + } + + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { + event, window_id, .. + } = event + { + match event { + WindowEvent::CloseRequested => { + println!("Window {window_id:?} has received the signal to close"); + + // This drops the window, causing it to close. + windows.remove(&window_id); + + if windows.is_empty() { + *control_flow = ControlFlow::Exit; + } + } + WindowEvent::KeyboardInput { + event: KeyEvent { + state: ElementState::Pressed, + .. + }, + .. + } => { + let window = Window::new(event_loop).unwrap(); + windows.insert(window.id(), window); + } + _ => (), + } + } + }) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs new file mode 100644 index 000000000..03f290c61 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs @@ -0,0 +1,128 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::WindowBuilder, +}; + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +use tao::platform::unix::WindowExtUnix; + +#[cfg(target_os = "macos")] +use tao::platform::macos::WindowExtMacOS; + +#[cfg(target_os = "ios")] +use tao::platform::ios::WindowExtIOS; + +#[cfg(windows)] +use tao::{ + dpi::PhysicalSize, platform::windows::IconExtWindows, platform::windows::WindowExtWindows, + window::Icon, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + let mut modifiers = ModifiersState::default(); + + eprintln!("Key mappings:"); + #[cfg(windows)] + eprintln!(" [any key]: Show the Overlay Icon"); + #[cfg(not(windows))] + eprintln!(" [1-5]: Show a Badge count"); + eprintln!(" Ctrl+1: Clear"); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::WindowEvent { event, .. } => match event { + WindowEvent::ModifiersChanged(new_state) => { + modifiers = new_state; + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + } => { + let _count = match key_str { + "1" => 1, + "2" => 2, + "3" => 3, + "4" => 4, + "5" => 5, + _ => 20, + }; + + if modifiers.is_empty() { + #[cfg(windows)] + { + let mut path = std::env::current_dir().unwrap(); + path.push("./examples/icon.ico"); + let icon = Icon::from_path(path, Some(PhysicalSize::new(32, 32))).unwrap(); + + window.set_overlay_icon(Some(&icon)); + } + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + window.set_badge_count(Some(_count), None); + + #[cfg(target_os = "macos")] + window.set_badge_label(_count.to_string().into()); + + #[cfg(target_os = "ios")] + window.set_badge_count(_count); + } else if modifiers.control_key() && key_str == "1" { + #[cfg(windows)] + window.set_overlay_icon(None); + + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + window.set_badge_count(None, None); + + #[cfg(target_os = "macos")] + window.set_badge_label(None); + + #[cfg(target_os = "ios")] + window.set_badge_count(0); + } + } + _ => {} + }, + _ => {} + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs new file mode 100644 index 000000000..542565105 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs @@ -0,0 +1,70 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] +fn main() { + use std::collections::HashMap; + #[cfg(target_os = "macos")] + use tao::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; + #[cfg(target_os = "linux")] + use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; + #[cfg(target_os = "windows")] + use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; + use tao::{ + dpi::LogicalSize, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + env_logger::init(); + let event_loop = EventLoop::new(); + let mut windows = HashMap::new(); + let main_window = WindowBuilder::new().build(&event_loop).unwrap(); + + #[cfg(target_os = "macos")] + let parent_window = main_window.ns_window(); + #[cfg(target_os = "windows")] + let parent_window = main_window.hwnd(); + #[cfg(target_os = "linux")] + let parent_window = main_window.gtk_window(); + + let child_window_builder = WindowBuilder::new().with_inner_size(LogicalSize::new(200, 200)); + + #[cfg(any(target_os = "windows", target_os = "macos"))] + let child_window_builder = child_window_builder.with_parent_window(parent_window); + + #[cfg(target_os = "linux")] + let child_window_builder = child_window_builder.with_transient_for(parent_window); + + let child_window = child_window_builder.build(&event_loop).unwrap(); + + windows.insert(child_window.id(), child_window); + windows.insert(main_window.id(), main_window); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::NewEvents(StartCause::Init) => println!("TAO application started!"), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + .. + } => { + println!("Window {window_id:?} has received the signal to close"); + // This drop the window, causing it to close. + windows.remove(&window_id); + if windows.is_empty() { + *control_flow = ControlFlow::Exit; + } + } + _ => (), + }; + }) +} + +#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] +fn main() { + println!("This platform doesn't have the parent window support."); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs new file mode 100644 index 000000000..c526a74cc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs @@ -0,0 +1,89 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::{ProgressBarState, ProgressState, WindowBuilder}, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + let mut modifiers = ModifiersState::default(); + + eprintln!("Key mappings:"); + eprintln!(" [1-5]: Set progress to [0%, 25%, 50%, 75%, 100%]"); + eprintln!(" Ctrl+1: Set state to None"); + eprintln!(" Ctrl+2: Set state to Normal"); + eprintln!(" Ctrl+3: Set state to Indeterminate"); + eprintln!(" Ctrl+4: Set state to Paused"); + eprintln!(" Ctrl+5: Set state to Error"); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::WindowEvent { event, .. } => match event { + WindowEvent::ModifiersChanged(new_state) => { + modifiers = new_state; + } + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + } => { + if modifiers.is_empty() { + let mut progress: u64 = 0; + match key_str { + "1" => progress = 0, + "2" => progress = 25, + "3" => progress = 50, + "4" => progress = 75, + "5" => progress = 100, + _ => {} + } + + window.set_progress_bar(ProgressBarState { + progress: Some(progress), + state: Some(ProgressState::Normal), + desktop_filename: None, + }); + } else if modifiers.control_key() { + let mut state = ProgressState::None; + match key_str { + "1" => state = ProgressState::None, + "2" => state = ProgressState::Normal, + "3" => state = ProgressState::Indeterminate, + "4" => state = ProgressState::Paused, + "5" => state = ProgressState::Error, + _ => {} + } + + window.set_progress_bar(ProgressBarState { + progress: None, + state: Some(state), + desktop_filename: None, + }); + } + } + _ => {} + }, + _ => {} + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs new file mode 100644 index 000000000..5b14ef4a2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs @@ -0,0 +1,45 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, +}; + +#[allow(clippy::single_match)] +fn main() { + let event_loop = EventLoop::new(); + + let mut window = Some(Window::new(&event_loop).unwrap()); + + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + // drop the window + window = None; + } + Event::Reopen { + has_visible_windows, + .. + } => { + println!("on reopen, has visible windows: {has_visible_windows}"); + if !has_visible_windows { + window = Some(Window::new(event_loop).unwrap()) + } + } + Event::MainEventsCleared => { + if let Some(w) = &window { + w.request_redraw(); + } + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs new file mode 100644 index 000000000..f3f563c6f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs @@ -0,0 +1,43 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{ElementState, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + println!("{event:?}"); + + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::MouseInput { + state: ElementState::Released, + .. + } => { + window.request_redraw(); + } + _ => (), + }, + Event::RedrawRequested(_) => { + println!("\nredrawing!\n"); + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs new file mode 100644 index 000000000..069d65d52 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs @@ -0,0 +1,45 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{thread, time}; + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +#[allow(clippy::collapsible_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + thread::spawn(move || loop { + thread::sleep(time::Duration::from_secs(1)); + window.request_redraw(); + }); + + event_loop.run(move |event, _, control_flow| { + println!("{event:?}"); + + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + _ => (), + }, + Event::RedrawRequested(_) => { + println!("\nredrawing!\n"); + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs new file mode 100644 index 000000000..e0d8c16f2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs @@ -0,0 +1,51 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let mut resizable = false; + + let window = WindowBuilder::new() + .with_title("Hit space to toggle resizability.") + .with_inner_size(LogicalSize::new(400.0, 200.0)) + .with_resizable(resizable) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::Space, + state: ElementState::Released, + .. + }, + .. + } => { + resizable = !resizable; + println!("Resizable: {resizable}"); + window.set_resizable(resizable); + } + _ => (), + }, + _ => (), + }; + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs new file mode 100644 index 000000000..2310f5c5a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs @@ -0,0 +1,56 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + dpi::PhysicalPosition, + event::{ElementState, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title("A fantastic window!"); + + println!("Ime position will system default"); + println!("Click to set ime position to cursor's"); + + let mut cursor_position = PhysicalPosition::new(0.0, 0.0); + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CursorMoved { position, .. }, + .. + } => { + cursor_position = position; + } + Event::WindowEvent { + event: + WindowEvent::MouseInput { + state: ElementState::Released, + .. + }, + .. + } => { + println!( + "Setting ime position to {}, {}", + cursor_position.x, cursor_position.y + ); + window.set_ime_position(cursor_position); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs new file mode 100644 index 000000000..c0e3284b9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs @@ -0,0 +1,50 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{event::KeyEvent, keyboard::KeyCode}; + +fn main() { + use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Theme, WindowBuilder}, + }; + + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + // .with_theme(Some(tao::window::Theme::Light)) + .build(&event_loop) + .unwrap(); + + println!("Initial theme: {:?}", window.theme()); + println!("Press D for Dark Mode"); + println!("Press L for Light Mode"); + println!("Press A for Auto Mode"); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { event, .. } = event { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: KeyEvent { physical_key, .. }, + .. + } => match physical_key { + KeyCode::KeyD => window.set_theme(Some(Theme::Dark)), + KeyCode::KeyL => window.set_theme(Some(Theme::Light)), + KeyCode::KeyA => window.set_theme(None), + _ => {} + }, + WindowEvent::ThemeChanged(theme) => { + println!("Theme is changed: {theme:?}") + } + _ => (), + } + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs new file mode 100644 index 000000000..eb3ce6f38 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs @@ -0,0 +1,43 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::time::{Duration, Instant}; + +use tao::{ + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + let timer_length = Duration::new(1, 0); + + event_loop.run(move |event, _, control_flow| { + println!("{event:?}"); + + match event { + Event::NewEvents(StartCause::Init) => { + *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length) + } + Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { + *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length); + println!("\nTimer\n"); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs new file mode 100644 index 000000000..22c99c9a1 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs @@ -0,0 +1,66 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(windows)] +use std::{num::NonZeroU32, rc::Rc}; + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_decorations(false) + .with_transparent(true) + .build(&event_loop) + .unwrap(); + + #[cfg(windows)] + let (window, _context, mut surface) = { + let window = Rc::new(window); + let context = softbuffer::Context::new(window.clone()).unwrap(); + let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); + (window, context, surface) + }; + + window.set_title("A fantastic window!"); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + println!("{event:?}"); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + #[cfg(windows)] + Event::RedrawRequested(_) => { + let (width, height) = { + let size = window.inner_size(); + (size.width, size.height) + }; + surface + .resize( + NonZeroU32::new(width).unwrap(), + NonZeroU32::new(height).unwrap(), + ) + .unwrap(); + + let mut buffer = surface.buffer_mut().unwrap(); + buffer.fill(0); + buffer.present().unwrap(); + } + + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs new file mode 100644 index 000000000..d40b3f6e3 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs @@ -0,0 +1,24 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::event_loop::EventLoop; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + let monitor = match event_loop.primary_monitor() { + Some(monitor) => monitor, + None => { + println!("No primary monitor detected."); + return; + } + }; + + println!("Listing available video modes:"); + + for mode in monitor.video_modes() { + println!("{mode}"); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs new file mode 100644 index 000000000..c4804fc4c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs @@ -0,0 +1,52 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[allow(clippy::single_match)] +fn main() { + let event_loop = EventLoop::new(); + + let mut window = Some( + WindowBuilder::new() + .with_title("A fantastic window!") + .with_inner_size(tao::dpi::LogicalSize::new(300.0, 300.0)) + .with_min_inner_size(tao::dpi::LogicalSize::new(200.0, 200.0)) + .build(&event_loop) + .unwrap(), + ); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + println!("{event:?}"); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id: _, + .. + } => { + // drop the window to fire the `Destroyed` event + window = None; + } + Event::WindowEvent { + event: WindowEvent::Destroyed, + window_id: _, + .. + } => { + *control_flow = ControlFlow::Exit; + } + Event::MainEventsCleared => { + if let Some(w) = &window { + w.request_redraw(); + } + } + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs new file mode 100644 index 000000000..1bb04c478 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs @@ -0,0 +1,176 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +// This example is used by developers to test various window functions. + +use tao::{ + dpi::{LogicalSize, PhysicalSize}, + event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, + event_loop::{ControlFlow, DeviceEventFilter, EventLoop}, + keyboard::{Key, KeyCode}, + window::{Fullscreen, WindowBuilder}, +}; + +#[allow(clippy::single_match)] +#[allow(clippy::collapsible_match)] +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + event_loop.set_device_event_filter(DeviceEventFilter::Never); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_inner_size(LogicalSize::new(100.0, 100.0)) + .build(&event_loop) + .unwrap(); + + eprintln!("debugging keys:"); + eprintln!(" (E) Enter exclusive fullscreen"); + eprintln!(" (F) Toggle borderless fullscreen"); + eprintln!(" (P) Toggle borderless fullscreen on system's preferred monitor"); + eprintln!(" (V) Toggle visibility"); + eprintln!(" (T) Toggle always on top"); + eprintln!(" (B) Toggle always on bottom"); + eprintln!(" (C) Toggle content protection"); + eprintln!(" (R) Toggle resizable"); + eprintln!(" (M) Toggle minimized"); + eprintln!(" (X) Toggle maximized"); + eprintln!(" (Q) Quit event loop"); + eprintln!(" (Shift + M) Toggle minimizable"); + eprintln!(" (Shift + X) Toggle maximizable"); + eprintln!(" (Shift + Q) Toggle closable"); + + let mut always_on_bottom = false; + let mut always_on_top = false; + let mut visible = true; + let mut content_protection = false; + let mut resizable = false; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + // This used to use the virtual key, but the new API + // only provides the `physical_key` (`Code`). + Event::DeviceEvent { + event: + DeviceEvent::Key(RawKeyEvent { + physical_key, + state: ElementState::Released, + .. + }), + .. + } => match physical_key { + KeyCode::KeyM => { + if window.is_minimized() { + window.set_minimized(false); + window.set_focus() + } + } + KeyCode::KeyV => { + if !visible { + visible = !visible; + window.set_visible(visible); + } + } + _ => (), + }, + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + }, + .. + } => match key_str { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + "e" => { + fn area(size: PhysicalSize) -> u32 { + size.width * size.height + } + + let monitor = window.current_monitor().unwrap(); + if let Some(mode) = monitor + .video_modes() + .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) + { + window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); + } else { + eprintln!("no video modes available"); + } + } + "f" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + let monitor = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); + } + } + "p" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); + } + } + "r" => { + resizable = !resizable; + window.set_resizable(resizable); + println!("Resizable: {resizable}"); + } + "m" => { + window.set_minimized(!window.is_minimized()); + } + "q" => { + *control_flow = ControlFlow::Exit; + } + "v" => { + visible = !visible; + window.set_visible(visible); + } + "x" => { + window.set_maximized(!window.is_maximized()); + } + "t" => { + always_on_top = !always_on_top; + window.set_always_on_top(always_on_top); + } + "b" => { + always_on_bottom = !always_on_bottom; + window.set_always_on_bottom(always_on_bottom); + } + "c" => { + content_protection = !content_protection; + window.set_content_protection(content_protection); + } + "M" => { + let minimizable = !window.is_minimizable(); + window.set_minimizable(minimizable); + } + "X" => { + let maximizable = !window.is_maximizable(); + window.set_maximizable(maximizable); + } + "Q" => { + let closable = !window.is_closable(); + window.set_closable(closable); + } + _ => (), + }, + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + .. + } if window_id == window.id() => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs new file mode 100644 index 000000000..bc5c4c610 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs @@ -0,0 +1,64 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +extern crate image; +use std::path::Path; + +use tao::{ + event::Event, + event_loop::{ControlFlow, EventLoop}, + window::{Icon, WindowBuilder}, +}; + +#[allow(clippy::single_match)] +fn main() { + env_logger::init(); + + // You'll have to choose an icon size at your own discretion. On Linux, the icon should be + // provided in whatever size it was naturally drawn; that is, don’t scale the image before passing + // it to Tao. But on Windows, you will have to account for screen scaling. Here we use 32px, + // since it seems to work well enough in most cases. Be careful about going too high, or + // you'll be bitten by the low-quality downscaling built into the WM. + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); + + let icon = load_icon(Path::new(path)); + + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("An iconic window!") + // At present, this only does anything on Windows and Linux, so if you want to save load + // time, you can put icon loading behind a function that returns `None` on other platforms. + .with_window_icon(Some(icon)) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { event, .. } = event { + use tao::event::WindowEvent::*; + match event { + CloseRequested => *control_flow = ControlFlow::Exit, + DroppedFile(path) => { + window.set_window_icon(Some(load_icon(&path))); + } + _ => (), + } + } + }); +} + +fn load_icon(path: &Path) -> Icon { + let (icon_rgba, icon_width, icon_height) = { + // alternatively, you can embed the icon in the binary through `include_bytes!` macro and use `image::load_from_memory` + let image = image::open(path) + .expect("Failed to open icon path") + .into_rgba8(); + let (width, height) = image.dimensions(); + let rgba = image.into_raw(); + (rgba, width, height) + }; + Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs new file mode 100644 index 000000000..c4754bb63 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs @@ -0,0 +1,59 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +// Limit this example to only compatible platforms. +#[cfg(not(target_os = "ios"))] +#[allow(clippy::single_match)] +fn main() { + use std::{thread::sleep, time::Duration}; + + use tao::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + platform::run_return::EventLoopExtRunReturn, + window::WindowBuilder, + }; + let mut event_loop = EventLoop::new(); + + env_logger::init(); + let _window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + let mut quit = false; + + while !quit { + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + if let Event::WindowEvent { event, .. } = &event { + // Print only Window events to reduce noise + println!("{event:?}"); + } + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + quit = true; + } + Event::MainEventsCleared => { + *control_flow = ControlFlow::Exit; + } + _ => (), + } + }); + + // Sleep for 1/60 second to simulate rendering + println!("rendering"); + sleep(Duration::from_millis(16)); + } +} + +#[cfg(target_os = "ios")] +fn main() { + println!("This platform doesn't support run_return."); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs new file mode 100644 index 000000000..3bc21cedc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs @@ -0,0 +1,88 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! The `Error` struct and associated types. +use std::{error, fmt}; + +use crate::platform_impl; + +/// An error whose cause it outside Tao's control. +#[non_exhaustive] +#[derive(Debug)] +pub enum ExternalError { + /// The operation is not supported by the backend. + NotSupported(NotSupportedError), + /// The OS cannot perform the operation. + Os(OsError), +} + +/// The error type for when the requested operation is not supported by the backend. +#[derive(Clone)] +pub struct NotSupportedError { + _marker: (), +} + +/// The error type for when the OS cannot perform the requested operation. +#[derive(Debug)] +pub struct OsError { + line: u32, + file: &'static str, + error: platform_impl::OsError, +} + +impl NotSupportedError { + #[inline] + #[allow(dead_code)] + pub(crate) fn new() -> NotSupportedError { + NotSupportedError { _marker: () } + } +} + +impl OsError { + #[allow(dead_code)] + pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { + OsError { line, file, error } + } +} + +#[allow(unused_macros)] +macro_rules! os_error { + ($error:expr) => {{ + crate::error::OsError::new(line!(), file!(), $error) + }}; +} + +impl fmt::Display for OsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.pad(&format!( + "os error at {}:{}: {}", + self.file, self.line, self.error + )) + } +} + +impl fmt::Display for ExternalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ExternalError::NotSupported(e) => e.fmt(f), + ExternalError::Os(e) => e.fmt(f), + } + } +} + +impl fmt::Debug for NotSupportedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_struct("NotSupportedError").finish() + } +} + +impl fmt::Display for NotSupportedError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.pad("the requested operation is not supported by Tao") + } +} + +impl error::Error for OsError {} +impl error::Error for ExternalError {} +impl error::Error for NotSupportedError {} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs new file mode 100644 index 000000000..14325dc5e --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs @@ -0,0 +1,982 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! The `Event` enum and assorted supporting types. +//! +//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get +//! processed and used to modify the program state. For more details, see the root-level documentation. +//! +//! Some of these events represent different "parts" of a traditional event-handling loop. You could +//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this: +//! +//! ```rust,ignore +//! let mut control_flow = ControlFlow::Poll; +//! let mut start_cause = StartCause::Init; +//! +//! while control_flow != ControlFlow::Exit { +//! event_handler(NewEvents(start_cause), ..., &mut control_flow); +//! +//! for e in (window events, user events, device events) { +//! event_handler(e, ..., &mut control_flow); +//! } +//! event_handler(MainEventsCleared, ..., &mut control_flow); +//! +//! for w in (redraw windows) { +//! event_handler(RedrawRequested(w), ..., &mut control_flow); +//! } +//! event_handler(RedrawEventsCleared, ..., &mut control_flow); +//! +//! start_cause = wait_if_necessary(control_flow); +//! } +//! +//! event_handler(LoopDestroyed, ..., &mut control_flow); +//! ``` +//! +//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully +//! describes what happens in what order. +//! +//! [event_loop_run]: crate::event_loop::EventLoop::run +use std::{path::PathBuf, time::Instant}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + keyboard::{self, ModifiersState}, + platform_impl, + window::{Theme, WindowId}, +}; + +/// Describes a generic event. +/// +/// See the module-level docs for more information on the event loop manages each event. +#[non_exhaustive] +#[derive(Debug, PartialEq)] +pub enum Event<'a, T: 'static> { + /// Emitted when new events arrive from the OS to be processed. + /// + /// This event type is useful as a place to put code that should be done before you start + /// processing events, such as updating frame timing information for benchmarking or checking + /// the [`StartCause`][crate::event::StartCause] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. + NewEvents(StartCause), + + /// Emitted when the OS sends an event to a tao window. + #[non_exhaustive] + WindowEvent { + window_id: WindowId, + event: WindowEvent<'a>, + }, + + /// Emitted when the OS sends an event to a device. + #[non_exhaustive] + DeviceEvent { + device_id: DeviceId, + event: DeviceEvent, + }, + + /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) + UserEvent(T), + + /// Emitted when all of the event loop's input events have been processed and redraw processing + /// is about to begin. + /// + /// This event is useful as a place to put your code that should be run after all + /// state-changing events have been handled and you want to do stuff (updating state, performing + /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws + /// graphics when something changes, it's usually better to do it in response to + /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted + /// immediately after this event. Programs that draw graphics continuously, like most games, + /// can render here unconditionally for simplicity. + MainEventsCleared, + + /// Emitted after `MainEventsCleared` when a window should be redrawn. + /// + /// This gets triggered in two scenarios: + /// - The OS has performed an operation that's invalidated the window's contents (such as + /// resizing the window). + /// - The application has explicitly requested a redraw via + /// [`Window::request_redraw`](crate::window::Window::request_redraw). + /// + /// During each iteration of the event loop, Tao will aggregate duplicate redraw requests + /// into a single event, to help avoid duplicating rendering work. + /// + /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless + /// something changes, like most non-game GUIs. + /// + /// ## Platform-specific + /// + /// - **Linux: This is triggered by `draw` signal of the gtk window. It can be used to detect if + /// the window is requested to redraw. But widgets it contains are usually not tied to its signal. + /// So if you really want to draw each component, please consider using `connect_draw` method + /// from [`WidgetExt`] directly.** + /// + /// [`WidgetExt`]: https://gtk-rs.org/gtk3-rs/stable/latest/docs/gtk/prelude/trait.WidgetExt.html + RedrawRequested(WindowId), + + /// Emitted after all `RedrawRequested` events have been processed and control flow is about to + /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted + /// immediately after `MainEventsCleared`. + /// + /// This event is useful for doing any cleanup or bookkeeping work after all the rendering + /// tasks have been completed. + RedrawEventsCleared, + + /// Emitted when the event loop is being shut down. + /// + /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that + /// gets emitted. You generally want to treat this as an "do on quit" event. + LoopDestroyed, + + /// Emitted when the app is open by external resources, like opening a file or deeplink. + Opened { urls: Vec }, + + /// ## Platform-specific + /// + /// - **macOS**: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen with return value same as hasVisibleWindows + /// - **Other**: Unsupported. + #[non_exhaustive] + Reopen { has_visible_windows: bool }, + + /// Emitted when a scene is requested by the system. + /// + /// This event is emitted when a scene is requested by the system. + /// Scenes created by `Window::new` are not emitted with this event. + /// It is also not emitted for the main scene. + #[cfg(target_os = "ios")] + SceneRequested { + /// Scene that was requested by the system. + scene: objc2::rc::Retained, + /// Options that were used to request the scene. + /// + /// This lets you determine why the scene was requested. + options: objc2::rc::Retained, + }, +} + +impl Clone for Event<'static, T> { + fn clone(&self) -> Self { + use self::Event::*; + match self { + WindowEvent { window_id, event } => WindowEvent { + window_id: *window_id, + event: event.clone(), + }, + UserEvent(event) => UserEvent(event.clone()), + DeviceEvent { device_id, event } => DeviceEvent { + device_id: *device_id, + event: event.clone(), + }, + NewEvents(cause) => NewEvents(*cause), + MainEventsCleared => MainEventsCleared, + RedrawRequested(wid) => RedrawRequested(*wid), + RedrawEventsCleared => RedrawEventsCleared, + LoopDestroyed => LoopDestroyed, + Opened { urls } => Opened { urls: urls.clone() }, + Reopen { + has_visible_windows, + } => Reopen { + has_visible_windows: *has_visible_windows, + }, + #[cfg(target_os = "ios")] + SceneRequested { scene, options } => SceneRequested { + scene: scene.clone(), + options: options.clone(), + }, + } + } +} + +impl<'a, T> Event<'a, T> { + pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { + use self::Event::*; + match self { + UserEvent(_) => Err(self), + WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), + DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), + NewEvents(cause) => Ok(NewEvents(cause)), + MainEventsCleared => Ok(MainEventsCleared), + RedrawRequested(wid) => Ok(RedrawRequested(wid)), + RedrawEventsCleared => Ok(RedrawEventsCleared), + LoopDestroyed => Ok(LoopDestroyed), + Opened { urls } => Ok(Opened { urls }), + Reopen { + has_visible_windows, + } => Ok(Reopen { + has_visible_windows, + }), + #[cfg(target_os = "ios")] + SceneRequested { scene, options } => Ok(SceneRequested { scene, options }), + } + } + + /// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime. + /// Otherwise, return `None`. + pub fn to_static(self) -> Option> { + use self::Event::*; + match self { + WindowEvent { window_id, event } => event + .to_static() + .map(|event| WindowEvent { window_id, event }), + UserEvent(event) => Some(UserEvent(event)), + DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), + NewEvents(cause) => Some(NewEvents(cause)), + MainEventsCleared => Some(MainEventsCleared), + RedrawRequested(wid) => Some(RedrawRequested(wid)), + RedrawEventsCleared => Some(RedrawEventsCleared), + LoopDestroyed => Some(LoopDestroyed), + Opened { urls } => Some(Opened { urls }), + Reopen { + has_visible_windows, + } => Some(Reopen { + has_visible_windows, + }), + #[cfg(target_os = "ios")] + SceneRequested { scene, options } => Some(SceneRequested { scene, options }), + } + } +} + +/// Describes the reason the event loop is resuming. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum StartCause { + /// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the + /// moment the timeout was requested and the requested resume time. The actual resume time is + /// guaranteed to be equal to or after the requested resume time. + #[non_exhaustive] + ResumeTimeReached { + start: Instant, + requested_resume: Instant, + }, + + /// Sent if the OS has new events to send to the window, after a wait was requested. Contains + /// the moment the wait was requested and the resume time, if requested. + #[non_exhaustive] + WaitCancelled { + start: Instant, + requested_resume: Option, + }, + + /// Sent if the event loop is being resumed after the loop's control flow was set to + /// `ControlFlow::Poll`. + Poll, + + /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. + Init, +} + +/// Describes an event from a `Window`. +#[non_exhaustive] +#[derive(Debug, PartialEq)] +pub enum WindowEvent<'a> { + /// The size of the window has changed. Contains the client area's new dimensions. + Resized(PhysicalSize), + + /// The position of the window has changed. Contains the window's new position. + /// + /// ## Platform-specific + /// + /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global coordinate system. + Moved(PhysicalPosition), + + /// The window has been requested to close. + CloseRequested, + + /// The window has been destroyed. + /// + /// ## Platform-specific + /// + /// - **Windows / Linux:** Only fired if the [`crate::window::Window`] is dropped. + /// - **macOS:** Fired if the [`crate::window::Window`] is dropped or the dock `Quit` item is clicked. + Destroyed, + + /// The window has been started. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStart` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Started, + + /// The window has been suspended. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onPause` method of the Activity. + /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Suspended, + + /// The window has been resumed. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. + /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Resumed, + + /// The window has been stopped. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStop` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Stopped, + + /// A file has been dropped into the window. + /// + /// When the user drops multiple files at once, this event will be emitted for each file + /// separately. + DroppedFile(PathBuf), + + /// A file is being hovered over the window. + /// + /// When the user hovers multiple files at once, this event will be emitted for each file + /// separately. + HoveredFile(PathBuf), + + /// A file was hovered, but has exited the window. + /// + /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were + /// hovered. + HoveredFileCancelled, + + /// The window received a unicode character. + ReceivedImeText(String), + + /// The window gained or lost focus. + /// + /// The parameter is true if the window has gained focus, and false if it has lost focus. + Focused(bool), + + /// An event from the keyboard has been received. + /// + /// ## Platform-specific + /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, + /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key + /// events which are not marked as `is_synthetic`. + #[non_exhaustive] + KeyboardInput { + device_id: DeviceId, + event: KeyEvent, + + /// If `true`, the event was generated synthetically by tao + /// in one of the following circumstances: + /// + /// * Synthetic key press events are generated for all keys pressed + /// when a window gains focus. Likewise, synthetic key release events + /// are generated for all keys pressed when a window goes out of focus. + /// ***Currently, this is only functional on Linux and Windows*** + /// + /// Otherwise, this value is always `false`. + is_synthetic: bool, + }, + + /// The keyboard modifiers have changed. + ModifiersChanged(ModifiersState), + + /// The cursor has moved on the window. + CursorMoved { + device_id: DeviceId, + + /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is + /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor + /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. + position: PhysicalPosition, + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] + modifiers: ModifiersState, + }, + + /// The cursor has entered the window. + CursorEntered { device_id: DeviceId }, + + /// The cursor has left the window. + CursorLeft { device_id: DeviceId }, + + /// A mouse wheel movement or touchpad scroll occurred. + MouseWheel { + device_id: DeviceId, + delta: MouseScrollDelta, + phase: TouchPhase, + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] + modifiers: ModifiersState, + }, + + /// An mouse button press has been received. + MouseInput { + device_id: DeviceId, + state: ElementState, + button: MouseButton, + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] + modifiers: ModifiersState, + }, + + /// Touchpad pressure event. + /// + /// At the moment, only supported on Apple forcetouch-capable macbooks. + /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad + /// is being pressed) and stage (integer representing the click level). + TouchpadPressure { + device_id: DeviceId, + pressure: f32, + stage: i64, + }, + + /// Motion on some analog axis. May report data redundant to other, more specific events. + AxisMotion { + device_id: DeviceId, + axis: AxisId, + value: f64, + }, + + /// Touch event has been received + Touch(Touch), + + /// The window's scale factor has changed. + /// + /// The following user actions can cause DPI changes: + /// + /// * Changing the display's resolution. + /// * Changing the display's scale factor (e.g. in Control Panel on Windows). + /// * Moving the window to a display with a different scale factor. + /// + /// After this event callback has been processed, the window will be resized to whatever value + /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested + /// by the OS, but it can be changed to any value. + /// + /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. + ScaleFactorChanged { + scale_factor: f64, + new_inner_size: &'a mut PhysicalSize, + }, + + /// The system window theme has changed. + /// + /// Applications might wish to react to this to change the theme of the content of the window + /// when the system changes the window theme. + /// + /// ## Platform-specific + /// + /// - **Android / iOS:** Unsupported + ThemeChanged(Theme), + + /// The window decorations has been clicked. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS / Android / iOS:** Unsupported + DecorationsClick, +} + +impl Clone for WindowEvent<'static> { + fn clone(&self) -> Self { + use self::WindowEvent::*; + match self { + Resized(size) => Resized(*size), + Moved(pos) => Moved(*pos), + CloseRequested => CloseRequested, + Destroyed => Destroyed, + Started => Started, + Suspended => Suspended, + Resumed => Resumed, + Stopped => Stopped, + DroppedFile(file) => DroppedFile(file.clone()), + HoveredFile(file) => HoveredFile(file.clone()), + HoveredFileCancelled => HoveredFileCancelled, + ReceivedImeText(c) => ReceivedImeText(c.clone()), + Focused(f) => Focused(*f), + KeyboardInput { + device_id, + event, + is_synthetic, + } => KeyboardInput { + device_id: *device_id, + event: event.clone(), + is_synthetic: *is_synthetic, + }, + + ModifiersChanged(modifiers) => ModifiersChanged(*modifiers), + #[allow(deprecated)] + CursorMoved { + device_id, + position, + modifiers, + } => CursorMoved { + device_id: *device_id, + position: *position, + modifiers: *modifiers, + }, + CursorEntered { device_id } => CursorEntered { + device_id: *device_id, + }, + CursorLeft { device_id } => CursorLeft { + device_id: *device_id, + }, + #[allow(deprecated)] + MouseWheel { + device_id, + delta, + phase, + modifiers, + } => MouseWheel { + device_id: *device_id, + delta: *delta, + phase: *phase, + modifiers: *modifiers, + }, + #[allow(deprecated)] + MouseInput { + device_id, + state, + button, + modifiers, + } => MouseInput { + device_id: *device_id, + state: *state, + button: *button, + modifiers: *modifiers, + }, + TouchpadPressure { + device_id, + pressure, + stage, + } => TouchpadPressure { + device_id: *device_id, + pressure: *pressure, + stage: *stage, + }, + AxisMotion { + device_id, + axis, + value, + } => AxisMotion { + device_id: *device_id, + axis: *axis, + value: *value, + }, + Touch(touch) => Touch(*touch), + ThemeChanged(theme) => ThemeChanged(*theme), + ScaleFactorChanged { .. } => { + unreachable!("Static event can't be about scale factor changing") + } + DecorationsClick => DecorationsClick, + } + } +} + +impl<'a> WindowEvent<'a> { + pub fn to_static(self) -> Option> { + use self::WindowEvent::*; + match self { + Resized(size) => Some(Resized(size)), + Moved(position) => Some(Moved(position)), + CloseRequested => Some(CloseRequested), + Destroyed => Some(Destroyed), + Started => Some(Started), + Suspended => Some(Suspended), + Resumed => Some(Resumed), + Stopped => Some(Stopped), + DroppedFile(file) => Some(DroppedFile(file)), + HoveredFile(file) => Some(HoveredFile(file)), + HoveredFileCancelled => Some(HoveredFileCancelled), + ReceivedImeText(c) => Some(ReceivedImeText(c)), + Focused(focused) => Some(Focused(focused)), + KeyboardInput { + device_id, + event, + is_synthetic, + } => Some(KeyboardInput { + device_id, + event, + is_synthetic, + }), + ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), + #[allow(deprecated)] + CursorMoved { + device_id, + position, + modifiers, + } => Some(CursorMoved { + device_id, + position, + modifiers, + }), + CursorEntered { device_id } => Some(CursorEntered { device_id }), + CursorLeft { device_id } => Some(CursorLeft { device_id }), + #[allow(deprecated)] + MouseWheel { + device_id, + delta, + phase, + modifiers, + } => Some(MouseWheel { + device_id, + delta, + phase, + modifiers, + }), + #[allow(deprecated)] + MouseInput { + device_id, + state, + button, + modifiers, + } => Some(MouseInput { + device_id, + state, + button, + modifiers, + }), + TouchpadPressure { + device_id, + pressure, + stage, + } => Some(TouchpadPressure { + device_id, + pressure, + stage, + }), + AxisMotion { + device_id, + axis, + value, + } => Some(AxisMotion { + device_id, + axis, + value, + }), + Touch(touch) => Some(Touch(touch)), + ThemeChanged(theme) => Some(ThemeChanged(theme)), + ScaleFactorChanged { .. } => None, + DecorationsClick => Some(DecorationsClick), + } + } +} + +/// Identifier of an input device. +/// +/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which +/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or +/// physical. Virtual devices typically aggregate inputs from multiple physical devices. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(pub(crate) platform_impl::DeviceId); + +impl DeviceId { + /// # Safety + /// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `DeviceId`. + /// + /// **Passing this into a tao function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + DeviceId(platform_impl::DeviceId::dummy()) + } +} + +/// Represents raw hardware events that are not associated with any particular window. +/// +/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person +/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because +/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs +/// may not match. +/// +/// Note that these events are delivered regardless of input focus. +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq)] +pub enum DeviceEvent { + Added, + Removed, + + /// Change in physical position of a pointing device. + /// + /// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`. + #[non_exhaustive] + MouseMotion { + /// (x, y) change in position in unspecified units. + /// + /// Different devices may use different units. + delta: (f64, f64), + }, + + /// Physical scroll event + #[non_exhaustive] + MouseWheel { + delta: MouseScrollDelta, + }, + + /// Motion on some analog axis. This event will be reported for all arbitrary input devices + /// that tao supports on this platform, including mouse devices. If the device is a mouse + /// device then this will be reported alongside the MouseMotion event. + #[non_exhaustive] + Motion { + axis: AxisId, + value: f64, + }, + + #[non_exhaustive] + Button { + button: ButtonId, + state: ElementState, + }, + + Key(RawKeyEvent), + + #[non_exhaustive] + Text { + codepoint: char, + }, +} + +/// Describes a keyboard input as a raw device event. +/// +/// Note that holding down a key may produce repeated `RawKeyEvent`s. The +/// operating system doesn't provide information whether such an event is a +/// repeat or the initial keypress. An application may emulate this by, for +/// example keeping a Map/Set of pressed keys and determining whether a keypress +/// corresponds to an already pressed key. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct RawKeyEvent { + pub physical_key: keyboard::KeyCode, + pub state: ElementState, +} + +/// Describes a keyboard input targeting a window. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEvent { + /// Represents the position of a key independent of the currently active layout. + /// + /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). + /// The most prevalent use case for this is games. For example the default keys for the player + /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys + /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" + /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) + /// + /// Note that `Fn` and `FnLock` key events are not guaranteed to be emitted by `tao`. These + /// keys are usually handled at the hardware or OS level. + pub physical_key: keyboard::KeyCode, + + /// This value is affected by all modifiers except Ctrl. + /// + /// This has two use cases: + /// - Allows querying whether the current input is a Dead key. + /// - Allows handling key-bindings on platforms which don't + /// support `key_without_modifiers`. + /// + /// ## Platform-specific + /// - **Web:** Dead keys might be reported as the real key instead + /// of `Dead` depending on the browser/OS. + pub logical_key: keyboard::Key<'static>, + + /// Contains the text produced by this keypress. + /// + /// In most cases this is identical to the content + /// of the `Character` variant of `logical_key`. + /// However, on Windows when a dead key was pressed earlier + /// but cannot be combined with the character from this + /// keypress, the produced text will consist of two characters: + /// the dead-key-character followed by the character resulting + /// from this keypress. + /// + /// An additional difference from `logical_key` is that + /// this field stores the text representation of any key + /// that has such a representation. For example when + /// `logical_key` is `Key::Enter`, this field is `Some("\r")`. + /// + /// This is `None` if the current keypress cannot + /// be interpreted as text. + /// + /// See also: `text_with_all_modifiers()` + pub text: Option<&'static str>, + + pub location: keyboard::KeyLocation, + pub state: ElementState, + pub repeat: bool, + + pub(crate) platform_specific: platform_impl::KeyEventExtra, +} + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +impl KeyEvent { + /// Identical to `KeyEvent::text` but this is affected by Ctrl. + /// + /// For example, pressing Ctrl+a produces `Some("\x01")`. + pub fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifiers + } + + /// This value ignores all modifiers including, + /// but not limited to Shift, Caps Lock, + /// and Ctrl. In most cases this means that the + /// unicode character in the resulting string is lowercase. + /// + /// This is useful for key-bindings / shortcut key combinations. + /// + /// In case `logical_key` reports `Dead`, this will still report the + /// key as `Character` according to the current keyboard layout. This value + /// cannot be `Dead`. + pub fn key_without_modifiers(&self) -> keyboard::Key<'static> { + self.platform_specific.key_without_modifiers.clone() + } +} + +#[cfg(any(target_os = "android", target_os = "ios"))] +impl KeyEvent { + /// Identical to `KeyEvent::text`. + pub fn text_with_all_modifiers(&self) -> Option<&str> { + self.text + } + + /// Identical to `KeyEvent::logical_key`. + pub fn key_without_modifiers(&self) -> keyboard::Key<'static> { + self.logical_key.clone() + } +} + +/// Describes touch-screen input state. +#[non_exhaustive] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TouchPhase { + Started, + Moved, + Ended, + Cancelled, +} + +/// Represents a touch event +/// +/// Every time the user touches the screen, a new `Start` event with an unique +/// identifier for the finger is generated. When the finger is lifted, an `End` +/// event is generated with the same finger id. +/// +/// After a `Start` event has been emitted, there may be zero or more `Move` +/// events when the finger is moved or the touch pressure changes. +/// +/// The finger id may be reused by the system after an `End` event. The user +/// should assume that a new `Start` event received with the same id has nothing +/// to do with the old finger and is a new finger. +/// +/// A `Cancelled` event is emitted when the system has canceled tracking this +/// touch, such as when the window loses focus, or on iOS if the user moves the +/// device against their face. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Touch { + pub device_id: DeviceId, + pub phase: TouchPhase, + pub location: PhysicalPosition, + /// Describes how hard the screen was pressed. May be `None` if the platform + /// does not support pressure sensitivity. + /// + /// ## Platform-specific + /// + /// - Only available on **iOS** 9.0+ and **Windows** 8+. + pub force: Option, + /// Unique identifier of a finger. + pub id: u64, +} + +/// Describes the force of a touch event +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Force { + /// On iOS, the force is calibrated so that the same number corresponds to + /// roughly the same amount of pressure on the screen regardless of the + /// device. + #[non_exhaustive] + Calibrated { + /// The force of the touch, where a value of 1.0 represents the force of + /// an average touch (predetermined by the system, not user-specific). + /// + /// The force reported by Apple Pencil is measured along the axis of the + /// pencil. If you want a force perpendicular to the device, you need to + /// calculate this value using the `altitude_angle` value. + force: f64, + /// The maximum possible force for a touch. + /// + /// The value of this field is sufficiently high to provide a wide + /// dynamic range for values of the `force` field. + max_possible_force: f64, + /// The altitude (in radians) of the stylus. + /// + /// A value of 0 radians indicates that the stylus is parallel to the + /// surface. The value of this property is Pi/2 when the stylus is + /// perpendicular to the surface. + altitude_angle: Option, + }, + /// If the platform reports the force as normalized, we have no way of + /// knowing how much pressure 1.0 corresponds to - we know it's the maximum + /// amount of force, but as to how much force, you might either have to + /// press really really hard, or not hard at all, depending on the device. + Normalized(f64), +} + +impl Force { + /// Returns the force normalized to the range between 0.0 and 1.0 inclusive. + /// Instead of normalizing the force, you should prefer to handle + /// `Force::Calibrated` so that the amount of force the user has to apply is + /// consistent across devices. + pub fn normalized(&self) -> f64 { + match self { + Force::Calibrated { + force, + max_possible_force, + altitude_angle, + } => { + let force = match altitude_angle { + Some(altitude_angle) => force / altitude_angle.sin(), + None => *force, + }; + force / max_possible_force + } + Force::Normalized(force) => *force, + } + } +} + +/// Identifier for a specific analog axis on some device. +pub type AxisId = u32; + +/// Identifier for a specific button on some device. +pub type ButtonId = u32; + +/// Describes the input state of a key. +#[non_exhaustive] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ElementState { + Pressed, + Released, +} + +/// Describes a button of a mouse controller. +#[non_exhaustive] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum MouseButton { + Left, + Right, + Middle, + Other(u16), +} + +/// Describes a difference in the mouse scroll wheel state. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum MouseScrollDelta { + /// Amount in lines or rows to scroll in the horizontal + /// and vertical directions. + /// + /// Positive values indicate movement forward + /// (away from the user) or rightwards. + LineDelta(f32, f32), + /// Amount in pixels to scroll in the horizontal and + /// vertical direction. + /// + /// Scroll events are expressed as a PixelDelta if + /// supported by the device (eg. a touchpad) and + /// platform. + PixelDelta(PhysicalPosition), +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs new file mode 100644 index 000000000..eb73dbbba --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs @@ -0,0 +1,416 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`. +//! +//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy] +//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method. +//! +//! See the root-level documentation for information on how to create and use an event loop to +//! handle events. +//! +//! [create_proxy]: crate::event_loop::EventLoop::create_proxy +//! [event_loop_proxy]: crate::event_loop::EventLoopProxy +//! [send_event]: crate::event_loop::EventLoopProxy::send_event +use std::{error, fmt, marker::PhantomData, ops::Deref, time::Instant}; + +use crate::{ + dpi::PhysicalPosition, + error::ExternalError, + event::Event, + monitor::MonitorHandle, + platform_impl, + window::{ProgressBarState, Theme}, +}; + +/// Provides a way to retrieve events from the system and from the windows that were registered to +/// the events loop. +/// +/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()` +/// initializes everything that will be required to create windows. +/// +/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs. +/// +/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic +/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the +/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the +/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread. +/// +pub struct EventLoop { + pub(crate) event_loop: platform_impl::EventLoop, + pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync +} + +/// Target that associates windows with an `EventLoop`. +/// +/// This type exists to allow you to create new windows while Tao executes +/// your callback. `EventLoop` will coerce into this type (`impl Deref for +/// EventLoop`), so functions that take this as a parameter can also take +/// `&EventLoop`. +#[derive(Clone)] +pub struct EventLoopWindowTarget { + pub(crate) p: platform_impl::EventLoopWindowTarget, + pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync +} + +impl fmt::Debug for EventLoop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("EventLoop { .. }") + } +} + +impl fmt::Debug for EventLoopWindowTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("EventLoopWindowTarget { .. }") + } +} + +/// Object that allows building the event loop. +/// +/// This is used to make specifying options that affect the whole application +/// easier. But note that constructing multiple event loops is not supported. +#[derive(Default)] +pub struct EventLoopBuilder { + pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, + _p: PhantomData, +} +impl EventLoopBuilder<()> { + /// Start building a new event loop. + #[inline] + pub fn new() -> Self { + Self::with_user_event() + } +} +impl EventLoopBuilder { + /// Start building a new event loop, with the given type as the user event + /// type. + #[inline] + pub fn with_user_event() -> Self { + Self { + platform_specific: Default::default(), + _p: PhantomData, + } + } + /// Builds a new event loop. + /// + /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.*** + /// Attempting to create the event loop on a different thread will panic. This restriction isn't + /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when + /// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed + /// in the relevant `platform` module if the target platform supports creating an event loop on + /// any thread. + /// + /// Usage will result in display backend initialisation, this can be controlled on linux + /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. + /// If it is not set, winit will try to connect to a wayland connection, and if it fails will + /// fallback on x11. If this variable is set with any other value, winit will panic. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn build(&mut self) -> EventLoop { + EventLoop { + #[cfg_attr( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + allow(clippy::unnecessary_mut_passed) + )] + event_loop: platform_impl::EventLoop::new(&mut self.platform_specific), + _marker: PhantomData, + } + } +} + +/// Set by the user callback given to the `EventLoop::run` method. +/// +/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared] +/// is emitted. Defaults to `Poll`. +/// +/// ## Persistency +/// Almost every change is persistent between multiple calls to the event loop closure within a +/// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset. +/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will +/// reset the control flow to `Poll`. +/// +/// [events_cleared]: crate::event::Event::RedrawEventsCleared +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ControlFlow { + /// When the current loop iteration finishes, immediately begin a new iteration regardless of + /// whether or not new events are available to process. + Poll, + /// When the current loop iteration finishes, suspend the thread until another event arrives. + Wait, + /// When the current loop iteration finishes, suspend the thread until either another event + /// arrives or the given time is reached. + WaitUntil(Instant), + /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set, + /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will + /// result in the `control_flow` parameter being reset to `ExitWithCode`. + /// + /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this + /// with exit code 0. + /// + /// ## Platform-specific + /// + /// - **Android / iOS / WASM**: The supplied exit code is unused. + /// - **Unix**: On most Unix-like platforms, only the 8 least significant bits will be used, + /// which can cause surprises with negative exit values (`-42` would end up as `214`). See + /// [`std::process::exit`]. + /// + /// [`Exit`]: ControlFlow::Exit + ExitWithCode(i32), +} + +impl ControlFlow { + /// Alias for [`ExitWithCode`]`(0)`. + /// + /// [`ExitWithCode`]: ControlFlow::ExitWithCode + #[allow(non_upper_case_globals)] + pub const Exit: Self = Self::ExitWithCode(0); +} + +impl Default for ControlFlow { + #[inline(always)] + fn default() -> ControlFlow { + ControlFlow::Poll + } +} + +impl EventLoop<()> { + /// Alias for [`EventLoopBuilder::new().build()`]. + /// + /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build + #[inline] + pub fn new() -> EventLoop<()> { + EventLoopBuilder::new().build() + } +} + +impl Default for EventLoop<()> { + fn default() -> Self { + Self::new() + } +} + +impl EventLoop { + /// Hijacks the calling thread and initializes the tao event loop with the provided + /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to + /// access any data from the calling context. + /// + /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the + /// event loop's behavior. + /// + /// Any values not passed to this function will *not* be dropped. + /// + /// ## Platform-specific + /// + /// - **Unix**: The program terminates with exit code 1 if the display server + /// disconnects. + /// + /// [`ControlFlow`]: crate::event_loop::ControlFlow + #[inline] + pub fn run(self, event_handler: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), + { + self.event_loop.run(event_handler) + } + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + event_loop_proxy: self.event_loop.create_proxy(), + } + } +} + +impl Deref for EventLoop { + type Target = EventLoopWindowTarget; + fn deref(&self) -> &EventLoopWindowTarget { + self.event_loop.window_target() + } +} + +impl EventLoopWindowTarget { + /// Returns the list of all the monitors available on the system. + #[inline] + pub fn available_monitors(&self) -> impl Iterator { + self + .p + .available_monitors() + .into_iter() + .map(|inner| MonitorHandle { inner }) + } + + /// Returns the primary monitor of the system. + /// + /// Returns `None` if it can't identify any monitor as a primary one. + #[inline] + pub fn primary_monitor(&self) -> Option { + self.p.primary_monitor() + } + + /// Returns the monitor that contains the given point. + /// + /// ## Platform-specific: + /// + /// - **Android / iOS:** Unsupported. + #[inline] + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + self + .p + .monitor_from_point(x, y) + .map(|inner| MonitorHandle { inner }) + } + + /// Change [`DeviceEvent`] filter mode. + /// + /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, tao + /// will ignore them by default for unfocused windows. This method allows changing + /// this filter at runtime to explicitly capture them again. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS / iOS / Android:** Unsupported. + /// + /// [`DeviceEvent`]: crate::event::DeviceEvent + pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { + #[cfg(target_os = "windows")] + self.p.set_device_event_filter(_filter); + } + + /// Returns the current cursor position + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`. + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + self.p.cursor_position() + } + + /// Sets the progress bar state + /// + /// ## Platform-specific + /// + /// - **Windows:** Unsupported. Use the Progress Bar Function Available in Window (Windows can have different progress bars for different window) + /// - **Linux:** Only supported desktop environments with `libunity` (e.g. GNOME). + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_progress_bar(&self, _progress: ProgressBarState) { + #[cfg(any(target_os = "linux", target_os = "macos"))] + self.p.set_progress_bar(_progress) + } + + /// Sets the theme for the application. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_theme(&self, _theme: Option) { + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "macos", + ))] + self.p.set_theme(_theme) + } +} + +#[cfg(feature = "rwh_05")] +unsafe impl rwh_05::HasRawDisplayHandle for EventLoop { + fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { + rwh_05::HasRawDisplayHandle::raw_display_handle(&**self) + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasDisplayHandle for EventLoop { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + rwh_06::HasDisplayHandle::display_handle(&**self) + } +} + +#[cfg(feature = "rwh_05")] +unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget { + fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { + self.p.raw_display_handle_rwh_05() + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasDisplayHandle for EventLoopWindowTarget { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + let raw = self.p.raw_display_handle_rwh_06()?; + // SAFETY: The display will never be deallocated while the event loop is alive. + Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) + } +} + +/// Used to send custom events to `EventLoop`. +pub struct EventLoopProxy { + event_loop_proxy: platform_impl::EventLoopProxy, +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + event_loop_proxy: self.event_loop_proxy.clone(), + } + } +} + +impl EventLoopProxy { + /// Send an event to the `EventLoop` from which this proxy was created. This emits a + /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this + /// function. + /// + /// Returns an `Err` if the associated `EventLoop` no longer exists. + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.event_loop_proxy.send_event(event) + } +} + +impl fmt::Debug for EventLoopProxy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("EventLoopProxy { .. }") + } +} + +/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that +/// no longer exists. Contains the original event given to `send_event`. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct EventLoopClosed(pub T); + +impl fmt::Display for EventLoopClosed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Tried to wake up a closed `EventLoop`") + } +} + +impl error::Error for EventLoopClosed {} + +/// Filter controlling the propagation of device events. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub enum DeviceEventFilter { + /// Always filter out device events. + Always, + /// Filter out device events while the window is not focused. + #[default] + Unfocused, + /// Report all device events regardless of window focus. + Never, +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs new file mode 100644 index 000000000..8827e77bf --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs @@ -0,0 +1,169 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::platform_impl::PlatformIcon; +use std::{error::Error, fmt, io, mem}; + +#[repr(C)] +#[derive(Debug)] +pub(crate) struct Pixel { + pub(crate) r: u8, + pub(crate) g: u8, + pub(crate) b: u8, + pub(crate) a: u8, +} + +pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); + +#[non_exhaustive] +#[derive(Debug)] +/// An error produced when using `Icon::from_rgba` with invalid arguments. +pub enum BadIcon { + /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be + /// safely interpreted as 32bpp RGBA pixels. + #[non_exhaustive] + ByteCountNotDivisibleBy4 { byte_count: usize }, + /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. + /// At least one of your arguments is incorrect. + #[non_exhaustive] + DimensionsVsPixelCount { + width: u32, + height: u32, + width_x_height: usize, + pixel_count: usize, + }, + /// Produced when the provided icon width or height is equal to zero. + #[non_exhaustive] + DimensionsZero { width: u32, height: u32 }, + /// Produced when the provided icon width or height is equal to zero. + #[non_exhaustive] + DimensionsMultiplyOverflow { width: u32, height: u32 }, + /// Produced when underlying OS functionality failed to create the icon + OsError(io::Error), +} + +impl fmt::Display for BadIcon { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f, + "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", + ), + BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height, + pixel_count, + } => write!(f, + "The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.", + ), + BadIcon::DimensionsZero { + width, + height, + } => write!(f, + "The specified dimensions ({width:?}x{height:?}) must be greater than zero." + ), + BadIcon::DimensionsMultiplyOverflow { + width, + height, + } => write!(f, + "The specified dimensions multiplication has overflowed ({width:?}x{height:?})." + ), + BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"), + } + } +} + +impl Error for BadIcon { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct RgbaIcon { + pub(crate) rgba: Vec, + pub(crate) width: u32, + pub(crate) height: u32, +} + +/// For platforms which don't have window icons (e.g. web) +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NoIcon; + +#[allow(dead_code)] // These are not used on every platform +mod constructors { + use super::*; + + impl RgbaIcon { + /// Creates an `Icon` from 32bpp RGBA data. + /// + /// The length of `rgba` must be divisible by 4, and `width * height` must equal + /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + if width == 0 || height == 0 { + return Err(BadIcon::DimensionsZero { width, height }); + } + + if rgba.len() % PIXEL_SIZE != 0 { + return Err(BadIcon::ByteCountNotDivisibleBy4 { + byte_count: rgba.len(), + }); + } + let width_usize = width as usize; + let height_usize = height as usize; + let width_x_height = match width_usize.checked_mul(height_usize) { + Some(v) => v, + None => return Err(BadIcon::DimensionsMultiplyOverflow { width, height }), + }; + + let pixel_count = rgba.len() / PIXEL_SIZE; + if pixel_count != width_x_height { + Err(BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height, + pixel_count, + }) + } else { + Ok(RgbaIcon { + rgba, + width, + height, + }) + } + } + } + + impl NoIcon { + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + // Create the rgba icon anyway to validate the input + let _ = RgbaIcon::from_rgba(rgba, width, height)?; + Ok(NoIcon) + } + } +} + +/// An icon used for the window titlebar, taskbar, etc. +#[derive(Clone)] +pub struct Icon { + pub(crate) inner: PlatformIcon, +} + +impl fmt::Debug for Icon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.inner, formatter) + } +} + +impl Icon { + /// Creates an `Icon` from 32bpp RGBA data. + /// + /// The length of `rgba` must be divisible by 4, and `width * height` must equal + /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + Ok(Icon { + inner: PlatformIcon::from_rgba(rgba, width, height)?, + }) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs new file mode 100644 index 000000000..8628f0f67 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs @@ -0,0 +1,1596 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! **UNSTABLE** -- Types related to the keyboard. + +// This file contains a substantial portion of the UI Events Specification by the W3C. In +// particular, the variant names within `Key` and `KeyCode` and their documentation are modified +// versions of contents of the aforementioned specification. +// +// The original documents are: +// +// ### For `Key` +// UI Events KeyboardEvent key Values +// https://www.w3.org/TR/2017/CR-uievents-key-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// ### For `KeyCode` +// UI Events KeyboardEvent code Values +// https://www.w3.org/TR/2017/CR-uievents-code-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// These documents were used under the terms of the following license. This W3C license as well as +// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the +// documentation attached to their variants. + +// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- +// +// License +// +// By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, +// and will comply with the following terms and conditions. +// +// Permission to copy, modify, and distribute this work, with or without modification, for any +// purpose and without fee or royalty is hereby granted, provided that you include the following on +// ALL copies of the work or portions thereof, including modifications: +// +// - The full text of this NOTICE in a location viewable to users of the redistributed or derivative +// work. +// - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none +// exist, the W3C Software and Document Short Notice should be included. +// - Notice of any changes or modifications, through a copyright statement on the new code or +// document such as "This software or document includes material copied from or derived from +// [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." +// +// Disclaimers +// +// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR +// ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD +// PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. +// +// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES +// ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. +// +// The name and trademarks of copyright holders may NOT be used in advertising or publicity +// pertaining to the work without specific, written prior permission. Title to copyright in this +// work will at all times remain with copyright holders. +// +// --------- END OF W3C LICENSE -------------------------------------------------------------------- + +// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- +// +// tao: https://github.com/tauri-apps/tao +// +// Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European +// Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights +// Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// [1] http://www.w3.org/Consortium/Legal/copyright-software +// +// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- + +use std::{fmt, str::FromStr}; + +use crate::{ + error::OsError, + platform_impl::{ + keycode_from_scancode as platform_keycode_from_scancode, + keycode_to_scancode as platform_keycode_to_scancode, + }, +}; + +impl ModifiersState { + /// Returns `true` if the shift key is pressed. + pub fn shift_key(&self) -> bool { + self.intersects(Self::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn control_key(&self) -> bool { + self.intersects(Self::CONTROL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt_key(&self) -> bool { + self.intersects(Self::ALT) + } + /// Returns `true` if the super key is pressed. + pub fn super_key(&self) -> bool { + self.intersects(Self::SUPER) + } +} + +bitflags! { + /// Represents the current state of the keyboard modifiers + /// + /// Each flag represents a modifier and is set if this modifier is active. + #[derive(Clone, Copy, Default, Debug, PartialEq)] + pub struct ModifiersState: u32 { + // left and right modifiers are currently commented out, but we should be able to support + // them in a future release + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + /// The "control" key. + const CONTROL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const SUPER = 0b100 << 9; + // const LSUPER = 0b010 << 9; + // const RSUPER = 0b001 << 9; + } +} + +#[cfg(feature = "serde")] +mod modifiers_serde { + use super::ModifiersState; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Default, Serialize, Deserialize)] + #[serde(default)] + #[serde(rename = "ModifiersState")] + pub struct ModifiersStateSerialize { + pub shift_key: bool, + pub control_key: bool, + pub alt_key: bool, + pub super_key: bool, + } + + impl Serialize for ModifiersState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = ModifiersStateSerialize { + shift_key: self.shift_key(), + control_key: self.control_key(), + alt_key: self.alt_key(), + super_key: self.super_key(), + }; + s.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for ModifiersState { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let ModifiersStateSerialize { + shift_key, + control_key, + alt_key, + super_key, + } = ModifiersStateSerialize::deserialize(deserializer)?; + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, shift_key); + m.set(ModifiersState::CONTROL, control_key); + m.set(ModifiersState::ALT, alt_key); + m.set(ModifiersState::SUPER, super_key); + Ok(m) + } + } +} + +/// Contains the platform-native physical key identifier (aka scancode) +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NativeKeyCode { + Unidentified, + Windows(u16), + MacOS(u16), + Gtk(u16), + + /// This is the android "key code" of the event as returned by + /// `KeyEvent.getKeyCode()` + Android(i32), +} + +/// Represents the code of a physical key. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few +/// exceptions: +/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and +/// "SuperRight" here. +/// - The key that the specification calls "Super" is reported as `Unidentified` here. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. +/// +/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyCode { + /// This variant is used when the key cannot be translated to any + /// other variant. + /// + /// The native scancode is provided (if available) in order + /// to allow the user to specify keybindings for keys which + /// are not defined by this API. + Unidentified(NativeKeyCode), + /// ` on a US keyboard. This is also called a backtick or grave. + /// This is the 半角/全角/漢字 + /// (hankaku/zenkaku/kanji) key on Japanese keyboards + Backquote, + /// Used for both the US \\ (on the 101-key layout) and also for the key + /// located between the " and Enter keys on row C of the 102-, + /// 104- and 106-key layouts. + /// Labeled # on a UK (102) keyboard. + Backslash, + /// [ on a US keyboard. + BracketLeft, + /// ] on a US keyboard. + BracketRight, + /// , on a US keyboard. + Comma, + /// 0 on a US keyboard. + Digit0, + /// 1 on a US keyboard. + Digit1, + /// 2 on a US keyboard. + Digit2, + /// 3 on a US keyboard. + Digit3, + /// 4 on a US keyboard. + Digit4, + /// 5 on a US keyboard. + Digit5, + /// 6 on a US keyboard. + Digit6, + /// 7 on a US keyboard. + Digit7, + /// 8 on a US keyboard. + Digit8, + /// 9 on a US keyboard. + Digit9, + /// = on a US keyboard. + Equal, + /// Located between the left Shift and Z keys. + /// Labeled \\ on a UK keyboard. + IntlBackslash, + /// Located between the / and right Shift keys. + /// Labeled \\ (ro) on a Japanese keyboard. + IntlRo, + /// Located between the = and Backspace keys. + /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a + /// Russian keyboard. + IntlYen, + /// a on a US keyboard. + /// Labeled q on an AZERTY (e.g., French) keyboard. + KeyA, + /// b on a US keyboard. + KeyB, + /// c on a US keyboard. + KeyC, + /// d on a US keyboard. + KeyD, + /// e on a US keyboard. + KeyE, + /// f on a US keyboard. + KeyF, + /// g on a US keyboard. + KeyG, + /// h on a US keyboard. + KeyH, + /// i on a US keyboard. + KeyI, + /// j on a US keyboard. + KeyJ, + /// k on a US keyboard. + KeyK, + /// l on a US keyboard. + KeyL, + /// m on a US keyboard. + KeyM, + /// n on a US keyboard. + KeyN, + /// o on a US keyboard. + KeyO, + /// p on a US keyboard. + KeyP, + /// q on a US keyboard. + /// Labeled a on an AZERTY (e.g., French) keyboard. + KeyQ, + /// r on a US keyboard. + KeyR, + /// s on a US keyboard. + KeyS, + /// t on a US keyboard. + KeyT, + /// u on a US keyboard. + KeyU, + /// v on a US keyboard. + KeyV, + /// w on a US keyboard. + /// Labeled z on an AZERTY (e.g., French) keyboard. + KeyW, + /// x on a US keyboard. + KeyX, + /// y on a US keyboard. + /// Labeled z on a QWERTZ (e.g., German) keyboard. + KeyY, + /// z on a US keyboard. + /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a + /// QWERTZ (e.g., German) keyboard. + KeyZ, + /// - on a US keyboard. + Minus, + /// Shift+= on a US keyboard. + Plus, + /// . on a US keyboard. + Period, + /// ' on a US keyboard. + Quote, + /// ; on a US keyboard. + Semicolon, + /// / on a US keyboard. + Slash, + /// Alt, Option, or ⌥. + AltLeft, + /// Alt, Option, or ⌥. + /// This is labeled AltGr on many keyboard layouts. + AltRight, + /// Backspace or ⌫. + /// Labeled Delete on Apple keyboards. + Backspace, + /// CapsLock or ⇪ + CapsLock, + /// The application context menu key, which is typically found between the right + /// Super key and the right Control key. + ContextMenu, + /// Control or ⌃ + ControlLeft, + /// Control or ⌃ + ControlRight, + /// Enter or ↵. Labeled Return on Apple keyboards. + Enter, + /// The Windows, ⌘, Command, or other OS symbol key. + SuperLeft, + /// The Windows, ⌘, Command, or other OS symbol key. + SuperRight, + /// Shift or ⇧ + ShiftLeft, + /// Shift or ⇧ + ShiftRight, + /// (space) + Space, + /// Tab or ⇥ + Tab, + /// Japanese: 変 (henkan) + Convert, + /// Japanese: カタカナ/ひらがな/ローマ字 (katakana/hiragana/romaji) + KanaMode, + /// Korean: HangulMode 한/영 (han/yeong) + /// + /// Japanese (Mac keyboard): か (kana) + Lang1, + /// Korean: Hanja 한 (hanja) + /// + /// Japanese (Mac keyboard): 英 (eisu) + Lang2, + /// Japanese (word-processing keyboard): Katakana + Lang3, + /// Japanese (word-processing keyboard): Hiragana + Lang4, + /// Japanese (word-processing keyboard): Zenkaku/Hankaku + Lang5, + /// Japanese: 無変換 (muhenkan) + NonConvert, + /// ⌦. The forward delete key. + /// Note that on Apple keyboards, the key labelled Delete on the main part of + /// the keyboard is encoded as [`Backspace`]. + /// + /// [`Backspace`]: Self::Backspace + Delete, + /// Page Down, End, or ↘ + End, + /// Help. Not present on standard PC keyboards. + Help, + /// Home or ↖ + Home, + /// Insert or Ins. Not present on Apple keyboards. + Insert, + /// Page Down, PgDn, or ⇟ + PageDown, + /// Page Up, PgUp, or ⇞ + PageUp, + /// ↓ + ArrowDown, + /// ← + ArrowLeft, + /// → + ArrowRight, + /// ↑ + ArrowUp, + /// On the Mac, this is used for the numpad Clear key. + NumLock, + /// 0 Ins on a keyboard. 0 on a phone or remote control + Numpad0, + /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote control + Numpad1, + /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control + Numpad2, + /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control + Numpad3, + /// 4 ← on a keyboard. 4 GHI on a phone or remote control + Numpad4, + /// 5 on a keyboard. 5 JKL on a phone or remote control + Numpad5, + /// 6 → on a keyboard. 6 MNO on a phone or remote control + Numpad6, + /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone + /// or remote control + Numpad7, + /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control + Numpad8, + /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone + /// or remote control + Numpad9, + /// + + NumpadAdd, + /// Found on the Microsoft Natural Keyboard. + NumpadBackspace, + /// C or A (All Clear). Also for use with numpads that have a + /// Clear key that is separate from the NumLock key. On the Mac, the + /// numpad Clear key is encoded as [`NumLock`]. + /// + /// [`NumLock`]: Self::NumLock + NumpadClear, + /// C (Clear Entry) + NumpadClearEntry, + /// , (thousands separator). For locales where the thousands separator + /// is a "." (e.g., Brazil), this key may generate a .. + NumpadComma, + /// . Del. For locales where the decimal separator is "," (e.g., + /// Brazil), this key may generate a ,. + NumpadDecimal, + /// / + NumpadDivide, + NumpadEnter, + /// = + NumpadEqual, + /// # on a phone or remote control device. This key is typically found + /// below the 9 key and to the right of the 0 key. + NumpadHash, + /// M Add current entry to the value stored in memory. + NumpadMemoryAdd, + /// M Clear the value stored in memory. + NumpadMemoryClear, + /// M Replace the current entry with the value stored in memory. + NumpadMemoryRecall, + /// M Replace the value stored in memory with the current entry. + NumpadMemoryStore, + /// M Subtract current entry from the value stored in memory. + NumpadMemorySubtract, + /// * on a keyboard. For use with numpads that provide mathematical + /// operations (+, - * and /). + /// + /// Use `NumpadStar` for the * key on phones and remote controls. + NumpadMultiply, + /// ( Found on the Microsoft Natural Keyboard. + NumpadParenLeft, + /// ) Found on the Microsoft Natural Keyboard. + NumpadParenRight, + /// * on a phone or remote control device. + /// + /// This key is typically found below the 7 key and to the left of + /// the 0 key. + /// + /// Use "NumpadMultiply" for the * key on + /// numeric keypads. + NumpadStar, + /// - + NumpadSubtract, + /// Esc or ⎋ + Escape, + /// Fn This is typically a hardware key that does not generate a separate code. + Fn, + /// FLock or FnLock. Function Lock key. Found on the Microsoft + /// Natural Keyboard. + FnLock, + /// PrtScr SysRq or Print Screen + PrintScreen, + /// Scroll Lock + ScrollLock, + /// Pause Break + Pause, + /// Some laptops place this key to the left of the ↑ key. + /// + /// This also the "back" button (triangle) on Android. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the ↑ key. + BrowserForward, + /// The "home" button on Android. + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + /// Eject or ⏏. This key is placed in the function section on some Apple + /// keyboards. + Eject, + /// Sometimes labelled My Computer on the keyboard + LaunchApp1, + /// Sometimes labelled Calculator on the keyboard + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + /// This key is placed in the function section on some Apple keyboards, replacing the + /// Eject key. + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + Hyper, + Turbo, + Abort, + Resume, + Suspend, + /// Found on Sun’s USB keyboard. + Again, + /// Found on Sun’s USB keyboard. + Copy, + /// Found on Sun’s USB keyboard. + Cut, + /// Found on Sun’s USB keyboard. + Find, + /// Found on Sun’s USB keyboard. + Open, + /// Found on Sun’s USB keyboard. + Paste, + /// Found on Sun’s USB keyboard. + Props, + /// Found on Sun’s USB keyboard. + Select, + /// Found on Sun’s USB keyboard. + Undo, + /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. + Hiragana, + /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. + Katakana, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl KeyCode { + /// Return platform specific scancode. + pub fn to_scancode(self) -> Option { + platform_keycode_to_scancode(self) + } + /// Return `KeyCode` from platform scancode. + pub fn from_scancode(scancode: u32) -> KeyCode { + platform_keycode_from_scancode(scancode) + } +} + +impl FromStr for KeyCode { + type Err = OsError; + fn from_str(accelerator_string: &str) -> Result { + let keycode = match accelerator_string.to_uppercase().as_str() { + "`" | "BACKQUOTE" => KeyCode::Backquote, + "BACKSLASH" => KeyCode::Backslash, + "[" | "BRACKETLEFT" => KeyCode::BracketLeft, + "]" | "BRACKETRIGHT" => KeyCode::BracketRight, + "," | "COMMA" => KeyCode::Comma, + "0" => KeyCode::Digit0, + "1" => KeyCode::Digit1, + "2" => KeyCode::Digit2, + "3" => KeyCode::Digit3, + "4" => KeyCode::Digit4, + "5" => KeyCode::Digit5, + "6" => KeyCode::Digit6, + "7" => KeyCode::Digit7, + "8" => KeyCode::Digit8, + "9" => KeyCode::Digit9, + "NUM0" | "NUMPAD0" => KeyCode::Numpad0, + "NUM1" | "NUMPAD1" => KeyCode::Numpad1, + "NUM2" | "NUMPAD2" => KeyCode::Numpad2, + "NUM3" | "NUMPAD3" => KeyCode::Numpad3, + "NUM4" | "NUMPAD4" => KeyCode::Numpad4, + "NUM5" | "NUMPAD5" => KeyCode::Numpad5, + "NUM6" | "NUMPAD6" => KeyCode::Numpad6, + "NUM7" | "NUMPAD7" => KeyCode::Numpad7, + "NUM8" | "NUMPAD8" => KeyCode::Numpad8, + "NUM9" | "NUMPAD9" => KeyCode::Numpad9, + "=" => KeyCode::Equal, + "-" => KeyCode::Minus, + "PLUS" => KeyCode::Plus, + "." | "PERIOD" => KeyCode::Period, + "'" | "QUOTE" => KeyCode::Quote, + "\\" => KeyCode::IntlBackslash, + "A" => KeyCode::KeyA, + "B" => KeyCode::KeyB, + "C" => KeyCode::KeyC, + "D" => KeyCode::KeyD, + "E" => KeyCode::KeyE, + "F" => KeyCode::KeyF, + "G" => KeyCode::KeyG, + "H" => KeyCode::KeyH, + "I" => KeyCode::KeyI, + "J" => KeyCode::KeyJ, + "K" => KeyCode::KeyK, + "L" => KeyCode::KeyL, + "M" => KeyCode::KeyM, + "N" => KeyCode::KeyN, + "O" => KeyCode::KeyO, + "P" => KeyCode::KeyP, + "Q" => KeyCode::KeyQ, + "R" => KeyCode::KeyR, + "S" => KeyCode::KeyS, + "T" => KeyCode::KeyT, + "U" => KeyCode::KeyU, + "V" => KeyCode::KeyV, + "W" => KeyCode::KeyW, + "X" => KeyCode::KeyX, + "Y" => KeyCode::KeyY, + "Z" => KeyCode::KeyZ, + + ";" | "SEMICOLON" => KeyCode::Semicolon, + "/" | "SLASH" => KeyCode::Slash, + "BACKSPACE" => KeyCode::Backspace, + "CAPSLOCK" => KeyCode::CapsLock, + "CONTEXTMENU" => KeyCode::ContextMenu, + "ENTER" => KeyCode::Enter, + "SPACE" => KeyCode::Space, + "TAB" => KeyCode::Tab, + "CONVERT" => KeyCode::Convert, + + "DELETE" => KeyCode::Delete, + "END" => KeyCode::End, + "HELP" => KeyCode::Help, + "HOME" => KeyCode::Home, + "PAGEDOWN" => KeyCode::PageDown, + "PAGEUP" => KeyCode::PageUp, + + "DOWN" | "ARROWDOWN" => KeyCode::ArrowDown, + "UP" | "ARROWUP" => KeyCode::ArrowUp, + "LEFT" | "ARROWLEFT" => KeyCode::ArrowLeft, + "RIGHT" | "ARROWRIGHT" => KeyCode::ArrowRight, + + "NUMLOCK" => KeyCode::NumLock, + "NUMADD" | "NUMPADADD" => KeyCode::NumpadAdd, + "NUMBACKSPACE" | "NUMPADBACKSPACE" => KeyCode::NumpadBackspace, + "NUMCLEAR" | "NUMPADCLEAR" => KeyCode::NumpadClear, + "NUMCOMMA" | "NUMPADCOMMA" => KeyCode::NumpadComma, + "NUMDIVIDE" | "NUMPADDIVIDE" => KeyCode::NumpadDivide, + "NUMSUBSTRACT" | "NUMPADSUBSTRACT" => KeyCode::NumpadSubtract, + "NUMENTER" | "NUMPADENTER" => KeyCode::NumpadEnter, + + "ESC" | "ESCAPE" => KeyCode::Escape, + "FN" => KeyCode::Fn, + "FNLOCK" => KeyCode::FnLock, + "PRINTSCREEN" => KeyCode::PrintScreen, + "SCROLLLOCK" => KeyCode::ScrollLock, + + "PAUSE" => KeyCode::Pause, + + "VOLUMEMUTE" => KeyCode::AudioVolumeMute, + "VOLUMEDOWN" => KeyCode::AudioVolumeDown, + "VOLUMEUP" => KeyCode::AudioVolumeUp, + "MEDIANEXTTRACK" => KeyCode::MediaTrackNext, + "MEDIAPREVIOUSTRACK" => KeyCode::MediaTrackPrevious, + "MEDIAPLAYPAUSE" => KeyCode::MediaPlayPause, + "LAUNCHMAIL" => KeyCode::LaunchMail, + + "SUSPEND" => KeyCode::Suspend, + "F1" => KeyCode::F1, + "F2" => KeyCode::F2, + "F3" => KeyCode::F3, + "F4" => KeyCode::F4, + "F5" => KeyCode::F5, + "F6" => KeyCode::F6, + "F7" => KeyCode::F7, + "F8" => KeyCode::F8, + "F9" => KeyCode::F9, + "F10" => KeyCode::F10, + "F11" => KeyCode::F11, + "F12" => KeyCode::F12, + "F13" => KeyCode::F13, + "F14" => KeyCode::F14, + "F15" => KeyCode::F15, + "F16" => KeyCode::F16, + "F17" => KeyCode::F17, + "F18" => KeyCode::F18, + "F19" => KeyCode::F19, + "F20" => KeyCode::F20, + "F21" => KeyCode::F21, + "F22" => KeyCode::F22, + "F23" => KeyCode::F23, + "F24" => KeyCode::F24, + "F25" => KeyCode::F25, + "F26" => KeyCode::F26, + "F27" => KeyCode::F27, + "F28" => KeyCode::F28, + "F29" => KeyCode::F29, + "F30" => KeyCode::F30, + "F31" => KeyCode::F31, + "F32" => KeyCode::F32, + "F33" => KeyCode::F33, + "F34" => KeyCode::F34, + "F35" => KeyCode::F35, + _ => KeyCode::Unidentified(NativeKeyCode::Unidentified), + }; + + Ok(keycode) + } +} + +impl fmt::Display for KeyCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + &KeyCode::Unidentified(_) => write!(f, "{:?}", "Unidentified"), + val => write!(f, "{val:?}"), + } + } +} + +/// Key represents the meaning of a keypress. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few +/// exceptions: +/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's +/// another key which the specification calls `Super`. That does not exist here.) +/// - The `Space` variant here, can be identified by the character it generates in the +/// specification. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. +/// - The `Dead` variant here, can specify the character which is inserted when pressing the +/// dead-key twice. +/// +/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Key<'a> { + /// A key string that corresponds to the character typed by the user, taking into account the + /// user’s current locale setting, and any system-level keyboard mapping overrides that are in + /// effect. + Character(&'a str), + + /// This variant is used when the key cannot be translated to any other variant. + /// + /// The native scancode is provided (if available) in order to allow the user to specify + /// keybindings for keys which are not defined by this API. + Unidentified(NativeKeyCode), + + /// Contains the text representation of the dead-key when available. + /// + /// ## Platform-specific + /// - **Web:** Always contains `None` + Dead(Option), + + /// The `Alt` (Alternative) key. + /// + /// This key enables the alternate modifier function for interpreting concurrent or subsequent + /// keyboard input. This key value is also used for the Apple Option key. + Alt, + /// The Alternate Graphics (AltGr or AltGraph) key. + /// + /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the + /// level 2 modifier). + AltGraph, + /// The `Caps Lock` (Capital) key. + /// + /// Toggle capital character lock function for interpreting subsequent keyboard input event. + CapsLock, + /// The `Control` or `Ctrl` key. + /// + /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard + /// input. + Control, + /// The Function switch `Fn` key. Activating this key simultaneously with another key changes + /// that key’s value to an alternate character or function. This key is often handled directly + /// in the keyboard hardware and does not usually generate key events. + Fn, + /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the + /// keyboard to changes some keys' values to an alternate character or function. This key is + /// often handled directly in the keyboard hardware and does not usually generate key events. + FnLock, + /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting + /// subsequent keyboard input. + NumLock, + /// Toggle between scrolling and cursor movement modes. + ScrollLock, + /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard + /// input. + Shift, + /// The Symbol modifier key (used on some virtual keyboards). + Symbol, + SymbolLock, + Hyper, + /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard + /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. + /// + /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. + Super, + /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key + /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for + /// the Android `KEYCODE_DPAD_CENTER`. + Enter, + /// The Horizontal Tabulation `Tab` key. + Tab, + /// Used in text to insert a space between words. Usually located below the character keys. + Space, + /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) + ArrowDown, + /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) + ArrowLeft, + /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) + ArrowRight, + /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) + ArrowUp, + /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). + End, + /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). + /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. + /// + /// [`GoHome`]: Self::GoHome + Home, + /// Scroll down or display next page of content. + PageDown, + /// Scroll up or display previous page of content. + PageUp, + /// Used to remove the character to the left of the cursor. This key value is also used for + /// the key labeled `Delete` on MacOS keyboards. + Backspace, + /// Remove the currently selected input. + Clear, + /// Copy the current selection. (`APPCOMMAND_COPY`) + Copy, + /// The Cursor Select key. + CrSel, + /// Cut the current selection. (`APPCOMMAND_CUT`) + Cut, + /// Used to delete the character to the right of the cursor. This key value is also used for the + /// key labeled `Delete` on MacOS keyboards when `Fn` is active. + Delete, + /// The Erase to End of Field key. This key deletes all characters from the current cursor + /// position to the end of the current field. + EraseEof, + /// The Extend Selection (Exsel) key. + ExSel, + /// Toggle between text modes for insertion or overtyping. + /// (`KEYCODE_INSERT`) + Insert, + /// The Paste key. (`APPCOMMAND_PASTE`) + Paste, + /// Redo the last action. (`APPCOMMAND_REDO`) + Redo, + /// Undo the last action. (`APPCOMMAND_UNDO`) + Undo, + /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. + Accept, + /// Redo or repeat an action. + Again, + /// The Attention (Attn) key. + Attn, + Cancel, + /// Show the application’s context menu. + /// This key is commonly found between the right `Super` key and the right `Control` key. + ContextMenu, + /// The `Esc` key. This key was originally used to initiate an escape sequence, but is + /// now more generally used to exit or "escape" the current context, such as closing a dialog + /// or exiting full screen mode. + Escape, + Execute, + /// Open the Find dialog. (`APPCOMMAND_FIND`) + Find, + /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, + /// `KEYCODE_HELP`) + Help, + /// Pause the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` + /// instead. + Pause, + /// Play or resume the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` + /// instead. + Play, + /// The properties (Props) key. + Props, + Select, + /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) + ZoomIn, + /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) + ZoomOut, + /// The Brightness Down key. Typically controls the display brightness. + /// (`KEYCODE_BRIGHTNESS_DOWN`) + BrightnessDown, + /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) + BrightnessUp, + /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) + Eject, + LogOff, + /// Toggle power state. (`KEYCODE_POWER`) + /// Note: Note: Some devices might not expose this key to the operating environment. + Power, + /// The `PowerOff` key. Sometime called `PowerDown`. + PowerOff, + /// Initiate print-screen function. + PrintScreen, + /// The Hibernate key. This key saves the current state of the computer to disk so that it can + /// be restored. The computer will then shutdown. + Hibernate, + /// The Standby key. This key turns off the display and places the computer into a low-power + /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. + /// (`KEYCODE_SLEEP`) + Standby, + /// The WakeUp key. (`KEYCODE_WAKEUP`) + WakeUp, + /// Initate the multi-candidate mode. + AllCandidates, + Alphanumeric, + /// Initiate the Code Input mode to allow characters to be entered by + /// their code points. + CodeInput, + /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a + /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to + /// produce a different character. + Compose, + /// Convert the current input method sequence. + Convert, + /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. + FinalMode, + /// Switch to the first character group. (ISO/IEC 9995) + GroupFirst, + /// Switch to the last character group. (ISO/IEC 9995) + GroupLast, + /// Switch to the next character group. (ISO/IEC 9995) + GroupNext, + /// Switch to the previous character group. (ISO/IEC 9995) + GroupPrevious, + /// Toggle between or cycle through input modes of IMEs. + ModeChange, + NextCandidate, + /// Accept current input method sequence without + /// conversion in IMEs. + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + /// Toggle between Hangul and English modes. + HangulMode, + HanjaMode, + JunjaMode, + /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. + /// (`KEYCODE_EISU`) + Eisu, + /// The (Half-Width) Characters key. + Hankaku, + /// The Hiragana (Japanese Kana characters) key. + Hiragana, + /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) + HiraganaKatakana, + /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from + /// romaji mode). + KanaMode, + /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is + /// typically used to switch to a hiragana keyboard for the purpose of converting input into + /// kanji. (`KEYCODE_KANA`) + KanjiMode, + /// The Katakana (Japanese Kana characters) key. + Katakana, + /// The Roman characters function key. + Romaji, + /// The Zenkaku (Full-Width) Characters key. + Zenkaku, + /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) + ZenkakuHankaku, + /// General purpose virtual function key, as index 1. + Soft1, + /// General purpose virtual function key, as index 2. + Soft2, + /// General purpose virtual function key, as index 3. + Soft3, + /// General purpose virtual function key, as index 4. + Soft4, + /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, + /// `KEYCODE_CHANNEL_DOWN`) + ChannelDown, + /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, + /// `KEYCODE_CHANNEL_UP`) + ChannelUp, + /// Close the current document or message (Note: This doesn’t close the application). + /// (`APPCOMMAND_CLOSE`) + Close, + /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) + MailForward, + /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) + MailReply, + /// Send the current message. (`APPCOMMAND_SEND_MAIL`) + MailSend, + /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) + MediaClose, + /// Initiate or continue forward playback at faster than normal speed, or increase speed if + /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) + MediaFastForward, + /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) + /// + /// Note: Media controller devices should use this value rather than `"Pause"` for their pause + /// keys. + MediaPause, + /// Initiate or continue media playback at normal speed, if not currently playing at normal + /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) + MediaPlay, + /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, + /// `KEYCODE_MEDIA_PLAY_PAUSE`) + MediaPlayPause, + /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, + /// `KEYCODE_MEDIA_RECORD`) + MediaRecord, + /// Initiate or continue reverse playback at faster than normal speed, or increase speed if + /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) + MediaRewind, + /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. + /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) + MediaStop, + /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) + MediaTrackNext, + /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, + /// `KEYCODE_MEDIA_PREVIOUS`) + MediaTrackPrevious, + /// Open a new document or message. (`APPCOMMAND_NEW`) + New, + /// Open an existing document or message. (`APPCOMMAND_OPEN`) + Open, + /// Print the current document or message. (`APPCOMMAND_PRINT`) + Print, + /// Save the current document or message. (`APPCOMMAND_SAVE`) + Save, + /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) + SpellCheck, + /// The `11` key found on media numpads that + /// have buttons from `1` ... `12`. + Key11, + /// The `12` key found on media numpads that + /// have buttons from `1` ... `12`. + Key12, + /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) + AudioBalanceLeft, + /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) + AudioBalanceRight, + /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, + /// `VK_BASS_BOOST_DOWN`) + AudioBassBoostDown, + /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) + AudioBassBoostToggle, + /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, + /// `VK_BASS_BOOST_UP`) + AudioBassBoostUp, + /// Adjust audio fader towards front. (`VK_FADER_FRONT`) + AudioFaderFront, + /// Adjust audio fader towards rear. (`VK_FADER_REAR`) + AudioFaderRear, + /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) + AudioSurroundModeNext, + /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) + AudioTrebleDown, + /// Increase treble. (`APPCOMMAND_TREBLE_UP`) + AudioTrebleUp, + /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) + AudioVolumeDown, + /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) + AudioVolumeUp, + /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, + /// `KEYCODE_VOLUME_MUTE`) + AudioVolumeMute, + /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) + MicrophoneToggle, + /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) + MicrophoneVolumeDown, + /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) + MicrophoneVolumeUp, + /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) + MicrophoneVolumeMute, + /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) + SpeechCorrectionList, + /// Toggle between dictation mode and command/control mode. + /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) + SpeechInputToggle, + /// The first generic "LaunchApplication" key. This is commonly associated with launching "My + /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) + LaunchApplication1, + /// The second generic "LaunchApplication" key. This is commonly associated with launching + /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, + /// `KEYCODE_CALCULATOR`) + LaunchApplication2, + /// The "Calendar" key. (`KEYCODE_CALENDAR`) + LaunchCalendar, + /// The "Contacts" key. (`KEYCODE_CONTACTS`) + LaunchContacts, + /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) + LaunchMail, + /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) + BrowserBack, + /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) + BrowserFavorites, + /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) + BrowserForward, + /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) + BrowserHome, + /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) + BrowserRefresh, + /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) + BrowserSearch, + /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) + BrowserStop, + /// The Application switch key, which provides a list of recent apps to switch between. + /// (`KEYCODE_APP_SWITCH`) + AppSwitch, + /// The Call key. (`KEYCODE_CALL`) + Call, + /// The Camera key. (`KEYCODE_CAMERA`) + Camera, + /// The Camera focus key. (`KEYCODE_FOCUS`) + CameraFocus, + /// The End Call key. (`KEYCODE_ENDCALL`) + EndCall, + /// The Back key. (`KEYCODE_BACK`) + GoBack, + /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) + GoHome, + /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) + HeadsetHook, + LastNumberRedial, + /// The Notification key. (`KEYCODE_NOTIFICATION`) + Notification, + /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) + MannerMode, + VoiceDial, + /// Switch to viewing TV. (`KEYCODE_TV`) + TV, + /// TV 3D Mode. (`KEYCODE_3D_MODE`) + TV3DMode, + /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) + TVAntennaCable, + /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) + TVAudioDescription, + /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) + TVAudioDescriptionMixDown, + /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) + TVAudioDescriptionMixUp, + /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) + TVContentsMenu, + /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) + TVDataService, + /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) + TVInput, + /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) + TVInputComponent1, + /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) + TVInputComponent2, + /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) + TVInputComposite1, + /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) + TVInputComposite2, + /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) + TVInputHDMI1, + /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) + TVInputHDMI2, + /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) + TVInputHDMI3, + /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) + TVInputHDMI4, + /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) + TVInputVGA1, + /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) + TVMediaContext, + /// Toggle network. (`KEYCODE_TV_NETWORK`) + TVNetwork, + /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) + TVNumberEntry, + /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) + TVPower, + /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) + TVRadioService, + /// Satellite. (`KEYCODE_TV_SATELLITE`) + TVSatellite, + /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) + TVSatelliteBS, + /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) + TVSatelliteCS, + /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) + TVSatelliteToggle, + /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) + TVTerrestrialAnalog, + /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) + TVTerrestrialDigital, + /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) + TVTimer, + /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) + AVRInput, + /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) + AVRPower, + /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, + /// `KEYCODE_PROG_RED`) + ColorF0Red, + /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, + /// `KEYCODE_PROG_GREEN`) + ColorF1Green, + /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, + /// `KEYCODE_PROG_YELLOW`) + ColorF2Yellow, + /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, + /// `KEYCODE_PROG_BLUE`) + ColorF3Blue, + /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) + ColorF4Grey, + /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) + ColorF5Brown, + /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) + ClosedCaptionToggle, + /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) + Dimmer, + /// Swap video sources. (`VK_DISPLAY_SWAP`) + DisplaySwap, + /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) + DVR, + /// Exit the current application. (`VK_EXIT`) + Exit, + /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) + FavoriteClear0, + /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) + FavoriteClear1, + /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) + FavoriteClear2, + /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) + FavoriteClear3, + /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) + FavoriteRecall0, + /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) + FavoriteRecall1, + /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) + FavoriteRecall2, + /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) + FavoriteRecall3, + /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) + FavoriteStore0, + /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) + FavoriteStore1, + /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) + FavoriteStore2, + /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) + FavoriteStore3, + /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) + Guide, + /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) + GuideNextDay, + /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) + GuidePreviousDay, + /// Toggle display of information about currently selected context or media. (`VK_INFO`, + /// `KEYCODE_INFO`) + Info, + /// Toggle instant replay. (`VK_INSTANT_REPLAY`) + InstantReplay, + /// Launch linked content, if available and appropriate. (`VK_LINK`) + Link, + /// List the current program. (`VK_LIST`) + ListProgram, + /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) + LiveContent, + /// Lock or unlock current content or program. (`VK_LOCK`) + Lock, + /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) + /// + /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, + /// which is encoded as `"ContextMenu"`. + MediaApps, + /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) + MediaAudioTrack, + /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) + MediaLast, + /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) + MediaSkipBackward, + /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) + MediaSkipForward, + /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) + MediaStepBackward, + /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) + MediaStepForward, + /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) + MediaTopMenu, + /// Navigate in. (`KEYCODE_NAVIGATE_IN`) + NavigateIn, + /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) + NavigateNext, + /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) + NavigateOut, + /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) + NavigatePrevious, + /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) + NextFavoriteChannel, + /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) + NextUserProfile, + /// Access on-demand content or programs. (`VK_ON_DEMAND`) + OnDemand, + /// Pairing key to pair devices. (`KEYCODE_PAIRING`) + Pairing, + /// Move picture-in-picture window down. (`VK_PINP_DOWN`) + PinPDown, + /// Move picture-in-picture window. (`VK_PINP_MOVE`) + PinPMove, + /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) + PinPToggle, + /// Move picture-in-picture window up. (`VK_PINP_UP`) + PinPUp, + /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) + PlaySpeedDown, + /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) + PlaySpeedReset, + /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) + PlaySpeedUp, + /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) + RandomToggle, + /// Not a physical key, but this key code is sent when the remote control battery is low. + /// (`VK_RC_LOW_BATTERY`) + RcLowBattery, + /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) + RecordSpeedNext, + /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). + /// (`VK_RF_BYPASS`) + RfBypass, + /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) + ScanChannelsToggle, + /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) + ScreenModeNext, + /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) + Settings, + /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) + SplitScreenToggle, + /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) + STBInput, + /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) + STBPower, + /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) + Subtitle, + /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). + Teletext, + /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) + VideoModeNext, + /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) + Wink, + /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, + /// `KEYCODE_TV_ZOOM_MODE`) + ZoomToggle, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl<'a> Key<'a> { + pub fn to_text(&self) -> Option<&'a str> { + match self { + Key::Character(ch) => Some(*ch), + Key::Enter => Some("\r"), + Key::Backspace => Some("\x08"), + Key::Tab => Some("\t"), + Key::Space => Some(" "), + Key::Escape => Some("\x1b"), + _ => None, + } + } +} + +impl<'a> From<&'a str> for Key<'a> { + fn from(src: &'a str) -> Key<'a> { + Key::Character(src) + } +} + +/// Represents the location of a physical key. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyLocation { + Standard, + Left, + Right, + Numpad, +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs new file mode 100644 index 000000000..e157637f5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs @@ -0,0 +1,196 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! Tao is a cross-platform application window creation and event loop management library. +//! +//! # Building windows +//! +//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the +//! [`EventLoop::new()`] function. +//! +//! ```no_run +//! use tao::event_loop::EventLoop; +//! let event_loop = EventLoop::new(); +//! ``` +//! +//! Once this is done there are two ways to create a [`Window`]: +//! +//! - Calling [`Window::new(&event_loop)`][window_new]. +//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build]. +//! +//! The first method is the simplest, and will give you default values for everything. The second +//! method allows you to customize the way your [`Window`] will look and behave by modifying the +//! fields of the [`WindowBuilder`] object before you create the [`Window`]. +//! +//! # Event handling +//! +//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can +//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the +//! window or a key getting pressed while the window is focused. Devices can generate +//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. +//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a +//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. +//! +//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will +//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and +//! will run until the `control_flow` argument given to the closure is set to +//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which +//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates. +//! +//! Tao no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop +//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on +//! most other platforms. However, this model can be re-implemented to an extent with +//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why +//! it's discouraged, beyond compatibility reasons. +//! +//! +//! ```no_run +//! use tao::{ +//! event::{Event, WindowEvent}, +//! event_loop::{ControlFlow, EventLoop}, +//! window::WindowBuilder, +//! }; +//! +//! let event_loop = EventLoop::new(); +//! let window = WindowBuilder::new().build(&event_loop).unwrap(); +//! +//! event_loop.run(move |event, _, control_flow| { +//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't +//! // dispatched any events. This is ideal for games and similar applications. +//! *control_flow = ControlFlow::Poll; +//! +//! // ControlFlow::Wait pauses the event loop if no events are available to process. +//! // This is ideal for non-game applications that only update in response to user +//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. +//! *control_flow = ControlFlow::Wait; +//! +//! match event { +//! Event::WindowEvent { +//! event: WindowEvent::CloseRequested, +//! .. +//! } => { +//! println!("The close button was pressed; stopping"); +//! *control_flow = ControlFlow::Exit +//! }, +//! Event::MainEventsCleared => { +//! // Application update code. +//! +//! // Queue a RedrawRequested event. +//! // +//! // You only need to call this if you've determined that you need to redraw, in +//! // applications which do not always need to. Applications that redraw continuously +//! // can just render here instead. +//! window.request_redraw(); +//! }, +//! Event::RedrawRequested(_) => { +//! // Redraw the application. +//! // +//! // It's preferable for applications that do not render continuously to render in +//! // this event rather than in MainEventsCleared, since rendering in here allows +//! // the program to gracefully handle redraws requested by the OS. +//! }, +//! _ => () +//! } +//! }); +//! ``` +//! +//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be +//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`] +//! dispatched the event. +//! +//! # Drawing on the window +//! +//! Tao doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to +//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the +//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows you to create an +//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. +//! +//! Note that many platforms will display garbage data in the window's client area if the +//! application doesn't render anything to the window by the time the desktop compositor is ready to +//! display the window to the user. If you notice this happening, you should create the window with +//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the +//! window visible only once you're ready to render into it. +//! +//! [`EventLoop`]: event_loop::EventLoop +//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return +//! [`EventLoop::new()`]: event_loop::EventLoop::new +//! [event_loop_run]: event_loop::EventLoop::run +//! [`ControlFlow`]: event_loop::ControlFlow +//! [`Exit`]: event_loop::ControlFlow::Exit +//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode +//! [`Window`]: window::Window +//! [`WindowId`]: window::WindowId +//! [`WindowBuilder`]: window::WindowBuilder +//! [window_new]: window::Window::new +//! [window_builder_new]: window::WindowBuilder::new +//! [window_builder_build]: window::WindowBuilder::build +//! [window_id_fn]: window::Window::id +//! [`Event`]: event::Event +//! [`WindowEvent`]: event::WindowEvent +//! [`DeviceEvent`]: event::DeviceEvent +//! [`UserEvent`]: event::Event::UserEvent +//! [`LoopDestroyed`]: event::Event::LoopDestroyed +//! [`platform`]: platform +//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle +//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle +#![allow( + clippy::match_str_case_mismatch, + clippy::upper_case_acronyms, + clippy::from_over_into, + clippy::option_map_unit_fn, + clippy::needless_lifetimes, + clippy::type_complexity, + clippy::identity_op, + clippy::wrong_self_convention, + clippy::non_send_fields_in_send_ty +)] +#![deny(rustdoc::broken_intra_doc_links)] + +use dpi::PixelUnit; +#[cfg(feature = "rwh_04")] +pub use rwh_04; +#[cfg(feature = "rwh_05")] +pub use rwh_05; +#[cfg(feature = "rwh_06")] +pub use rwh_06; + +#[allow(unused_imports)] +#[macro_use] +extern crate log; +#[cfg(feature = "serde")] +#[macro_use] +extern crate serde; +#[macro_use] +extern crate bitflags; +#[cfg(any(target_os = "macos", target_os = "ios"))] +#[macro_use(class, msg_send, sel)] +extern crate objc2; + +pub use dpi; + +#[macro_use] +pub mod error; +pub mod event; +pub mod event_loop; +mod icon; +pub mod keyboard; +pub mod monitor; +mod platform_impl; + +pub mod window; + +pub mod platform; + +pub(crate) fn extract_width_height(size: dpi::Size) -> (PixelUnit, PixelUnit) { + match size { + dpi::Size::Physical(size) => ( + PixelUnit::Physical(size.width.into()), + PixelUnit::Physical(size.height.into()), + ), + dpi::Size::Logical(size) => ( + PixelUnit::Logical(size.width.into()), + PixelUnit::Logical(size.height.into()), + ), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs new file mode 100644 index 000000000..7e5404f57 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs @@ -0,0 +1,163 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! Types useful for interacting with a user's monitors. +//! +//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] +//! type. This is retrieved from one of the following methods, which return an iterator of +//! [`MonitorHandle`][monitor_handle]: +//! - [`EventLoopWindowTarget::available_monitors`][loop_get] +//! - [`Window::available_monitors`][window_get]. +//! +//! [monitor_handle]: crate::monitor::MonitorHandle +//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors +//! [window_get]: crate::window::Window::available_monitors +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + platform_impl, +}; + +/// Describes a fullscreen video mode of a monitor. +/// +/// Can be acquired with: +/// - [`MonitorHandle::video_modes`][monitor_get]. +/// +/// [monitor_get]: crate::monitor::MonitorHandle::video_modes +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct VideoMode { + pub(crate) video_mode: platform_impl::VideoMode, +} + +impl std::fmt::Debug for VideoMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.video_mode.fmt(f) + } +} + +impl PartialOrd for VideoMode { + fn partial_cmp(&self, other: &VideoMode) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VideoMode { + fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { + // TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32` + // to `u32` there + let size: (u32, u32) = self.size().into(); + let other_size: (u32, u32) = other.size().into(); + self.monitor().cmp(&other.monitor()).then( + size + .cmp(&other_size) + .then( + self + .refresh_rate() + .cmp(&other.refresh_rate()) + .then(self.bit_depth().cmp(&other.bit_depth())), + ) + .reverse(), + ) + } +} + +impl VideoMode { + /// Returns the resolution of this video mode. + #[inline] + pub fn size(&self) -> PhysicalSize { + self.video_mode.size() + } + + /// Returns the bit depth of this video mode, as in how many bits you have + /// available per color. This is generally 24 bits or 32 bits on modern + /// systems, depending on whether the alpha channel is counted or not. + /// + /// ## Platform-specific + /// - **iOS:** Always returns 32. + #[inline] + pub fn bit_depth(&self) -> u16 { + self.video_mode.bit_depth() + } + + /// Returns the refresh rate of this video mode. **Note**: the returned + /// refresh rate is an integer approximation, and you shouldn't rely on this + /// value to be exact. + #[inline] + pub fn refresh_rate(&self) -> u16 { + self.video_mode.refresh_rate() + } + + /// Returns the monitor that this video mode is valid for. Each monitor has + /// a separate set of valid video modes. + #[inline] + pub fn monitor(&self) -> MonitorHandle { + self.video_mode.monitor() + } +} + +impl std::fmt::Display for VideoMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}x{} @ {} Hz ({} bpp)", + self.size().width, + self.size().height, + self.refresh_rate(), + self.bit_depth() + ) + } +} + +/// Handle to a monitor. +/// +/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. +/// +/// [`Window`]: crate::window::Window +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MonitorHandle { + pub(crate) inner: platform_impl::MonitorHandle, +} + +impl MonitorHandle { + /// Returns a human-readable name of the monitor. + /// + /// Returns `None` if the monitor doesn't exist anymore. + #[inline] + pub fn name(&self) -> Option { + self.inner.name() + } + + /// Returns the monitor's resolution. + #[inline] + pub fn size(&self) -> PhysicalSize { + self.inner.size() + } + + /// Returns the top-left corner position of the monitor relative to the larger full + /// screen area. + #[inline] + pub fn position(&self) -> PhysicalPosition { + self.inner.position() + } + + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](crate::dpi) module for more information. + /// + /// ## Platform-specific + /// + /// - **Android:** Always returns 1.0. + #[inline] + pub fn scale_factor(&self) -> f64 { + self.inner.scale_factor() + } + + /// Returns all fullscreen video modes supported by this monitor. + /// + /// ## Platform-specific + /// - **Linux:** Unsupported. This will always return empty iterator. + #[inline] + pub fn video_modes(&self) -> impl Iterator { + self.inner.video_modes() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs new file mode 100644 index 000000000..7d4456cb8 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs @@ -0,0 +1,72 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "android")] + +pub mod prelude { + pub use crate::platform_impl::ndk_glue::*; + pub use tao_macros::{android_fn, generate_package_name}; +} +use crate::{ + event_loop::{EventLoop, EventLoopWindowTarget}, + platform_impl::ndk_glue::Rect, + window::{Window, WindowBuilder}, +}; +use ndk::configuration::Configuration; + +/// Additional methods on `EventLoop` that are specific to Android. +pub trait EventLoopExtAndroid {} + +impl EventLoopExtAndroid for EventLoop {} + +/// Additional methods on `EventLoopWindowTarget` that are specific to Android. +pub trait EventLoopWindowTargetExtAndroid {} + +/// Additional methods on `Window` that are specific to Android. +pub trait WindowExtAndroid { + fn content_rect(&self) -> Rect; + + fn config(&self) -> Configuration; + + fn activity_name(&self) -> String; +} + +impl WindowExtAndroid for Window { + fn content_rect(&self) -> Rect { + self.window.content_rect() + } + + fn config(&self) -> Configuration { + self.window.config() + } + + fn activity_name(&self) -> String { + self.window.activity_name().to_string() + } +} + +impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} + +/// Additional methods on `WindowBuilder` that are specific to Android. +pub trait WindowBuilderExtAndroid { + /// The name of the activity class to create. + fn with_activity_name(self, activity_name: String) -> Self; + + /// The name of the activity class that created this window. + /// + /// This is important to define which stack the activity will be created on. + fn with_created_by_activity_name(self, created_by_activity_name: String) -> Self; +} + +impl WindowBuilderExtAndroid for WindowBuilder { + fn with_activity_name(mut self, activity_name: String) -> Self { + self.platform_specific.activity_name = Some(activity_name); + self + } + + fn with_created_by_activity_name(mut self, created_by_activity_name: String) -> Self { + self.platform_specific.created_by_activity_name = Some(created_by_activity_name); + self + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs new file mode 100644 index 000000000..536c3a98b --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs @@ -0,0 +1,378 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "ios")] + +use std::os::raw::c_void; + +use crate::{ + event_loop::{EventLoop, EventLoopWindowTarget}, + monitor::{MonitorHandle, VideoMode}, + platform_impl::set_badge_count, + window::{Window, WindowBuilder}, +}; + +/// Additional methods on [`EventLoop`] that are specific to iOS. +pub trait EventLoopExtIOS { + /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device. + fn idiom(&self) -> Idiom; +} + +impl EventLoopExtIOS for EventLoop { + fn idiom(&self) -> Idiom { + self.event_loop.idiom() + } +} + +/// Additional methods on [`Window`] that are specific to iOS. +pub trait WindowExtIOS { + /// Returns a pointer to the [`UIWindow`] that is used by this window. + /// + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc + fn ui_window(&self) -> *mut c_void; + + /// Returns a pointer to the [`UIViewController`] that is used by this window. + /// + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc + fn ui_view_controller(&self) -> *mut c_void; + + /// Returns a pointer to the [`UIView`] that is used by this window. + /// + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc + fn ui_view(&self) -> *mut c_void; + + /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. + /// + /// The default value is device dependent, and it's recommended GLES or Metal applications set + /// this to [`MonitorHandle::scale_factor()`]. + /// + /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc + /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc + fn set_scale_factor(&self, scale_factor: f64); + + /// Sets the valid orientations for the [`Window`]. + /// + /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. + /// + /// This changes the value returned by + /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc), + /// and then calls + /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc). + fn set_valid_orientations(&self, valid_orientations: ValidOrientations); + + /// Sets whether the [`Window`] prefers the home indicator hidden. + /// + /// The default is to prefer showing the home indicator. + /// + /// This changes the value returned by + /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc). + /// + /// This only has an effect on iOS 11.0+. + fn set_prefers_home_indicator_hidden(&self, hidden: bool); + + /// Sets the screen edges for which the system gestures will take a lower priority than the + /// application's touch handling. + /// + /// This changes the value returned by + /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). + /// + /// This only has an effect on iOS 11.0+. + fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); + + /// Sets whether the [`Window`] prefers the status bar hidden. + /// + /// The default is to prefer showing the status bar. + /// + /// This changes the value returned by + /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc). + fn set_prefers_status_bar_hidden(&self, hidden: bool); + + /// Sets the badge count on iOS launcher. 0 hides the count + fn set_badge_count(&self, count: i32); + + /// Returns the identifier of the UIScene tied to this UIWindow. + fn scene_identifier(&self) -> String; +} + +impl WindowExtIOS for Window { + #[inline] + fn ui_window(&self) -> *mut c_void { + self.window.ui_window() as _ + } + + #[inline] + fn ui_view_controller(&self) -> *mut c_void { + self.window.ui_view_controller() as _ + } + + #[inline] + fn ui_view(&self) -> *mut c_void { + self.window.ui_view() as _ + } + + #[inline] + fn set_scale_factor(&self, scale_factor: f64) { + self.window.set_scale_factor(scale_factor) + } + + #[inline] + fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { + self.window.set_valid_orientations(valid_orientations) + } + + #[inline] + fn set_prefers_home_indicator_hidden(&self, hidden: bool) { + self.window.set_prefers_home_indicator_hidden(hidden) + } + + #[inline] + fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { + self + .window + .set_preferred_screen_edges_deferring_system_gestures(edges) + } + + #[inline] + fn set_prefers_status_bar_hidden(&self, hidden: bool) { + self.window.set_prefers_status_bar_hidden(hidden) + } + + #[inline] + fn set_badge_count(&self, count: i32) { + self.window.set_badge_count(count) + } + + #[inline] + fn scene_identifier(&self) -> String { + self.window.scene_identifier() + } +} + +pub trait EventLoopWindowTargetExtIOS { + /// Sets the badge count on iOS launcher. 0 hides the count + fn set_badge_count(&self, count: i32); +} + +impl EventLoopWindowTargetExtIOS for EventLoopWindowTarget { + fn set_badge_count(&self, count: i32) { + set_badge_count(count) + } +} + +/// Additional methods on [`WindowBuilder`] that are specific to iOS. +pub trait WindowBuilderExtIOS { + /// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided. + /// + /// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc). + /// + /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc + fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; + + /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. + /// + /// The default value is device dependent, and it's recommended GLES or Metal applications set + /// this to [`MonitorHandle::scale_factor()`]. + /// + /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc + /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc + fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder; + + /// Sets the valid orientations for the [`Window`]. + /// + /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. + /// + /// This sets the initial value returned by + /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). + fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder; + + /// Sets whether the [`Window`] prefers the home indicator hidden. + /// + /// The default is to prefer showing the home indicator. + /// + /// This sets the initial value returned by + /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). + /// + /// This only has an effect on iOS 11.0+. + fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder; + + /// Sets the screen edges for which the system gestures will take a lower priority than the + /// application's touch handling. + /// + /// This sets the initial value returned by + /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). + /// + /// This only has an effect on iOS 11.0+. + fn with_preferred_screen_edges_deferring_system_gestures( + self, + edges: ScreenEdge, + ) -> WindowBuilder; + + /// Sets whether the [`Window`] prefers the status bar hidden. + /// + /// The default is to prefer showing the status bar. + /// + /// This sets the initial value returned by + /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). + fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder; + + /// Sets the identifier of the UIScene that is requesting the creation of this new scene, + /// establishing a relationship between the two scenes. + /// + /// By default the system uses the foreground scene. + fn with_requesting_scene_identifier(self, identifier: String) -> WindowBuilder; +} + +impl WindowBuilderExtIOS for WindowBuilder { + #[inline] + fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder { + self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) }; + self + } + + #[inline] + fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder { + self.platform_specific.scale_factor = Some(scale_factor); + self + } + + #[inline] + fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder { + self.platform_specific.valid_orientations = valid_orientations; + self + } + + #[inline] + fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder { + self.platform_specific.prefers_home_indicator_hidden = hidden; + self + } + + #[inline] + fn with_preferred_screen_edges_deferring_system_gestures( + mut self, + edges: ScreenEdge, + ) -> WindowBuilder { + self + .platform_specific + .preferred_screen_edges_deferring_system_gestures = edges; + self + } + + #[inline] + fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder { + self.platform_specific.prefers_status_bar_hidden = hidden; + self + } + + #[inline] + fn with_requesting_scene_identifier(mut self, identifier: String) -> WindowBuilder { + self + .platform_specific + .requesting_scene_identifier + .replace(identifier); + self + } +} + +/// Additional methods on [`MonitorHandle`] that are specific to iOS. +pub trait MonitorHandleExtIOS { + /// Returns a pointer to the [`UIScreen`] that is used by this monitor. + /// + /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc + fn ui_screen(&self) -> *mut c_void; + + /// Returns the preferred [`VideoMode`] for this monitor. + /// + /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). + fn preferred_video_mode(&self) -> VideoMode; +} + +impl MonitorHandleExtIOS for MonitorHandle { + #[inline] + fn ui_screen(&self) -> *mut c_void { + self.inner.ui_screen() as _ + } + + #[inline] + fn preferred_video_mode(&self) -> VideoMode { + self.inner.preferred_video_mode() + } +} + +/// Valid orientations for a particular [`Window`]. +#[non_exhaustive] +#[derive(Clone, Copy, Debug)] +pub enum ValidOrientations { + /// Excludes `PortraitUpsideDown` on iphone + LandscapeAndPortrait, + + Landscape, + + /// Excludes `PortraitUpsideDown` on iphone + Portrait, +} + +impl Default for ValidOrientations { + #[inline] + fn default() -> ValidOrientations { + ValidOrientations::LandscapeAndPortrait + } +} + +/// The device [idiom]. +/// +/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Idiom { + Unspecified, + + /// iPhone and iPod touch. + Phone, + + /// iPad. + Pad, + + /// tvOS and Apple TV. + TV, + CarPlay, +} + +bitflags! { + /// The [edges] of a screen. + /// + /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc + #[derive(Clone, Copy ,Default)] + pub struct ScreenEdge: u8 { + const NONE = 0; + const TOP = 1 << 0; + const LEFT = 1 << 1; + const BOTTOM = 1 << 2; + const RIGHT = 1 << 3; + const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits() + | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits(); + } +} + +pub(crate) fn operating_system_version() -> (isize, isize, isize) { + let process_info = objc2_foundation::NSProcessInfo::processInfo(); + let version = process_info.operatingSystemVersion(); + ( + version.majorVersion, + version.minorVersion, + version.patchVersion, + ) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs new file mode 100644 index 000000000..2f0603c23 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs @@ -0,0 +1,5 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "linux")] diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs new file mode 100644 index 000000000..32b8cbbf3 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs @@ -0,0 +1,458 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "macos")] + +use std::os::raw::c_void; + +use objc2_foundation::NSObject; + +use crate::{ + dpi::{LogicalSize, Position}, + event_loop::{EventLoop, EventLoopWindowTarget}, + monitor::MonitorHandle, + platform_impl::{get_aux_state_mut, set_badge_label, set_dock_visibility, Parent}, + window::{Window, WindowBuilder}, +}; + +/// Additional methods on `Window` that are specific to MacOS. +pub trait WindowExtMacOS { + /// Returns a pointer to the cocoa `NSWindow` that is used by this window. + /// + /// The pointer will become invalid when the `Window` is destroyed. + fn ns_window(&self) -> *mut c_void; + + /// Returns a pointer to the cocoa `NSView` that is used by this window. + /// + /// The pointer will become invalid when the `Window` is destroyed. + fn ns_view(&self) -> *mut c_void; + + /// Returns whether or not the window is in simple fullscreen mode. + fn simple_fullscreen(&self) -> bool; + + /// Toggles a fullscreen mode that doesn't require a new macOS space. + /// Returns a boolean indicating whether the transition was successful (this + /// won't work if the window was already in the native fullscreen). + /// + /// This is how fullscreen used to work on macOS in versions before Lion. + /// And allows the user to have a fullscreen window without using another + /// space or taking control over the entire monitor. + fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; + + /// Returns whether or not the window has shadow. + fn has_shadow(&self) -> bool; + + /// Sets whether or not the window has shadow. + fn set_has_shadow(&self, has_shadow: bool); + + /// Set the window traffic light position relative to the upper left corner + fn set_traffic_light_inset>(&self, position: P); + /// Put the window in a state which indicates a file save is required. + /// + /// + fn set_is_document_edited(&self, edited: bool); + + /// Get the window's edit state + fn is_document_edited(&self) -> bool; + + /// Sets whether the system can automatically organize windows into tabs. + /// + /// + fn set_allows_automatic_window_tabbing(&self, enabled: bool); + + /// Returns whether the system can automatically organize windows into tabs. + fn allows_automatic_window_tabbing(&self) -> bool; + + /// Group windows together by using the same tabbing identifier. + /// + /// + fn set_tabbing_identifier(&self, identifier: &str); + + /// Returns the window's tabbing identifier. + fn tabbing_identifier(&self) -> String; + + /// The content view consumes the full size of the window. + /// + /// + fn set_fullsize_content_view(&self, fullsize: bool); + + /// A Boolean value that indicates whether the title bar draws its background. + /// + /// + fn set_titlebar_transparent(&self, transparent: bool); + + /// Sets the badge label on the taskbar + fn set_badge_label(&self, label: Option); +} + +impl WindowExtMacOS for Window { + #[inline] + fn ns_window(&self) -> *mut c_void { + self.window.ns_window() + } + + #[inline] + fn ns_view(&self) -> *mut c_void { + self.window.ns_view() + } + + #[inline] + fn simple_fullscreen(&self) -> bool { + self.window.simple_fullscreen() + } + + #[inline] + fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { + self.window.set_simple_fullscreen(fullscreen) + } + + #[inline] + fn has_shadow(&self) -> bool { + self.window.has_shadow() + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + self.window.set_has_shadow(has_shadow) + } + + #[inline] + fn set_traffic_light_inset>(&self, position: P) { + self.window.set_traffic_light_inset(position) + } + + #[inline] + fn set_is_document_edited(&self, edited: bool) { + self.window.set_is_document_edited(edited) + } + + #[inline] + fn is_document_edited(&self) -> bool { + self.window.is_document_edited() + } + + #[inline] + fn set_allows_automatic_window_tabbing(&self, enabled: bool) { + self.window.set_allows_automatic_window_tabbing(enabled) + } + + #[inline] + fn allows_automatic_window_tabbing(&self) -> bool { + self.window.allows_automatic_window_tabbing() + } + + #[inline] + fn set_tabbing_identifier(&self, identifier: &str) { + self.window.set_tabbing_identifier(identifier) + } + + #[inline] + fn tabbing_identifier(&self) -> String { + self.window.tabbing_identifier() + } + + #[inline] + fn set_fullsize_content_view(&self, fullsize: bool) { + self.window.set_fullsize_content_view(fullsize); + } + + #[inline] + fn set_titlebar_transparent(&self, transparent: bool) { + self.window.set_titlebar_transparent(transparent); + } + + #[inline] + fn set_badge_label(&self, label: Option) { + self.window.set_badge_label(label); + } +} + +/// Corresponds to `NSApplicationActivationPolicy`. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum ActivationPolicy { + /// Corresponds to `NSApplicationActivationPolicyRegular`. + #[default] + Regular, + /// Corresponds to `NSApplicationActivationPolicyAccessory`. + Accessory, + /// Corresponds to `NSApplicationActivationPolicyProhibited`. + Prohibited, +} + +/// Additional methods on `WindowBuilder` that are specific to MacOS. +/// +/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method +/// on the base `WindowBuilder`: +/// +/// - `with_titlebar_transparent` +/// - `with_title_hidden` +/// - `with_titlebar_hidden` +/// - `with_titlebar_buttons_hidden` +/// - `with_fullsize_content_view` +pub trait WindowBuilderExtMacOS { + /// Sets a parent to the window to be created. + fn with_parent_window(self, parent: *mut c_void) -> WindowBuilder; + /// Enables click-and-drag behavior for the entire window, not just the titlebar. + fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder; + /// Makes the titlebar transparent and allows the content to appear behind it. + fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder; + /// Hides the window title. + fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder; + /// Hides the window titlebar. + fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder; + /// Hides the window titlebar buttons. + fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder; + /// Makes the window content appear behind the titlebar. + fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; + /// Build window with `resizeIncrements` property. Values must not be 0. + fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; + fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; + /// Sets whether or not the window has shadow. + fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; + /// Sets the traffic light position to (x, y) relative to the upper left corner + fn with_traffic_light_inset>(self, inset: P) -> WindowBuilder; + /// Sets whether the system can automatically organize windows into tabs. + fn with_automatic_window_tabbing(self, automatic_tabbing: bool) -> WindowBuilder; + /// Defines the window [tabbing identifier]. + /// + /// [tabbing identifier]: + fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder; +} + +impl WindowBuilderExtMacOS for WindowBuilder { + #[inline] + fn with_parent_window(mut self, parent: *mut c_void) -> WindowBuilder { + self.platform_specific.parent = Parent::ChildOf(parent); + self + } + + #[inline] + fn with_movable_by_window_background( + mut self, + movable_by_window_background: bool, + ) -> WindowBuilder { + self.platform_specific.movable_by_window_background = movable_by_window_background; + self + } + + #[inline] + fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder { + self.platform_specific.titlebar_transparent = titlebar_transparent; + self + } + + #[inline] + fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder { + self.platform_specific.titlebar_hidden = titlebar_hidden; + self + } + + #[inline] + fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder { + self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; + self + } + + #[inline] + fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder { + self.platform_specific.title_hidden = title_hidden; + self + } + + #[inline] + fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder { + self.platform_specific.fullsize_content_view = fullsize_content_view; + self + } + + #[inline] + fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { + self.platform_specific.resize_increments = Some(increments); + self + } + + #[inline] + fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder { + self.platform_specific.disallow_hidpi = disallow_hidpi; + self + } + + #[inline] + fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder { + self.platform_specific.has_shadow = has_shadow; + self + } + + #[inline] + fn with_traffic_light_inset>(mut self, inset: P) -> WindowBuilder { + self.platform_specific.traffic_light_inset = Some(inset.into()); + self + } + + #[inline] + fn with_automatic_window_tabbing(mut self, automatic_tabbing: bool) -> WindowBuilder { + self.platform_specific.automatic_tabbing = automatic_tabbing; + self + } + + #[inline] + fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder { + self + .platform_specific + .tabbing_identifier + .replace(tabbing_identifier.into()); + self + } +} + +pub trait EventLoopExtMacOS { + /// Sets the activation policy for the application. It is set to + /// `NSApplicationActivationPolicyRegular` by default. + /// + /// This function only takes effect if it's called before calling + /// [`run`](crate::event_loop::EventLoop::run) or + /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return). + /// To set the activation policy after that, use + /// [`EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime`](crate::platform::macos::EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime). + fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); + + /// Sets the visibility of the application in the dock. + /// + /// This function only takes effect if it's called before calling + /// [`run`](crate::event_loop::EventLoop::run) or + /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return). + fn set_dock_visibility(&mut self, visible: bool); + + /// Used to prevent the application from automatically activating when launched if + /// another application is already active + /// + /// The default behavior is to ignore other applications and activate when launched. + /// + /// This function only takes effect if it's called before calling + /// [`run`](crate::event_loop::EventLoop::run) or + /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) + fn set_activate_ignoring_other_apps(&mut self, ignore: bool); +} + +impl EventLoopExtMacOS for EventLoop { + #[inline] + fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { + unsafe { + get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy; + } + } + + #[inline] + fn set_dock_visibility(&mut self, visible: bool) { + unsafe { + get_aux_state_mut(&**self.event_loop.delegate).dock_visibility = visible; + } + } + + #[inline] + fn set_activate_ignoring_other_apps(&mut self, ignore: bool) { + unsafe { + get_aux_state_mut(&**self.event_loop.delegate).activate_ignoring_other_apps = ignore; + } + } +} + +/// Additional methods on `MonitorHandle` that are specific to MacOS. +pub trait MonitorHandleExtMacOS { + /// Returns the identifier of the monitor for Cocoa. + fn native_id(&self) -> u32; + /// Returns a pointer to the NSScreen representing this monitor. + fn ns_screen(&self) -> Option<*mut c_void>; +} + +impl MonitorHandleExtMacOS for MonitorHandle { + #[inline] + fn native_id(&self) -> u32 { + self.inner.native_identifier() + } + + fn ns_screen(&self) -> Option<*mut c_void> { + self + .inner + .ns_screen() + .map(|s| objc2::rc::Retained::into_raw(s) as *mut c_void) + } +} + +/// Additional methods on `EventLoopWindowTarget` that are specific to macOS. +pub trait EventLoopWindowTargetExtMacOS { + /// Hide the entire application. In most applications this is typically triggered with Command-H. + fn hide_application(&self); + /// Show the entire application. + fn show_application(&self); + /// Hide the other applications. In most applications this is typically triggered with Command+Option-H. + fn hide_other_applications(&self); + /// Sets the activation policy for the application. It is set to + /// `NSApplicationActivationPolicyRegular` by default. + /// + /// To set the activation policy before the app starts running, see + /// [`EventLoopExtMacOS::set_activation_policy`](crate::platform::macos::EventLoopExtMacOS::set_activation_policy). + fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy); + + /// Sets the visibility of the application in the dock. + /// + /// To set the dock visibility before the app starts running, see + /// [`EventLoopExtMacOS::set_dock_visibility`](crate::platform::macos::EventLoopExtMacOS::set_dock_visibility). + fn set_dock_visibility(&self, visible: bool); + + /// Sets the badge label on macos dock + fn set_badge_label(&self, label: Option); +} + +impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { + fn hide_application(&self) { + // TODO: Safety. + let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + objc2_app_kit::NSApplication::sharedApplication(mtm).hide(None) + } + + fn show_application(&self) { + // TODO: Safety. + let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + unsafe { objc2_app_kit::NSApplication::sharedApplication(mtm).unhide(None) } + } + + fn hide_other_applications(&self) { + // TODO: Safety. + let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + objc2_app_kit::NSApplication::sharedApplication(mtm).hideOtherApplications(None) + } + + fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy) { + use objc2_app_kit::NSApplicationActivationPolicy; + + let ns_activation_policy = match activation_policy { + ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, + ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, + ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, + }; + + // TODO: Safety. + let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; + objc2_app_kit::NSApplication::sharedApplication(mtm).setActivationPolicy(ns_activation_policy); + } + + fn set_dock_visibility(&self, visible: bool) { + let Some(Ok(delegate)) = (unsafe { + // TODO: Safety. + let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); + objc2_app_kit::NSApplication::sharedApplication(mtm) + .delegate() + .map(|delegate| delegate.downcast::()) + }) else { + return; + }; + set_dock_visibility(&delegate, visible); + } + + fn set_badge_label(&self, label: Option) { + set_badge_label(label); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs new file mode 100644 index 000000000..2ca20b36d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! Contains traits with platform-specific methods in them. +//! +//! Contains the follow OS-specific modules: +//! +//! - `android` +//! - `ios` +//! - `macos` +//! - `unix` +//! - `linux` +//! - `windows` +//! +//! And the following platform-specific module: +//! +//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) +//! +//! However only the module corresponding to the platform you're compiling to will be available. + +pub mod android; +pub mod ios; +pub mod linux; +pub mod macos; +pub mod run_return; +pub mod unix; +pub mod windows; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs new file mode 100644 index 000000000..e86ae1f6a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs @@ -0,0 +1,50 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(not(target_os = "ios"))] + +use crate::{ + event::Event, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, +}; + +/// Additional methods on `EventLoop` to return control flow to the caller. +pub trait EventLoopExtRunReturn { + /// A type provided by the user that can be passed through `Event::UserEvent`. + type UserEvent; + + /// Initializes the `tao` event loop. + /// + /// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns + /// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`. + /// + /// # Caveats + /// Despite its appearance at first glance, this is *not* a perfect replacement for + /// `poll_events`. For example, this function will not return on Windows or macOS while a + /// window is getting resized, resulting in all application logic outside of the + /// `event_handler` closure not running until the resize operation ends. Other OS operations + /// may also result in such freezes. This behavior is caused by fundamental limitations in the + /// underlying OS APIs, which cannot be hidden by `tao` without severe stability repercussions. + /// + /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. + /// + /// ## Platform-specific + /// + /// - **Unix-alikes** (**X11** or **Wayland**): This function returns `1` upon disconnection from + /// the display server. + fn run_return(&mut self, event_handler: F) -> i32 + where + F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow); +} + +impl EventLoopExtRunReturn for EventLoop { + type UserEvent = T; + + fn run_return(&mut self, event_handler: F) -> i32 + where + F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow), + { + self.event_loop.run_return(event_handler) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs new file mode 100644 index 000000000..c25b89fc8 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs @@ -0,0 +1,308 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +#[cfg(feature = "x11")] +use std::{os::raw::c_int, sync::Arc}; + +// XConnection utilities +#[doc(hidden)] +#[cfg(feature = "x11")] +pub use crate::platform_impl::x11; + +#[cfg(feature = "x11")] +use crate::platform_impl::x11::xdisplay::XError; +pub use crate::platform_impl::EventLoop as UnixEventLoop; +use crate::{ + error::{ExternalError, OsError}, + event_loop::{EventLoopBuilder, EventLoopWindowTarget}, + monitor::MonitorHandle, + platform_impl::{Parent, Window as UnixWindow}, + window::{Window, WindowBuilder}, +}; + +#[cfg(feature = "x11")] +use self::x11::xdisplay::XConnection; + +/// Additional methods on `EventLoop` that are specific to Unix. +pub trait EventLoopBuilderExtUnix { + /// Whether to allow the event loop to be created off of the main thread. + /// + /// By default, the window is only allowed to be created on the main + /// thread, to make platform compatibility easier. + /// + /// # `Window` caveats + /// + /// Note that any `Window` created on the new thread will be destroyed when the thread + /// terminates. Attempting to use a `Window` after its parent thread terminates has + /// unspecified, although explicitly not undefined, behavior. + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; + + /// Set the gtk application id. + /// + /// If no application ID is given then some features (most notably application uniqueness) will be disabled. + fn with_app_id>(&mut self, id: S) -> &mut Self; +} + +impl EventLoopBuilderExtUnix for EventLoopBuilder { + #[inline] + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { + self.platform_specific.any_thread = any_thread; + self + } + + fn with_app_id>(&mut self, id: S) -> &mut Self { + self.platform_specific.app_id = Some(id.into()); + self + } +} + +/// Additional methods on `Window` that are specific to Unix. +pub trait WindowExtUnix { + /// Create a new Tao window from an existing GTK window. Generally you should use + /// the non-Linux `WindowBuilder`, this is for those who need lower level window access + /// and know what they're doing. + fn new_from_gtk_window( + event_loop_window_target: &EventLoopWindowTarget, + window: gtk::ApplicationWindow, + ) -> Result; + + /// Returns the `gtk::ApplicatonWindow` from gtk crate that is used by this window. + fn gtk_window(&self) -> >k::ApplicationWindow; + + /// Returns the vertical `gtk::Box` that is added by default as the sole child of this window. + /// Returns `None` if the default vertical `gtk::Box` creation was disabled by [`WindowBuilderExtUnix::with_default_vbox`]. + fn default_vbox(&self) -> Option<>k::Box>; + + /// Whether to show the window icon in the taskbar or not. + fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>; + + fn set_badge_count(&self, count: Option, desktop_filename: Option); +} + +impl WindowExtUnix for Window { + fn gtk_window(&self) -> >k::ApplicationWindow { + &self.window.window + } + + fn default_vbox(&self) -> Option<>k::Box> { + self.window.default_vbox.as_ref() + } + + fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { + self.window.set_skip_taskbar(skip) + } + + fn new_from_gtk_window( + event_loop_window_target: &EventLoopWindowTarget, + window: gtk::ApplicationWindow, + ) -> Result { + let window = UnixWindow::new_from_gtk_window(&event_loop_window_target.p, window)?; + Ok(Window { window }) + } + + fn set_badge_count(&self, count: Option, desktop_filename: Option) { + self.window.set_badge_count(count, desktop_filename); + } +} + +pub trait WindowBuilderExtUnix { + /// Whether to create the window icon with the taskbar icon or not. + fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; + /// Set this window as a transient dialog for `parent` + /// + fn with_transient_for(self, parent: &impl gtk::glib::IsA) -> WindowBuilder; + + /// Whether to enable or disable the internal draw for transparent window. + /// + /// When transparent attribute is enabled, we will call `connect_draw` and draw a transparent background. + /// For anyone who wants to draw the background themselves, set this to `false`. + /// Default is `true`. + fn with_transparent_draw(self, draw: bool) -> WindowBuilder; + + /// Whether to enable or disable the double buffered rendering of the window. + /// + /// Default is `true`. + fn with_double_buffered(self, double_buffered: bool) -> WindowBuilder; + + /// Whether to enable the rgba visual for the window. + /// + /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` + fn with_rgba_visual(self, rgba_visual: bool) -> WindowBuilder; + + /// Whether to set this window as app paintable + /// + /// + /// + /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` + fn with_app_paintable(self, app_paintable: bool) -> WindowBuilder; + + /// Whether to set cursor moved event. Cursor event is suited for native GUI frameworks and + /// games. But it can block gtk's own pipeline occasionally. Turn this off can help Gtk looks + /// smoother. + /// + /// Default is `true`. + fn with_cursor_moved_event(self, cursor_moved: bool) -> WindowBuilder; + + /// Whether to create a vertical `gtk::Box` and add it as the sole child of this window. + /// Created by default. + fn with_default_vbox(self, add: bool) -> WindowBuilder; +} + +impl WindowBuilderExtUnix for WindowBuilder { + fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder { + self.platform_specific.skip_taskbar = skip; + self + } + + fn with_transient_for(mut self, parent: &impl gtk::glib::IsA) -> WindowBuilder { + use gtk::glib::Cast; + self.platform_specific.parent = Parent::ChildOf(parent.clone().upcast()); + self + } + + fn with_transparent_draw(mut self, draw: bool) -> WindowBuilder { + self.platform_specific.auto_transparent = draw; + self + } + + fn with_double_buffered(mut self, double_buffered: bool) -> WindowBuilder { + self.platform_specific.double_buffered = double_buffered; + self + } + + fn with_rgba_visual(mut self, rgba_visual: bool) -> WindowBuilder { + self.platform_specific.rgba_visual = rgba_visual; + self + } + + fn with_app_paintable(mut self, app_paintable: bool) -> WindowBuilder { + self.platform_specific.app_paintable = app_paintable; + self + } + + fn with_cursor_moved_event(mut self, cursor_moved: bool) -> WindowBuilder { + self.platform_specific.cursor_moved = cursor_moved; + self + } + + fn with_default_vbox(mut self, add: bool) -> WindowBuilder { + self.platform_specific.default_vbox = add; + self + } +} + +/// Additional methods on `EventLoopWindowTarget` that are specific to Unix. +pub trait EventLoopWindowTargetExtUnix { + /// True if the `EventLoopWindowTarget` uses Wayland. + fn is_wayland(&self) -> bool; + + /// True if the `EventLoopWindowTarget` uses X11. + #[cfg(feature = "x11")] + fn is_x11(&self) -> bool; + + #[cfg(feature = "x11")] + fn xlib_xconnection(&self) -> Option>; + + // /// Returns a pointer to the `wl_display` object of wayland that is used by this + // /// `EventLoopWindowTarget`. + // /// + // /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). + // /// + // /// The pointer will become invalid when the winit `EventLoop` is destroyed. + // fn wayland_display(&self) -> Option<*mut raw::c_void>; + + /// Returns the gtk application for this event loop. + fn gtk_app(&self) -> >k::Application; + + /// Sets the badge count on the taskbar + fn set_badge_count(&self, count: Option, desktop_filename: Option); +} + +impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { + #[inline] + fn is_wayland(&self) -> bool { + self.p.is_wayland() + } + + #[cfg(feature = "x11")] + #[inline] + fn is_x11(&self) -> bool { + !self.p.is_wayland() + } + + #[cfg(feature = "x11")] + #[inline] + fn xlib_xconnection(&self) -> Option> { + if self.is_x11() { + if let Ok(xconn) = XConnection::new(Some(x_error_callback)) { + Some(Arc::new(xconn)) + } else { + None + } + } else { + None + } + } + + // #[inline] + // fn wayland_display(&self) -> Option<*mut raw::c_void> { + // match self.p { + // LinuxEventLoopWindowTarget::Wayland(ref p) => { + // Some(p.display().get_display_ptr() as *mut _) + // } + // #[cfg(feature = "x11")] + // _ => None, + // } + // } + + #[inline] + fn gtk_app(&self) -> >k::Application { + &self.p.app + } + + #[inline] + fn set_badge_count(&self, count: Option, desktop_filename: Option) { + self.p.set_badge_count(count, desktop_filename); + } +} + +#[cfg(feature = "x11")] +unsafe extern "C" fn x_error_callback( + _display: *mut x11::ffi::Display, + event: *mut x11::ffi::XErrorEvent, +) -> c_int { + let error = XError { + // TODO get the error text as description + description: String::new(), + error_code: (*event).error_code, + request_code: (*event).request_code, + minor_code: (*event).minor_code, + }; + + error!("X11 error: {:#?}", error); + + // Fun fact: this return value is completely ignored. + 0 +} + +/// Additional methods on `MonitorHandle` that are specific to Unix. +pub trait MonitorHandleExtUnix { + /// Returns the gdk handle of the monitor. + fn gdk_monitor(&self) -> >k::gdk::Monitor; +} + +impl MonitorHandleExtUnix for MonitorHandle { + #[inline] + fn gdk_monitor(&self) -> >k::gdk::Monitor { + &self.inner.monitor + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs new file mode 100644 index 000000000..a248b04b3 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs @@ -0,0 +1,449 @@ +// Copyright 2014-2021 The tao contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "windows")] + +use std::path::Path; + +use crate::{ + dpi::PhysicalSize, + error::ExternalError, + event::DeviceId, + event_loop::EventLoopBuilder, + monitor::MonitorHandle, + platform_impl::{Parent, WinIcon}, + window::{BadIcon, Icon, Theme, Window, WindowBuilder}, +}; +use windows::Win32::UI::Input::KeyboardAndMouse::*; + +pub type HWND = isize; +pub type HMENU = isize; + +/// Additional methods on `EventLoop` that are specific to Windows. +pub trait EventLoopBuilderExtWindows { + /// Whether to allow the event loop to be created off of the main thread. + /// + /// By default, the window is only allowed to be created on the main + /// thread, to make platform compatibility easier. + /// + /// # `Window` caveats + /// + /// Note that any `Window` created on the new thread will be destroyed when the thread + /// terminates. Attempting to use a `Window` after its parent thread terminates has + /// unspecified, although explicitly not undefined, behavior. + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; + + /// Whether to enable process-wide DPI awareness. + /// + /// By default, `tao` will attempt to enable process-wide DPI awareness. If + /// that's undesirable, you can disable it with this function. + /// + /// # Example + /// + /// Disable process-wide DPI awareness. + /// + /// ``` + /// use tao::event_loop::EventLoopBuilder; + /// #[cfg(target_os = "windows")] + /// use tao::platform::windows::EventLoopBuilderExtWindows; + /// + /// let mut builder = EventLoopBuilder::new(); + /// #[cfg(target_os = "windows")] + /// builder.with_dpi_aware(false); + /// # if false { // We can't test this part + /// let event_loop = builder.build(); + /// # } + /// ``` + fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self; + + /// A callback to be executed before dispatching a win32 message to the window procedure. + /// Return true to disable tao's internal message dispatching. + /// + /// # Example + /// + /// ``` + /// # use windows::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG}; + /// use tao::event_loop::EventLoopBuilder; + /// #[cfg(target_os = "windows")] + /// use tao::platform::windows::EventLoopBuilderExtWindows; + /// + /// let mut builder = EventLoopBuilder::new(); + /// #[cfg(target_os = "windows")] + /// builder.with_msg_hook(|msg|{ + /// let msg = msg as *const MSG; + /// # let accels_: Vec = Vec::new(); + /// # let accels = accels_.as_slice(); + /// let translated = unsafe { + /// TranslateAcceleratorW( + /// (*msg).hwnd, + /// CreateAcceleratorTableW(accels).unwrap(), + /// msg, + /// ) == 1 + /// }; + /// translated + /// }); + /// ``` + fn with_msg_hook(&mut self, callback: F) -> &mut Self + where + F: FnMut(*const std::ffi::c_void) -> bool + 'static; + + /// Forces a theme or uses the system settings if `None` was provided. + /// + /// This will only affect some controls like context menus. + /// + /// ## Note + /// + /// Since this setting is app-wide, using [`WindowBuilder::with_theme`] + /// will not change the affected controls for that specific window, + /// so it is recommended to always use the same theme used for this app-wide setting + /// or use `None` so it automatically uses the theme of this method + /// or falls back to the system preference. + fn with_theme(&mut self, theme: Option) -> &mut Self; +} + +impl EventLoopBuilderExtWindows for EventLoopBuilder { + #[inline] + fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { + self.platform_specific.any_thread = any_thread; + self + } + + #[inline] + fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self { + self.platform_specific.dpi_aware = dpi_aware; + self + } + + #[inline] + fn with_msg_hook(&mut self, callback: F) -> &mut Self + where + F: FnMut(*const std::ffi::c_void) -> bool + 'static, + { + self.platform_specific.msg_hook = Some(Box::new(callback)); + self + } + + #[inline] + fn with_theme(&mut self, theme: Option) -> &mut Self { + self.platform_specific.preferred_theme = theme; + self + } +} + +/// Additional methods on `Window` that are specific to Windows. +pub trait WindowExtWindows { + /// Returns the HINSTANCE of the window + fn hinstance(&self) -> isize; + /// Returns the native handle that is used by this window. + /// + /// The pointer will become invalid when the native window was destroyed. + fn hwnd(&self) -> isize; + + /// Enables or disables mouse and keyboard input to the specified window. + /// + /// A window must be enabled before it can be activated. + /// If an application has create a modal dialog box by disabling its owner window + /// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable + /// the owner window before destroying the dialog box. + /// Otherwise, another window will receive the keyboard focus and be activated. + /// + /// If a child window is disabled, it is ignored when the system tries to determine which + /// window should receive mouse messages. + /// + /// For more information, see + /// and + fn set_enable(&self, enabled: bool); + + /// This sets `ICON_BIG`. A good ceiling here is 256x256. + fn set_taskbar_icon(&self, taskbar_icon: Option); + + /// This sets the overlay icon + fn set_overlay_icon(&self, icon: Option<&Icon>); + + /// Returns the current window theme. + fn theme(&self) -> Theme; + + /// Reset the dead key state of the keyboard. + /// + /// This is useful when a dead key is bound to trigger an action. Then + /// this function can be called to reset the dead key state so that + /// follow-up text input won't be affected by the dead key. + fn reset_dead_keys(&self); + + /// Starts the resizing drag from given edge + fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32); + + /// Whether to show the window icon in the taskbar or not. + fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>; + + /// Shows or hides the background drop shadow for undecorated windows. + /// + /// Enabling the shadow causes a thin 1px line to appear on the top of the window. + fn set_undecorated_shadow(&self, shadow: bool); + + /// Returns whether this window has shadow for undecorated windows. + fn has_undecorated_shadow(&self) -> bool; + + /// Sets right-to-left layout. + /// + /// Enabling this mainly flips the orientation of menus and title bar buttons + fn set_rtl(&self, rtl: bool); +} + +impl WindowExtWindows for Window { + #[inline] + fn hinstance(&self) -> isize { + self.window.hinstance().0 as _ + } + + #[inline] + fn hwnd(&self) -> isize { + self.window.hwnd().0 as _ + } + + #[inline] + fn set_enable(&self, enabled: bool) { + unsafe { + let _ = EnableWindow(self.window.hwnd(), enabled); + } + } + + #[inline] + fn set_taskbar_icon(&self, taskbar_icon: Option) { + self.window.set_taskbar_icon(taskbar_icon) + } + + #[inline] + fn theme(&self) -> Theme { + self.window.theme() + } + + #[inline] + fn reset_dead_keys(&self) { + self.window.reset_dead_keys(); + } + + #[inline] + fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32) { + self.window.begin_resize_drag(edge, button, x, y) + } + + #[inline] + fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { + self.window.set_skip_taskbar(skip) + } + + #[inline] + fn set_undecorated_shadow(&self, shadow: bool) { + self.window.set_undecorated_shadow(shadow) + } + + #[inline] + fn has_undecorated_shadow(&self) -> bool { + self.window.has_undecorated_shadow() + } + + #[inline] + fn set_rtl(&self, rtl: bool) { + self.window.set_rtl(rtl) + } + + #[inline] + fn set_overlay_icon(&self, icon: Option<&Icon>) { + self.window.set_overlay_icon(icon); + } +} + +/// Additional methods on `WindowBuilder` that are specific to Windows. +pub trait WindowBuilderExtWindows { + /// Sets a parent to the window to be created. + /// + /// A child window has the WS_CHILD style and is confined to the client area of its parent window. + /// + /// For more information, see + fn with_parent_window(self, parent: HWND) -> WindowBuilder; + + /// Set an owner to the window to be created. Can be used to create a dialog box, for example. + /// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable) + /// on the owner window to create a modal dialog box. + /// + /// From MSDN: + /// - An owned window is always above its owner in the z-order. + /// - The system automatically destroys an owned window when its owner is destroyed. + /// - An owned window is hidden when its owner is minimized. + /// + /// For more information, see + fn with_owner_window(self, parent: HWND) -> WindowBuilder; + + /// Sets a menu on the window to be created. + /// + /// Parent and menu are mutually exclusive; a child window cannot have a menu! + /// + /// The menu must have been manually created beforehand with [`windows::Win32::UI::WindowsAndMessaging::CreateMenu`] + /// or similar. + /// + /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. + /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. + fn with_menu(self, menu: HMENU) -> WindowBuilder; + + /// This sets `ICON_BIG`. A good ceiling here is 256x256. + fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; + + /// This sets `WS_EX_NOREDIRECTIONBITMAP`. + fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder; + + /// Enables or disables drag and drop support (enabled by default). Will interfere with other crates + /// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of + /// `COINIT_APARTMENTTHREADED`) on the same thread. Note that tao may still attempt to initialize + /// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. + /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any tao functions. + /// See for more information. + fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; + + /// Whether to create the window icon with the taskbar icon or not. + fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; + + /// Customize the window class name. + fn with_window_classname>(self, classname: S) -> WindowBuilder; + + /// Shows or hides the background drop shadow for undecorated windows. + /// + /// The shadow is hidden by default. + /// Enabling the shadow causes a thin 1px line to appear on the top of the window. + fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder; + + /// Sets right-to-left layout. + fn with_rtl(self, rtl: bool) -> WindowBuilder; +} + +impl WindowBuilderExtWindows for WindowBuilder { + #[inline] + fn with_parent_window(mut self, parent: HWND) -> WindowBuilder { + self.platform_specific.parent = Parent::ChildOf(windows::Win32::Foundation::HWND(parent as _)); + self + } + + #[inline] + fn with_owner_window(mut self, parent: HWND) -> WindowBuilder { + self.platform_specific.parent = Parent::OwnedBy(windows::Win32::Foundation::HWND(parent as _)); + self + } + + #[inline] + fn with_menu(mut self, menu: HMENU) -> WindowBuilder { + self.platform_specific.menu = Some(windows::Win32::UI::WindowsAndMessaging::HMENU(menu as _)); + self + } + + #[inline] + fn with_taskbar_icon(mut self, taskbar_icon: Option) -> WindowBuilder { + self.platform_specific.taskbar_icon = taskbar_icon; + self + } + + #[inline] + fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder { + self.platform_specific.no_redirection_bitmap = flag; + self + } + + #[inline] + fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder { + self.platform_specific.drag_and_drop = flag; + self + } + + #[inline] + fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder { + self.platform_specific.skip_taskbar = skip; + self + } + + #[inline] + fn with_window_classname>(mut self, classname: S) -> WindowBuilder { + self.platform_specific.window_classname = classname.into(); + self + } + + #[inline] + fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder { + self.platform_specific.decoration_shadow = shadow; + self + } + + #[inline] + fn with_rtl(mut self, rtl: bool) -> WindowBuilder { + self.platform_specific.rtl = rtl; + self + } +} + +/// Additional methods on `MonitorHandle` that are specific to Windows. +pub trait MonitorHandleExtWindows { + /// Returns the name of the monitor adapter specific to the Win32 API. + fn native_id(&self) -> String; + + /// Returns the handle of the monitor - `HMONITOR`. + fn hmonitor(&self) -> isize; +} + +impl MonitorHandleExtWindows for MonitorHandle { + #[inline] + fn native_id(&self) -> String { + self.inner.native_identifier() + } + + #[inline] + fn hmonitor(&self) -> isize { + self.inner.hmonitor().0 as _ + } +} + +/// Additional methods on `DeviceId` that are specific to Windows. +pub trait DeviceIdExtWindows { + /// Returns an identifier that persistently refers to this specific device. + /// + /// Will return `None` if the device is no longer available. + fn persistent_identifier(&self) -> Option; +} + +impl DeviceIdExtWindows for DeviceId { + #[inline] + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() + } +} + +/// Additional methods on `Icon` that are specific to Windows. +pub trait IconExtWindows: Sized { + /// Create an icon from a file path. + /// + /// Specify `size` to load a specific icon size from the file, or `None` to load the default + /// icon size from the file. + /// + /// In cases where the specified size does not exist in the file, Windows may perform scaling + /// to get an icon of the desired size. + fn from_path>(path: P, size: Option>) -> Result; + + /// Create an icon from a resource embedded in this executable or library. + /// + /// Specify `size` to load a specific icon size from the file, or `None` to load the default + /// icon size from the file. + /// + /// In cases where the specified size does not exist in the file, Windows may perform scaling + /// to get an icon of the desired size. + fn from_resource(ordinal: u16, size: Option>) -> Result; +} + +impl IconExtWindows for Icon { + fn from_path>(path: P, size: Option>) -> Result { + let win_icon = WinIcon::from_path(path, size)?; + Ok(Icon { inner: win_icon }) + } + + fn from_resource(ordinal: u16, size: Option>) -> Result { + let win_icon = WinIcon::from_resource(ordinal, size)?; + Ok(Icon { inner: win_icon }) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs new file mode 100644 index 000000000..5acb4d7ff --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs @@ -0,0 +1,1498 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, event, + event_loop::{self, ControlFlow}, + keyboard::{KeyCode, NativeKeyCode}, + monitor, + window::{self, ResizeDirection, Theme, WindowSizeConstraints}, +}; +use crossbeam_channel::{Receiver, Sender}; +use ndk::{ + configuration::Configuration, + looper::{ForeignLooper, Poll, ThreadLooper}, +}; +use std::{ + collections::{HashSet, VecDeque}, + error::Error, + fmt, + sync::{LazyLock, RwLock}, + time::{Duration, Instant}, +}; + +#[derive(Debug)] +pub struct JniCallError { + source: jni::errors::Error, + java_exception: Option, +} + +impl JniCallError { + fn new(source: jni::errors::Error, java_exception: Option) -> Self { + Self { + source, + java_exception, + } + } +} + +impl From for JniCallError { + fn from(source: jni::errors::Error) -> Self { + Self::new(source, None) + } +} + +impl fmt::Display for JniCallError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.java_exception { + Some(java_exception) => write!(f, "{}: {java_exception}", self.source), + None => self.source.fmt(f), + } + } +} + +impl Error for JniCallError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} + +/// Logs and clears a pending Java exception while preserving the original JNI error. +macro_rules! jni_handle_error { + ($env:ident, $e:expr) => {{ + let e = $e; + let message = (|| -> jni::errors::Result> { + if $env.exception_check()? { + let throwable = $env.exception_occurred()?; + $env.exception_clear()?; + + let message = $env + .call_method(&throwable, "toString", "()Ljava/lang/String;", &[])? + .l()?; + let message: jni::objects::JString = message.into(); + return $env.get_string(&message).map(|s| Some(s.into())); + } + + Ok(None) + })(); + + let java_exception = match message { + Ok(Some(message)) => { + log::error!( + "tao: JNI call failed at {}:{} with Java exception: {message}", + file!(), + line!() + ); + Some(message) + } + Ok(None) => { + log::error!("tao: JNI call failed at {}:{}: {e}", file!(), line!()); + None + } + Err(err) => { + let message = format!("failed to read Java exception: {err}"); + log::error!( + "tao: JNI call failed at {}:{}: {e}; {message}", + file!(), + line!() + ); + Some(message) + } + }; + + $crate::platform_impl::platform::JniCallError::new(e, java_exception) + }}; +} + +macro_rules! jni_call_method { + ($env:ident, $obj:expr, $method:expr, $sig:expr, $args:expr, $ret_typ:ident) => {{ + $env + .call_method($obj, $method, $sig, $args) + .and_then(|v| v.$ret_typ()) + .map_err(|e| jni_handle_error!($env, e)) + }}; + + ($env:ident, $obj:expr, $method:expr, $sig:expr, $ret_typ:ident) => { + jni_call_method!($env, $obj, $method, $sig, &[], $ret_typ) + }; +} + +pub mod ndk_glue; +use ndk_glue::{ActivityId, Event, Rect, WindowEvent}; + +static CONFIG: LazyLock> = + LazyLock::new(|| RwLock::new(Configuration::new())); + +#[derive(Debug)] +pub enum OsError { + JniCallError(JniCallError), + NoAvailableActivity, +} + +impl std::error::Error for OsError {} +impl std::fmt::Display for OsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OsError::JniCallError(e) => write!(f, "JNI error: {e}"), + OsError::NoAvailableActivity => write!(f, "no available activity"), + } + } +} + +enum EventSource { + Callback, + InputQueue, + User, +} + +fn poll(poll: Poll) -> Option { + match poll { + Poll::Event { ident, .. } => match ident { + ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback), + ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue), + _ => unreachable!(), + }, + Poll::Timeout => None, + Poll::Wake => Some(EventSource::User), + Poll::Callback => unreachable!(), + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra {} + +pub struct EventLoop { + window_target: event_loop::EventLoopWindowTarget, + receiver: Receiver, + sender_to_clone: Sender, + first_event: Option, + start_cause: event::StartCause, + looper: ThreadLooper, + running: HashSet, +} + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + +impl EventLoop { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { + let (sender, receiver) = crossbeam_channel::unbounded(); + + Self { + window_target: event_loop::EventLoopWindowTarget { + p: EventLoopWindowTarget { + _marker: std::marker::PhantomData, + }, + _marker: std::marker::PhantomData, + }, + sender_to_clone: sender, + receiver, + first_event: None, + start_cause: event::StartCause::Init, + looper: ThreadLooper::for_thread().unwrap(), + running: HashSet::new(), + } + } + + pub fn run(mut self, event_handler: F) -> ! + where + F: + 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let exit_code = self.run_return(event_handler); + ::std::process::exit(exit_code); + } + + pub fn run_return(&mut self, mut event_handler: F) -> i32 + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let event_handler = &mut event_handler; + let control_flow = &mut ControlFlow::default(); + + loop { + self.call_event_handler( + event_handler, + control_flow, + event::Event::NewEvents(self.start_cause), + ); + + let mut redraw_window_id = None; + let mut resized_window_id = None; + + match self.first_event.take() { + Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { + Event::Resume { id: window_id } => { + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Resumed, + }, + ); + } + Event::WindowEvent { + id: window_id, + event, + } => match event { + WindowEvent::Resized => resized_window_id = Some(window_id), + WindowEvent::RedrawNeeded => redraw_window_id = Some(window_id), + WindowEvent::Started => { + self.running.insert(window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Started, + }, + ); + } + WindowEvent::Focused(focused) => { + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Focused(focused), + }, + ); + } + WindowEvent::Stopped => { + self.running.remove(&window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Stopped, + }, + ); + } + WindowEvent::Destroyed => { + self.running.remove(&window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Destroyed, + }, + ); + } + _ => {} + }, + Event::Pause { id: window_id } => { + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Suspended, + }, + ); + } + Event::Opened => { + let urls = ndk_glue::take_intent_urls(); + if !urls.is_empty() { + self.call_event_handler(event_handler, control_flow, event::Event::Opened { urls }); + } + } + //Event::ConfigChanged => { + // #[allow(deprecated)] // TODO: use ndk-context instead + // let am = ndk_glue::native_activity().asset_manager(); + // let config = Configuration::from_asset_manager(&am); + // let old_scale_factor = MonitorHandle.scale_factor(); + // *CONFIG.write().unwrap() = config; + // let scale_factor = MonitorHandle.scale_factor(); + // if (scale_factor - old_scale_factor).abs() < f64::EPSILON { + // let mut size = MonitorHandle.size(); + // let event = event::Event::WindowEvent { + // window_id: window::WindowId(WindowId), + // event: event::WindowEvent::ScaleFactorChanged { + // new_inner_size: &mut size, + // scale_factor, + // }, + // }; + // self.call_event_handler(event_handler, control_flow, event); + // } + //} + _ => {} + }, + + Some(EventSource::InputQueue) => { + /* + if let Some(input_queue) = ndk_glue::input_queue().as_ref() { + while let Ok(Some(event)) = input_queue.event() { + if let Some(event) = input_queue.pre_dispatch(event) { + let mut handled = true; + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + match &event { + InputEvent::MotionEvent(motion_event) => { + let phase = match motion_event.action() { + MotionAction::Down | MotionAction::PointerDown => { + Some(event::TouchPhase::Started) + } + MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended), + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => Some(event::TouchPhase::Cancelled), + _ => { + handled = false; + None // TODO mouse events + } + }; + if let Some(phase) = phase { + let pointers: Box>> = match phase + { + event::TouchPhase::Started | event::TouchPhase::Ended => { + Box::new(std::iter::once( + motion_event.pointer_at_index(motion_event.pointer_index()), + )) + } + event::TouchPhase::Moved | event::TouchPhase::Cancelled => { + Box::new(motion_event.pointers()) + } + }; + + for pointer in pointers { + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch(event::Touch { + device_id, + phase, + location, + id: pointer.pointer_id() as u64, + force: None, + }), + }; + self.call_event_handler( + event_handler, + control_flow, + event + ); + } + } + } + InputEvent::KeyEvent(key) => { + let state = match key.action() { + KeyAction::Down => event::ElementState::Pressed, + KeyAction::Up => event::ElementState::Released, + _ => event::ElementState::Released, + }; + + let keycode = key.key_code(); + let native = NativeKeyCode::Android(keycode.into()); + let physical_key = KeyCode::Unidentified(native); + let logical_key = keycode_to_logical(keycode, native); + // TODO: maybe use getUnicodeChar to get the logical key + + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::KeyboardInput { + device_id, + event: event::KeyEvent { + state, + physical_key, + logical_key, + location: keycode_to_location(keycode), + repeat: key.repeat_count() > 0, + text: None, + platform_specific: KeyEventExtra {}, + }, + is_synthetic: false, + }, + }; + self.call_event_handler( + event_handler, + control_flow, + event, + ); + } + _ => {} + }; + input_queue.finish_event(event, handled); + } + } + } + */ + } + _ => {} + } + + // We need to treat every event as if they were from [`EventLoopProxy::send_event`] + // + // > All return values may also imply ALOOPER_POLL_WAKE. If you call this in a loop, + // > you must treat all return values as if they also indicated ALOOPER_POLL_WAKE. + // > https://developer.android.com/ndk/reference/group/looper#alooper_pollonce + while let Ok(event) = self.receiver.try_recv() { + self.call_event_handler(event_handler, control_flow, event::Event::UserEvent(event)); + } + + self.call_event_handler(event_handler, control_flow, event::Event::MainEventsCleared); + + if let Some(window_id) = resized_window_id { + if self.running.contains(&window_id) { + let size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Resized(size), + }; + self.call_event_handler(event_handler, control_flow, event); + } + } + + if let Some(window_id) = redraw_window_id { + if self.running.contains(&window_id) { + let event = event::Event::RedrawRequested(window_id); + self.call_event_handler(event_handler, control_flow, event); + } + } + + self.call_event_handler( + event_handler, + control_flow, + event::Event::RedrawEventsCleared, + ); + + match *control_flow { + ControlFlow::ExitWithCode(code) => { + self.first_event = poll(self.looper.poll_once_timeout(Duration::ZERO).unwrap()); + self.start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }; + + self.call_event_handler(event_handler, control_flow, event::Event::LoopDestroyed); + + return code; + } + ControlFlow::Poll => { + self.first_event = poll(self.looper.poll_all_timeout(Duration::ZERO).unwrap()); + self.start_cause = event::StartCause::Poll; + } + ControlFlow::Wait => { + self.first_event = poll(self.looper.poll_all().unwrap()); + self.start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + } + } + ControlFlow::WaitUntil(instant) => { + let start = Instant::now(); + let duration = if instant <= start { + Duration::default() + } else { + instant - start + }; + self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap()); + self.start_cause = if self.first_event.is_some() { + event::StartCause::WaitCancelled { + start, + requested_resume: Some(instant), + } + } else { + event::StartCause::ResumeTimeReached { + start, + requested_resume: instant, + } + } + } + } + } + } + + fn call_event_handler( + &self, + event_handler: &mut F, + control_flow: &mut ControlFlow, + event: event::Event<'_, T>, + ) where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + if let ControlFlow::ExitWithCode(code) = *control_flow { + event_handler( + event, + self.window_target(), + &mut ControlFlow::ExitWithCode(code), + ); + } else { + event_handler(event, self.window_target(), control_flow); + } + } + + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + queue: self.sender_to_clone.clone(), + looper: ForeignLooper::for_thread().expect("called from event loop thread"), + } + } +} + +pub struct EventLoopProxy { + queue: Sender, + looper: ForeignLooper, +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + let _ = self.queue.send(event); + self.looper.wake(); + Ok(()) + } +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + queue: self.queue.clone(), + looper: self.looper.clone(), + } + } +} + +#[derive(Clone)] +pub struct EventLoopWindowTarget { + _marker: std::marker::PhantomData, +} + +impl EventLoopWindowTarget { + pub fn primary_monitor(&self) -> Option { + Some(monitor::MonitorHandle { + inner: MonitorHandle, + }) + } + + #[inline] + pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { + warn!("`Window::monitor_from_point` is ignored on Android"); + None + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::Android( + rwh_06::AndroidDisplayHandle::new(), + )) + } + + pub fn cursor_position(&self) -> Result, error::ExternalError> { + debug!("`EventLoopWindowTarget::cursor_position` is ignored on Android"); + Ok((0, 0).into()) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct WindowId(ActivityId); + +impl WindowId { + pub fn dummy() -> Self { + WindowId(0) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct DeviceId; + +impl DeviceId { + pub fn dummy() -> Self { + DeviceId + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub activity_id: Option, + pub activity_name: Option, + pub created_by_activity_name: Option, +} + +pub struct Window { + activity_id: ActivityId, + activity_name: String, +} + +impl Window { + pub fn new( + _el: &EventLoopWindowTarget, + _window_attrs: window::WindowAttributes, + pl_attrs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + // FIXME this ignores requested window attributes + + let (activity_id, activity_name) = match pl_attrs.activity_name { + Some(activity_name) => { + let ctx = if let Some(created_by_activity_name) = pl_attrs.created_by_activity_name { + ndk_glue::CONTEXTS + .lock() + .unwrap() + .values() + .find(|ctx| ctx.activity_name == created_by_activity_name) + .cloned() + } else { + ndk_glue::main_android_context() + } + .ok_or_else(|| os_error!(OsError::NoAvailableActivity))?; + let activity_id = ctx + .create_activity(&activity_name) + .map_err(|error| os_error!(OsError::JniCallError(error)))?; + (activity_id, activity_name) + } + None => ndk_glue::next_available_activity() + .map(|(activity_id, ctx)| (activity_id, ctx.activity_name.clone())) + .ok_or_else(|| os_error!(OsError::NoAvailableActivity))?, + }; + Ok(Self { + activity_id, + activity_name, + }) + } + + pub fn id(&self) -> WindowId { + WindowId(self.activity_id) + } + + pub fn primary_monitor(&self) -> Option { + Some(monitor::MonitorHandle { + inner: MonitorHandle, + }) + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v + } + + #[inline] + pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { + warn!("`Window::monitor_from_point` is ignored on Android"); + None + } + + pub fn current_monitor(&self) -> Option { + Some(monitor::MonitorHandle { + inner: MonitorHandle, + }) + } + + pub fn scale_factor(&self) -> f64 { + MonitorHandle.scale_factor() + } + + pub fn request_redraw(&self) { + // TODO + } + + pub fn inner_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) + } + + pub fn outer_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) + } + + pub fn set_outer_position(&self, _position: Position) { + // no effect + } + + pub fn inner_size(&self) -> PhysicalSize { + self.outer_size() + } + + pub fn set_inner_size(&self, _size: Size) { + warn!("Cannot set window size on Android"); + } + + pub fn outer_size(&self) -> PhysicalSize { + MonitorHandle.size() + } + + pub fn set_min_inner_size(&self, _: Option) {} + pub fn set_max_inner_size(&self, _: Option) {} + pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) {} + + pub fn set_title(&self, _title: &str) {} + pub fn title(&self) -> String { + String::new() + } + + pub fn set_visible(&self, _visibility: bool) {} + + pub fn set_focus(&self) { + //FIXME: implementation goes here + warn!("set_focus not yet implemented on Android"); + } + + pub fn set_focusable(&self, _focusable: bool) { + warn!("set_focusable not yet implemented on Android"); + } + + pub fn is_focused(&self) -> bool { + log::warn!("`Window::is_focused` is ignored on Android"); + false + } + + pub fn is_always_on_top(&self) -> bool { + log::warn!("`Window::is_always_on_top` is ignored on Android"); + false + } + + pub fn set_resizable(&self, _resizeable: bool) { + warn!("`Window::set_resizable` is ignored on Android") + } + + pub fn set_minimizable(&self, _minimizable: bool) { + warn!("`Window::set_minimizable` is ignored on Android") + } + + pub fn set_maximizable(&self, _maximizable: bool) { + warn!("`Window::set_maximizable` is ignored on Android") + } + + pub fn set_closable(&self, _closable: bool) { + warn!("`Window::set_closable` is ignored on Android") + } + + pub fn set_minimized(&self, _minimized: bool) {} + + pub fn set_maximized(&self, _maximized: bool) {} + + pub fn is_maximized(&self) -> bool { + false + } + + pub fn is_minimized(&self) -> bool { + false + } + + pub fn is_visible(&self) -> bool { + log::warn!("`Window::is_visible` is ignored on Android"); + false + } + + pub fn is_resizable(&self) -> bool { + warn!("`Window::is_resizable` is ignored on Android"); + false + } + + pub fn is_minimizable(&self) -> bool { + warn!("`Window::is_minimizable` is ignored on Android"); + false + } + + pub fn is_maximizable(&self) -> bool { + warn!("`Window::is_maximizable` is ignored on Android"); + false + } + + pub fn is_closable(&self) -> bool { + warn!("`Window::is_closable` is ignored on Android"); + false + } + + pub fn is_decorated(&self) -> bool { + warn!("`Window::is_decorated` is ignored on Android"); + false + } + + pub fn set_fullscreen(&self, _monitor: Option) { + warn!("Cannot set fullscreen on Android"); + } + + pub fn fullscreen(&self) -> Option { + None + } + + pub fn set_decorations(&self, _decorations: bool) {} + + pub fn set_always_on_bottom(&self, _always_on_bottom: bool) {} + + pub fn set_always_on_top(&self, _always_on_top: bool) {} + + pub fn set_window_icon(&self, _window_icon: Option) {} + + pub fn set_ime_position(&self, _position: Position) {} + + pub fn request_user_attention(&self, _request_type: Option) {} + + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} + + pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn set_cursor_visible(&self, _: bool) {} + + pub fn drag_window(&self) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn drag_resize_window( + &self, + _direction: ResizeDirection, + ) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn set_background_color(&self, _color: Option) {} + + pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + + pub fn cursor_position(&self) -> Result, error::ExternalError> { + debug!("`Window::cursor_position` is ignored on Android"); + Ok((0, 0).into()) + } + + #[cfg(feature = "rwh_04")] + pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { + // TODO: Use main activity instead? + let mut handle = rwh_04::AndroidNdkHandle::empty(); + if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { + handle.a_native_window = w.as_obj().as_raw() as *mut _; + } else { + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); + }; + rwh_04::RawWindowHandle::AndroidNdk(handle) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { + // TODO: Use main activity instead? + let mut handle = rwh_05::AndroidNdkWindowHandle::empty(); + if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { + handle.a_native_window = w.as_obj().as_raw() as *mut _; + } else { + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); + }; + rwh_05::RawWindowHandle::AndroidNdk(handle) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + pub fn raw_window_handle_rwh_06(&self) -> Result { + // TODO: Use main activity instead? + if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { + let native_window = + unsafe { std::ptr::NonNull::new_unchecked(w.as_obj().as_raw() as *mut _) }; + // native_window shuldn't be null + let handle = rwh_06::AndroidNdkWindowHandle::new(native_window); + Ok(rwh_06::RawWindowHandle::AndroidNdk(handle)) + } else { + Err(rwh_06::HandleError::Unavailable) + } + } + + #[cfg(feature = "rwh_06")] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::Android( + rwh_06::AndroidDisplayHandle::new(), + )) + } + pub fn config(&self) -> Configuration { + CONFIG.read().unwrap().clone() + } + + pub fn content_rect(&self) -> Rect { + ndk_glue::content_rect() + } + + pub fn activity_name(&self) -> &str { + &self.activity_name + } + + pub fn theme(&self) -> Theme { + Theme::Light + } +} + +pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MonitorHandle; + +fn current_window_metrics_size( + env: &mut jni::JNIEnv<'_>, + window_manager: &jni::objects::JObject<'_>, +) -> Option> { + let metrics = jni_call_method!( + env, + window_manager, + "getCurrentWindowMetrics", + "()Landroid/view/WindowMetrics;", + l + ) + .ok()?; + + let rect = jni_call_method!(env, &metrics, "getBounds", "()Landroid/graphics/Rect;", l).ok()?; + let width = jni_call_method!(env, &rect, "width", "()I", i).ok()?; + let height = jni_call_method!(env, &rect, "height", "()I", i).ok()?; + Some(PhysicalSize::new(width as u32, height as u32)) +} + +fn legacy_display_metrics_size( + env: &mut jni::JNIEnv<'_>, + window_manager: &jni::objects::JObject<'_>, +) -> Option> { + let display = jni_call_method!( + env, + window_manager, + "getDefaultDisplay", + "()Landroid/view/Display;", + l + ) + .ok()?; + let display_metrics = env + .new_object("android/util/DisplayMetrics", "()V", &[]) + .ok()?; + + jni_call_method!( + env, + &display, + "getRealMetrics", + "(Landroid/util/DisplayMetrics;)V", + &[(&display_metrics).into()], + v + ) + .ok()?; + + let width = env + .get_field(&display_metrics, "widthPixels", "I") + .and_then(|v| v.i()) + .ok()?; + let height = env + .get_field(&display_metrics, "heightPixels", "I") + .and_then(|v| v.i()) + .ok()?; + + Some(PhysicalSize::new(width as u32, height as u32)) +} + +impl MonitorHandle { + pub fn name(&self) -> Option { + Some("Android Device".to_owned()) + } + + pub fn size(&self) -> PhysicalSize { + // TODO: support multi-window (main_window_manager might be incorrect) + + // TODO decide how to get JNIENV + let window_manager = ndk_glue::main_window_manager(); + let Some(w) = window_manager.as_ref() else { + return PhysicalSize::new(0, 0); + }; + let Some(ctx) = ndk_glue::main_android_context() else { + return PhysicalSize::new(0, 0); + }; + let Ok(vm) = (unsafe { jni::JavaVM::from_raw(ctx.java_vm.cast()) }) else { + return PhysicalSize::new(0, 0); + }; + let Ok(mut env) = vm.attach_current_thread() else { + return PhysicalSize::new(0, 0); + }; + let window_manager = w.as_obj(); + + let sdk_int = env + .get_static_field("android/os/Build$VERSION", "SDK_INT", "I") + .and_then(|v| v.i()) + .unwrap_or(0); + + let metrics_size = if sdk_int >= 30 { + current_window_metrics_size(&mut env, window_manager) + } else { + legacy_display_metrics_size(&mut env, window_manager) + }; + + if metrics_size.is_none() && env.exception_check().unwrap_or(false) { + let _ = env.exception_clear(); + } + + metrics_size.unwrap_or(PhysicalSize::new(0, 0)) + } + + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() + } + + pub fn scale_factor(&self) -> f64 { + let config = CONFIG.read().unwrap(); + config + .density() + .map(|dpi| dpi as f64 / 160.0) + .unwrap_or(1.0) + } + + pub fn video_modes(&self) -> impl Iterator { + let size = self.size().into(); + // FIXME this is not the real refresh rate + // (it is guarunteed to support 32 bit color though) + let v = vec![monitor::VideoMode { + video_mode: VideoMode { + size, + bit_depth: 32, + refresh_rate: 60, + monitor: self.clone(), + }, + }]; + + v.into_iter() + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct VideoMode { + size: (u32, u32), + bit_depth: u16, + refresh_rate: u16, + monitor: MonitorHandle, +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: self.monitor.clone(), + } + } +} + +/* +fn keycode_to_logical(keycode: ndk::event::Keycode, native: NativeKeyCode) -> Key<'static> { + use ndk::event::Keycode::*; + + // The android `Keycode` is sort-of layout dependent. More specifically + // if I press the Z key using a US layout, then I get KEYCODE_Z, + // but if I press the same key after switching to a HUN layout, I get + // KEYCODE_Y. + // + // To prevents us from using this value to determine the `physical_key` + // (also know as winit's `KeyCode`) + // + // Unfortunately the documentation says that the scancode values + // "are not reliable and vary from device to device". Which seems to mean + // that there's no way to reliably get the physical_key on android. + + match keycode { + Unknown => Key::Unidentified(native), + + // Can be added on demand + SoftLeft => Key::Unidentified(native), + SoftRight => Key::Unidentified(native), + + // Using `BrowserHome` instead of `GoHome` according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + Home => Key::BrowserHome, + Back => Key::BrowserBack, + Call => Key::Call, + Endcall => Key::EndCall, + + //------------------------------------------------------------------------------- + // Reporting unidentified, because the specific character is layout dependent. + // (I'm not sure though) + Keycode0 => Key::Unidentified(native), + Keycode1 => Key::Unidentified(native), + Keycode2 => Key::Unidentified(native), + Keycode3 => Key::Unidentified(native), + Keycode4 => Key::Unidentified(native), + Keycode5 => Key::Unidentified(native), + Keycode6 => Key::Unidentified(native), + Keycode7 => Key::Unidentified(native), + Keycode8 => Key::Unidentified(native), + Keycode9 => Key::Unidentified(native), + Star => Key::Unidentified(native), + Pound => Key::Unidentified(native), + A => Key::Unidentified(native), + B => Key::Unidentified(native), + C => Key::Unidentified(native), + D => Key::Unidentified(native), + E => Key::Unidentified(native), + F => Key::Unidentified(native), + G => Key::Unidentified(native), + H => Key::Unidentified(native), + I => Key::Unidentified(native), + J => Key::Unidentified(native), + K => Key::Unidentified(native), + L => Key::Unidentified(native), + M => Key::Unidentified(native), + N => Key::Unidentified(native), + O => Key::Unidentified(native), + P => Key::Unidentified(native), + Q => Key::Unidentified(native), + R => Key::Unidentified(native), + S => Key::Unidentified(native), + T => Key::Unidentified(native), + U => Key::Unidentified(native), + V => Key::Unidentified(native), + W => Key::Unidentified(native), + X => Key::Unidentified(native), + Y => Key::Unidentified(native), + Z => Key::Unidentified(native), + Comma => Key::Unidentified(native), + Period => Key::Unidentified(native), + Grave => Key::Unidentified(native), + Minus => Key::Unidentified(native), + Equals => Key::Unidentified(native), + LeftBracket => Key::Unidentified(native), + RightBracket => Key::Unidentified(native), + Backslash => Key::Unidentified(native), + Semicolon => Key::Unidentified(native), + Apostrophe => Key::Unidentified(native), + Slash => Key::Unidentified(native), + At => Key::Unidentified(native), + Plus => Key::Unidentified(native), + //------------------------------------------------------------------------------- + DpadUp => Key::ArrowUp, + DpadDown => Key::ArrowDown, + DpadLeft => Key::ArrowLeft, + DpadRight => Key::ArrowRight, + DpadCenter => Key::Enter, + + VolumeUp => Key::AudioVolumeUp, + VolumeDown => Key::AudioVolumeDown, + Power => Key::Power, + Camera => Key::Camera, + Clear => Key::Clear, + + AltLeft => Key::Alt, + AltRight => Key::Alt, + ShiftLeft => Key::Shift, + ShiftRight => Key::Shift, + Tab => Key::Tab, + Space => Key::Space, + Sym => Key::Symbol, + Explorer => Key::LaunchWebBrowser, + Envelope => Key::LaunchMail, + Enter => Key::Enter, + Del => Key::Backspace, + + // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM + Num => Key::Alt, + + Headsethook => Key::HeadsetHook, + Focus => Key::CameraFocus, + + Menu => Key::Unidentified(native), + + Notification => Key::Notification, + Search => Key::BrowserSearch, + MediaPlayPause => Key::MediaPlayPause, + MediaStop => Key::MediaStop, + MediaNext => Key::MediaTrackNext, + MediaPrevious => Key::MediaTrackPrevious, + MediaRewind => Key::MediaRewind, + MediaFastForward => Key::MediaFastForward, + Mute => Key::MicrophoneVolumeMute, + PageUp => Key::PageUp, + PageDown => Key::PageDown, + Pictsymbols => Key::Unidentified(native), + SwitchCharset => Key::Unidentified(native), + + // ----------------------------------------------------------------- + // Gamepad events should be exposed through a separate API, not + // keyboard events + ButtonA => Key::Unidentified(native), + ButtonB => Key::Unidentified(native), + ButtonC => Key::Unidentified(native), + ButtonX => Key::Unidentified(native), + ButtonY => Key::Unidentified(native), + ButtonZ => Key::Unidentified(native), + ButtonL1 => Key::Unidentified(native), + ButtonR1 => Key::Unidentified(native), + ButtonL2 => Key::Unidentified(native), + ButtonR2 => Key::Unidentified(native), + ButtonThumbl => Key::Unidentified(native), + ButtonThumbr => Key::Unidentified(native), + ButtonStart => Key::Unidentified(native), + ButtonSelect => Key::Unidentified(native), + ButtonMode => Key::Unidentified(native), + // ----------------------------------------------------------------- + Escape => Key::Escape, + ForwardDel => Key::Delete, + CtrlLeft => Key::Control, + CtrlRight => Key::Control, + CapsLock => Key::CapsLock, + ScrollLock => Key::ScrollLock, + MetaLeft => Key::Super, + MetaRight => Key::Super, + Function => Key::Fn, + Sysrq => Key::PrintScreen, + Break => Key::Pause, + MoveHome => Key::Home, + MoveEnd => Key::End, + Insert => Key::Insert, + Forward => Key::BrowserForward, + MediaPlay => Key::MediaPlay, + MediaPause => Key::MediaPause, + MediaClose => Key::MediaClose, + MediaEject => Key::Eject, + MediaRecord => Key::MediaRecord, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + NumLock => Key::NumLock, + Numpad0 => Key::Unidentified(native), + Numpad1 => Key::Unidentified(native), + Numpad2 => Key::Unidentified(native), + Numpad3 => Key::Unidentified(native), + Numpad4 => Key::Unidentified(native), + Numpad5 => Key::Unidentified(native), + Numpad6 => Key::Unidentified(native), + Numpad7 => Key::Unidentified(native), + Numpad8 => Key::Unidentified(native), + Numpad9 => Key::Unidentified(native), + NumpadDivide => Key::Unidentified(native), + NumpadMultiply => Key::Unidentified(native), + NumpadSubtract => Key::Unidentified(native), + NumpadAdd => Key::Unidentified(native), + NumpadDot => Key::Unidentified(native), + NumpadComma => Key::Unidentified(native), + NumpadEnter => Key::Unidentified(native), + NumpadEquals => Key::Unidentified(native), + NumpadLeftParen => Key::Unidentified(native), + NumpadRightParen => Key::Unidentified(native), + + VolumeMute => Key::AudioVolumeMute, + Info => Key::Info, + ChannelUp => Key::ChannelUp, + ChannelDown => Key::ChannelDown, + ZoomIn => Key::ZoomIn, + ZoomOut => Key::ZoomOut, + Tv => Key::TV, + Window => Key::Unidentified(native), + Guide => Key::Guide, + Dvr => Key::DVR, + Bookmark => Key::BrowserFavorites, + Captions => Key::ClosedCaptionToggle, + Settings => Key::Settings, + TvPower => Key::TVPower, + TvInput => Key::TVInput, + StbPower => Key::STBPower, + StbInput => Key::STBInput, + AvrPower => Key::AVRPower, + AvrInput => Key::AVRInput, + ProgRed => Key::ColorF0Red, + ProgGreen => Key::ColorF1Green, + ProgYellow => Key::ColorF2Yellow, + ProgBlue => Key::ColorF3Blue, + AppSwitch => Key::AppSwitch, + Button1 => Key::Unidentified(native), + Button2 => Key::Unidentified(native), + Button3 => Key::Unidentified(native), + Button4 => Key::Unidentified(native), + Button5 => Key::Unidentified(native), + Button6 => Key::Unidentified(native), + Button7 => Key::Unidentified(native), + Button8 => Key::Unidentified(native), + Button9 => Key::Unidentified(native), + Button10 => Key::Unidentified(native), + Button11 => Key::Unidentified(native), + Button12 => Key::Unidentified(native), + Button13 => Key::Unidentified(native), + Button14 => Key::Unidentified(native), + Button15 => Key::Unidentified(native), + Button16 => Key::Unidentified(native), + LanguageSwitch => Key::GroupNext, + MannerMode => Key::MannerMode, + Keycode3dMode => Key::TV3DMode, + Contacts => Key::LaunchContacts, + Calendar => Key::LaunchCalendar, + Music => Key::LaunchMusicPlayer, + Calculator => Key::LaunchApplication2, + ZenkakuHankaku => Key::ZenkakuHankaku, + Eisu => Key::Eisu, + Muhenkan => Key::NonConvert, + Henkan => Key::Convert, + KatakanaHiragana => Key::HiraganaKatakana, + Yen => Key::Unidentified(native), + Ro => Key::Unidentified(native), + Kana => Key::KanjiMode, + Assist => Key::Unidentified(native), + BrightnessDown => Key::BrightnessDown, + BrightnessUp => Key::BrightnessUp, + MediaAudioTrack => Key::MediaAudioTrack, + Sleep => Key::Standby, + Wakeup => Key::WakeUp, + Pairing => Key::Pairing, + MediaTopMenu => Key::MediaTopMenu, + Keycode11 => Key::Unidentified(native), + Keycode12 => Key::Unidentified(native), + LastChannel => Key::MediaLast, + TvDataService => Key::TVDataService, + VoiceAssist => Key::VoiceDial, + TvRadioService => Key::TVRadioService, + TvTeletext => Key::Teletext, + TvNumberEntry => Key::TVNumberEntry, + TvTerrestrialAnalog => Key::TVTerrestrialAnalog, + TvTerrestrialDigital => Key::TVTerrestrialDigital, + TvSatellite => Key::TVSatellite, + TvSatelliteBs => Key::TVSatelliteBS, + TvSatelliteCs => Key::TVSatelliteCS, + TvSatelliteService => Key::TVSatelliteToggle, + TvNetwork => Key::TVNetwork, + TvAntennaCable => Key::TVAntennaCable, + TvInputHdmi1 => Key::TVInputHDMI1, + TvInputHdmi2 => Key::TVInputHDMI2, + TvInputHdmi3 => Key::TVInputHDMI3, + TvInputHdmi4 => Key::TVInputHDMI4, + TvInputComposite1 => Key::TVInputComposite1, + TvInputComposite2 => Key::TVInputComposite2, + TvInputComponent1 => Key::TVInputComponent1, + TvInputComponent2 => Key::TVInputComponent2, + TvInputVga1 => Key::TVInputVGA1, + TvAudioDescription => Key::TVAudioDescription, + TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, + TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, + TvZoomMode => Key::ZoomToggle, + TvContentsMenu => Key::TVContentsMenu, + TvMediaContextMenu => Key::TVMediaContext, + TvTimerProgramming => Key::TVTimer, + Help => Key::Help, + NavigatePrevious => Key::NavigatePrevious, + NavigateNext => Key::NavigateNext, + NavigateIn => Key::NavigateIn, + NavigateOut => Key::NavigateOut, + StemPrimary => Key::Unidentified(native), + Stem1 => Key::Unidentified(native), + Stem2 => Key::Unidentified(native), + Stem3 => Key::Unidentified(native), + DpadUpLeft => Key::Unidentified(native), + DpadDownLeft => Key::Unidentified(native), + DpadUpRight => Key::Unidentified(native), + DpadDownRight => Key::Unidentified(native), + MediaSkipForward => Key::MediaSkipForward, + MediaSkipBackward => Key::MediaSkipBackward, + MediaStepForward => Key::MediaStepForward, + MediaStepBackward => Key::MediaStepBackward, + SoftSleep => Key::Unidentified(native), + Cut => Key::Cut, + Copy => Key::Copy, + Paste => Key::Paste, + SystemNavigationUp => Key::Unidentified(native), + SystemNavigationDown => Key::Unidentified(native), + SystemNavigationLeft => Key::Unidentified(native), + SystemNavigationRight => Key::Unidentified(native), + AllApps => Key::Unidentified(native), + Refresh => Key::BrowserRefresh, + ThumbsUp => Key::Unidentified(native), + ThumbsDown => Key::Unidentified(native), + ProfileSwitch => Key::Unidentified(native), + _ => Key::Unidentified(native), + } +} + +fn keycode_to_location(keycode: ndk::event::Keycode) -> KeyLocation { + use ndk::event::Keycode::*; + + match keycode { + AltLeft => KeyLocation::Left, + AltRight => KeyLocation::Right, + ShiftLeft => KeyLocation::Left, + ShiftRight => KeyLocation::Right, + + // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM + Num => KeyLocation::Left, + + CtrlLeft => KeyLocation::Left, + CtrlRight => KeyLocation::Right, + MetaLeft => KeyLocation::Left, + MetaRight => KeyLocation::Right, + + NumLock => KeyLocation::Numpad, + Numpad0 => KeyLocation::Numpad, + Numpad1 => KeyLocation::Numpad, + Numpad2 => KeyLocation::Numpad, + Numpad3 => KeyLocation::Numpad, + Numpad4 => KeyLocation::Numpad, + Numpad5 => KeyLocation::Numpad, + Numpad6 => KeyLocation::Numpad, + Numpad7 => KeyLocation::Numpad, + Numpad8 => KeyLocation::Numpad, + Numpad9 => KeyLocation::Numpad, + NumpadDivide => KeyLocation::Numpad, + NumpadMultiply => KeyLocation::Numpad, + NumpadSubtract => KeyLocation::Numpad, + NumpadAdd => KeyLocation::Numpad, + NumpadDot => KeyLocation::Numpad, + NumpadComma => KeyLocation::Numpad, + NumpadEnter => KeyLocation::Numpad, + NumpadEquals => KeyLocation::Numpad, + NumpadLeftParen => KeyLocation::Numpad, + NumpadRightParen => KeyLocation::Numpad, + + _ => KeyLocation::Standard, + } +} +*/ + +// FIXME: Implement android +pub fn keycode_to_scancode(_code: KeyCode) -> Option { + None +} + +pub fn keycode_from_scancode(_scancode: u32) -> KeyCode { + KeyCode::Unidentified(NativeKeyCode::Unidentified) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs new file mode 100644 index 000000000..765457e68 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs @@ -0,0 +1,802 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::window::WindowId; +use crossbeam_channel::Sender; +pub use jni::{ + self, + errors::Result as JniResult, + objects::{GlobalRef, JClass, JMap, JObject, JString}, + sys::jobject, + JNIEnv, +}; +use log::Level; +pub use ndk; +use ndk::{ + input_queue::InputQueue, + looper::{FdEvent, ForeignLooper, ThreadLooper}, +}; +use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; +use std::{ + collections::{BTreeMap, HashSet}, + ffi::{c_void, CStr, CString}, + fs::File, + io::{BufRead, BufReader}, + os::unix::prelude::*, + sync::{Arc, Condvar, LazyLock, Mutex, OnceLock, RwLock, RwLockReadGuard}, + thread, + time::Duration, +}; + +/// Android pacakge name that could be used to reference classes +/// in the android project. +pub static PACKAGE: OnceLock<&str> = OnceLock::new(); + +/// Character set for encoding text content in data URLs. +/// Encodes all control characters and special characters that might cause issues in URLs. +const DATA_URL_ENCODING_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'%') + .add(b'&') + .add(b'<') + .add(b'>') + .add(b'?') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'`') + .add(b'{') + .add(b'|') + .add(b'}'); + +/// Generate JNI compilant functions that are necessary for +/// building android apps with tao. +/// +/// Arguments in order: +/// 1. android app domain name in reverse snake_case as an ident (for ex: com_example) +/// 2. android package anme (for ex: wryapp) +/// 3. the android activity that has external linking for the following functions and calls them: +/// - `private external fun onFirstActivityCreate()` +/// - `private external fun onCreate(activity: WryActivity)` +/// - `private external fun onStart(activity: WryActivity)` +/// - `private external fun onResume(activity: WryActivity)` +/// - `private external fun onPause(activity: WryActivity)` +/// - `private external fun onStop(activity: WryActivity)` +/// - `private external fun onDestroy(activity: WryActivity)` +/// - `private external fun onWindowFocusChanged(activity: WryActivity, focus: Boolean)` +/// - `private external fun onLowMemory()` +/// - `private external fun onNewIntent(intent: Intent)` +/// 4. an on_activity_create function that will be ran once after the `onCreate` function above. +/// 5. the main entry point of your android application that runs in `onFirstActivityCreate` after `onCreate` and `on_activity_create`. +#[rustfmt::skip] +#[macro_export] +macro_rules! android_binding { + ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident) => { + ::tao::android_binding!($domain, $package, $activity, $on_activity_create, $main, ::tao) + }; + ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident, $tao:path) => {{ + // NOTE: be careful when changing how this use statement is written + use $tao::{platform::android::prelude::android_fn, platform::android::prelude::*}; + fn _____tao_store_package_name__() { + PACKAGE.get_or_init(move || generate_package_name!($domain, $package)); + } + + android_fn!( + $domain, + $package, + $activity, + onFirstActivityCreate, + [], + __VOID__, + [$main], + ); + android_fn!( + $domain, + $package, + $activity, + onCreate, + [JObject], + __VOID__, + [$on_activity_create], + _____tao_store_package_name__, + ); + android_fn!($domain, $package, $activity, onStart, [JObject]); + android_fn!($domain, $package, $activity, onStop, [JObject]); + android_fn!($domain, $package, $activity, onResume, [JObject]); + android_fn!($domain, $package, $activity, onPause, [JObject]); + android_fn!($domain, $package, $activity, onDestroy, [JObject]); + android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject, i32]); + android_fn!($domain, $package, $activity, onLowMemory, []); + android_fn!($domain, $package, $activity, onNewIntent, [JObject]); + }}; +} + +/// `ndk-glue` macros register the reading end of an event pipe with the +/// main [`ThreadLooper`] under this `ident`. +/// When returned from [`ThreadLooper::poll_*`](ThreadLooper::poll_once) +/// an event can be retrieved from [`poll_events()`]. +pub const NDK_GLUE_LOOPER_EVENT_PIPE_IDENT: i32 = 0; + +/// The [`InputQueue`] received from Android is registered with the main +/// [`ThreadLooper`] under this `ident`. +/// When returned from [`ThreadLooper::poll_*`](ThreadLooper::poll_once) +/// an event can be retrieved from [`input_queue()`]. +pub const NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT: i32 = 1; + +pub fn android_log(level: Level, tag: &CStr, msg: &CStr) { + let prio = match level { + Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, + Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, + Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, + Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, + Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, + }; + unsafe { + ndk_sys::__android_log_write(prio.0 as _, tag.as_ptr(), msg.as_ptr()); + } +} + +fn find_class<'a>( + env: &mut JNIEnv<'a>, + activity: &JObject<'_>, + name: String, +) -> Result, super::JniCallError> { + let class_name = env.new_string(name.replace('/', "."))?; + let my_class = jni_call_method!( + env, + activity, + "getAppClass", + "(Ljava/lang/String;)Ljava/lang/Class;", + &[(&class_name).into()], + l + )?; + Ok(my_class.into()) +} + +#[derive(Clone, Debug)] +pub struct AndroidContext { + pub java_vm: *mut c_void, + pub context_jobject: *mut c_void, + pub activity_name: String, + pub window_created: bool, +} + +impl AndroidContext { + pub fn create_activity(&self, activity_name: &str) -> Result { + let vm = unsafe { jni::JavaVM::from_raw(self.java_vm.cast()) }?; + let mut env = vm.attach_current_thread_as_daemon()?; + let main_activity = unsafe { JObject::from_raw(self.context_jobject.cast()) }; + + let activity_class = find_class( + &mut env, + &main_activity, + format!("{}/{activity_name}", PACKAGE.get().unwrap()), + )?; + + let activity_id = jni_call_method!( + env, + &main_activity, + "startActivity", + "(Ljava/lang/Class;)I", + &[(&activity_class).into()], + i + )?; + + let (tx, rx) = crossbeam_channel::bounded(1); + ACTIVITY_CREATED_SENDERS + .lock() + .unwrap() + .insert(activity_id, tx); + rx.recv_timeout(Duration::from_secs(5)).map_err(|e| { + log::error!("failed to create activity {activity_name}: {e}"); + super::JniCallError::new( + jni::errors::Error::JniCall(jni::errors::JniError::Unknown), + Some(e.to_string()), + ) + })?; + + Ok(activity_id) + } +} + +unsafe impl Send for AndroidContext {} +unsafe impl Sync for AndroidContext {} + +pub type ActivityId = i32; + +pub(crate) static CONTEXTS: LazyLock>> = + LazyLock::new(Default::default); +// Keeping a reference so we can store it in ndk-context +static APPLICATION_CONTEXT: OnceLock = OnceLock::new(); +static WINDOW_MANAGER: LazyLock>> = + LazyLock::new(Default::default); +pub(crate) static ACTIVITY_CREATED_SENDERS: LazyLock>>> = + LazyLock::new(Default::default); +static INTENT_URLS: LazyLock>> = LazyLock::new(Default::default); +static INPUT_QUEUE: LazyLock>> = LazyLock::new(Default::default); +static CONTENT_RECT: LazyLock> = LazyLock::new(Default::default); +static LOOPER: LazyLock>> = LazyLock::new(Default::default); +static RESUMED_ACTIVITIES: LazyLock>> = LazyLock::new(Default::default); + +pub fn main_window_manager() -> Option { + WINDOW_MANAGER.lock().unwrap().values().next().cloned() +} + +pub fn activity_window_manager(activity_id: ActivityId) -> Option { + WINDOW_MANAGER.lock().unwrap().get(&activity_id).cloned() +} + +pub fn window_manager(activity_id: ActivityId) -> Option { + WINDOW_MANAGER.lock().unwrap().get(&activity_id).cloned() +} + +pub fn input_queue() -> RwLockReadGuard<'static, Option> { + INPUT_QUEUE.read().unwrap() +} + +pub fn content_rect() -> Rect { + CONTENT_RECT.read().unwrap().clone() +} + +fn activity_id(env: &mut JNIEnv<'_>, activity: &JObject<'_>) -> ActivityId { + env + .call_method(activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap() +} + +pub fn main_android_context() -> Option { + CONTEXTS.lock().unwrap().values().next().cloned() +} + +pub fn next_available_activity() -> Option<(ActivityId, AndroidContext)> { + CONTEXTS + .lock() + .unwrap() + .iter() + .filter(|(_, ctx)| !ctx.window_created) + .next() + .map(|(id, ctx)| (*id, ctx.clone())) +} + +pub static PIPE: LazyLock<[OwnedFd; 2]> = LazyLock::new(|| { + let mut pipe: [RawFd; 2] = Default::default(); + unsafe { libc::pipe(pipe.as_mut_ptr()) }; + pipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) +}); + +pub fn poll_events() -> Option { + unsafe { + let size = std::mem::size_of::(); + let mut event = Event::LowMemory; + if libc::read(PIPE[0].as_raw_fd(), &mut event as *mut _ as *mut _, size) + == size as libc::ssize_t + { + Some(event) + } else { + None + } + } +} + +pub fn take_intent_urls() -> Vec { + INTENT_URLS.lock().unwrap().drain(..).collect() +} + +unsafe fn wake(event: Event) { + log::trace!("{:?}", event); + let size = std::mem::size_of::(); + let res = libc::write(PIPE[1].as_raw_fd(), &event as *const _ as *const _, size); + assert_eq!(res, size as libc::ssize_t); +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Rect { + pub left: u32, + pub top: u32, + pub right: u32, + pub bottom: u32, +} + +// event must be copyable to be used in the event loop +#[derive(Clone, Debug, Eq, PartialEq, Copy)] +#[repr(u8)] +pub enum Event { + Resume { id: WindowId }, + Pause { id: WindowId }, + LowMemory, + WindowEvent { id: WindowId, event: WindowEvent }, + ContentRectChanged, + Opened, +} + +#[derive(Clone, Debug, Eq, PartialEq, Copy)] +#[repr(u8)] +pub enum WindowEvent { + Focused(bool), + Created, + Started, + Resized, + RedrawNeeded, + Stopped, + Destroyed, +} + +#[allow(non_snake_case)] +pub unsafe fn onFirstActivityCreate(_env: JNIEnv, _: JClass, main: fn()) { + let logpipe = { + let mut logpipe: [RawFd; 2] = Default::default(); + libc::pipe(logpipe.as_mut_ptr()); + libc::dup2(logpipe[1], libc::STDOUT_FILENO); + libc::dup2(logpipe[1], libc::STDERR_FILENO); + + logpipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) + }; + thread::spawn(move || { + let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); + let file = File::from_raw_fd(logpipe[0].as_raw_fd()); + let mut reader = BufReader::new(file); + let mut buffer = String::new(); + loop { + buffer.clear(); + if let Ok(len) = reader.read_line(&mut buffer) { + if len == 0 { + break; + } else if let Ok(msg) = CString::new(buffer.clone()) { + android_log(Level::Info, tag, &msg); + } + } + } + }); + + let looper_ready = Arc::new(Condvar::new()); + let signal_looper_ready = looper_ready.clone(); + + thread::spawn(move || { + let looper = ThreadLooper::prepare(); + let foreign = looper.into_foreign(); + foreign + .add_fd( + PIPE[0].as_fd(), + NDK_GLUE_LOOPER_EVENT_PIPE_IDENT, + FdEvent::INPUT, + std::ptr::null_mut(), + ) + .unwrap(); + + { + let mut locked_looper = LOOPER.lock().unwrap(); + *locked_looper = Some(foreign); + signal_looper_ready.notify_one(); + } + + main(); + }); + + // Don't return from this function (`ANativeActivity_onCreate`) until the thread + // has created its `ThreadLooper` and assigned it to the static `LOOPER` variable. + let locked_looper = LOOPER.lock().unwrap(); + let _mutex_guard = looper_ready + .wait_while(locked_looper, |looper| looper.is_none()) + .unwrap(); +} + +#[allow(non_snake_case)] +pub unsafe fn onCreate( + mut env: JNIEnv, + _jclass: JClass, + activity: JObject, + setup: unsafe fn(&str, JNIEnv, &ThreadLooper, GlobalRef), +) { + let intent = + jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).unwrap(); + + let vm = env.get_java_vm().unwrap(); + + APPLICATION_CONTEXT.get_or_init(|| { + let application = jni_call_method!( + env, + &activity, + "getApplicationContext", + "()Landroid/content/Context;", + l + ) + .unwrap(); + let application = env.new_global_ref(application).unwrap(); + ndk_context::initialize_android_context( + vm.get_java_vm_pointer() as *mut _, + application.as_obj().as_raw() as *mut _, + ); + application + }); + + let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); + + let activity_name: JString = jni_call_method!( + env, + &activity, + "getLocalClassName", + "()Ljava/lang/String;", + l + ) + .unwrap() + .into(); + let activity_name = env + .get_string(&activity_name) + .unwrap() + .to_string_lossy() + .to_string(); + + // Initialize global context + let window_manager = jni_call_method!( + env, + &activity, + "getWindowManager", + "()Landroid/view/WindowManager;", + l + ) + .unwrap(); + let window_manager = env.new_global_ref(window_manager).unwrap(); + WINDOW_MANAGER + .lock() + .unwrap() + .insert(activity_id, window_manager); + let activity = env.new_global_ref(activity).unwrap(); + let thread_env = vm.attach_current_thread_as_daemon().unwrap(); + + CONTEXTS.lock().unwrap().insert( + activity_id, + AndroidContext { + java_vm: vm.get_java_vm_pointer() as *mut _, + context_jobject: activity.as_obj().as_raw() as *mut _, + activity_name, + window_created: false, + }, + ); + let looper = ThreadLooper::for_thread().unwrap(); + setup(PACKAGE.get().unwrap(), thread_env, &looper, activity); + + if let Some(tx) = ACTIVITY_CREATED_SENDERS + .lock() + .unwrap() + .remove(&activity_id) + { + let _ = tx.send(()); + } + + handle_intent(env, intent); +} + +#[allow(non_snake_case)] +pub unsafe fn onResume(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + let did_resume = !RESUMED_ACTIVITIES.lock().unwrap().insert(activity_id); + // first Activity onResume() is called even after onCreate() + // to match the iOS implementation, we ignore the first resume event + if did_resume { + wake(Event::Resume { + id: WindowId(super::WindowId(activity_id)), + }); + } +} + +#[allow(non_snake_case)] +pub unsafe fn onPause(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::Pause { + id: WindowId(super::WindowId(activity_id)), + }); +} + +#[allow(non_snake_case)] +pub unsafe fn onWindowFocusChanged( + mut env: JNIEnv, + _: JClass, + activity: JObject, + has_focus: libc::c_int, +) { + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); + let event = Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Focused(has_focus != 0), + }; + wake(event); +} + +#[allow(non_snake_case)] +pub unsafe fn onNewIntent(env: JNIEnv, _: JClass, intent: JObject) { + handle_intent(env, intent); +} + +pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { + let action = jni_call_method!(env, &intent, "getAction", "()Ljava/lang/String;", l) + .unwrap() + .into(); + let action = env + .get_string(&action) + .map(|action| action.to_string_lossy().to_string()); + + let Ok(action) = action else { + return; + }; + + // Only handle SEND, SEND_MULTIPLE, and VIEW actions + if action != "android.intent.action.SEND" + && action != "android.intent.action.VIEW" + && action != "android.intent.action.SEND_MULTIPLE" + { + return; + } + + let mut urls = HashSet::new(); + + // Get intent type (may be null) + let intent_type = jni_call_method!(env, &intent, "getType", "()Ljava/lang/String;", l) + .ok() + .filter(|jstr| !jstr.is_null()) + .map(|jstr| jstr.into()) + .and_then(|intent_type: JString| { + env + .get_string(&intent_type) + .ok() + .map(|s| s.to_string_lossy().to_string()) + }); + + // Handle text/plain intents (EXTRA_TEXT) + if intent_type.as_deref() == Some("text/plain") { + let extra_text_key = env.new_string("android.intent.extra.TEXT").unwrap(); + let extra_text = jni_call_method!( + env, + &intent, + "getStringExtra", + "(Ljava/lang/String;)Ljava/lang/String;", + &[(&extra_text_key).into()], + l + ) + .ok() + .and_then(|jstr| { + let jstr: JString = jstr.into(); + env + .get_string(&jstr) + .ok() + .map(|s| s.to_string_lossy().to_string()) + }); + + if let Some(text) = extra_text { + if !text.is_empty() { + // Check if it's a valid URL + if let Ok(url) = url::Url::parse(&text) { + urls.insert(url); + } else { + // If not a URL, create a data URL for plain text + // Use percent encoding for the text content + let encoded = utf8_percent_encode(&text, DATA_URL_ENCODING_SET).to_string(); + if let Ok(url) = url::Url::parse(&format!("data:text/plain,{}", encoded)) { + urls.insert(url); + } + } + } + } + } + + // Handle ClipData (API >= KITKAT, which is API 19) + // We'll try to get clip data, and if it fails, we'll continue + let clip_data = jni_call_method!( + env, + &intent, + "getClipData", + "()Landroid/content/ClipData;", + l + ) + .ok() + .filter(|clip_data| !clip_data.is_null()); + + if let Some(clip_data) = clip_data { + // getItemCount may return null if the intent has only a single uri in which case the uri can be received by getData|String instead. + if let Ok(item_count) = jni_call_method!(env, &clip_data, "getItemCount", "()I", i) { + for i in 0..item_count { + let clip_item = jni_call_method!( + env, + &clip_data, + "getItemAt", + "(I)Landroid/content/ClipData$Item;", + &[i.into()], + l + ) + .unwrap(); + + let uri = jni_call_method!(env, &clip_item, "getUri", "()Landroid/net/Uri;", l) + .ok() + .filter(|uri| !uri.is_null()); + + if let Some(uri) = uri { + let uri_string: JString = + jni_call_method!(env, &uri, "toString", "()Ljava/lang/String;", l) + .unwrap() + .into(); + let uri_str = env + .get_string(&uri_string) + .unwrap() + .to_string_lossy() + .to_string(); + if let Ok(url) = url::Url::parse(&uri_str) { + urls.insert(url); + } else { + log::error!("failed to parse URI: {}", uri_str); + } + } + } + } + } + + // Handle EXTRA_STREAM (for file sharing) + let extras = jni_call_method!(env, &intent, "getExtras", "()Landroid/os/Bundle;", l) + .ok() + .filter(|extras| !extras.is_null()); + + if let Some(extras) = extras { + let extra_stream_key = env.new_string("android.intent.extra.STREAM").unwrap(); + let extra_stream = jni_call_method!( + env, + &extras, + "get", + "(Ljava/lang/String;)Ljava/lang/Object;", + &[(&extra_stream_key).into()], + l + ) + .ok() + .filter(|extra_stream| !extra_stream.is_null()); + + if let Some(stream_uri) = extra_stream { + let uri_string: JString = + jni_call_method!(env, &stream_uri, "toString", "()Ljava/lang/String;", l) + .unwrap() + .into(); + let uri_str = env + .get_string(&uri_string) + .unwrap() + .to_string_lossy() + .to_string(); + if let Ok(url) = url::Url::parse(&uri_str) { + urls.insert(url); + } else { + log::error!("failed to parse URI: {}", uri_str); + } + } + } + + // Handle getDataString() for VIEW intents (deeplinks) + if action == "android.intent.action.VIEW" { + let data_string = jni_call_method!(env, &intent, "getDataString", "()Ljava/lang/String;", l) + .ok() + .filter(|data_string| !data_string.is_null()) + .and_then(|jstr| { + let jstr: JString = jstr.into(); + env + .get_string(&jstr) + .ok() + .map(|s| s.to_string_lossy().to_string()) + }); + + if let Some(data_str) = data_string { + if let Ok(url) = url::Url::parse(&data_str) { + urls.insert(url); + } else { + log::error!("failed to parse data string: {}", data_str); + } + } else { + log::error!("Intent data string is null"); + } + } + + if !urls.is_empty() { + INTENT_URLS.lock().unwrap().extend(urls); + wake(Event::Opened); + } +} + +#[allow(non_snake_case)] +pub unsafe fn onStart(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Started, + }); +} + +#[allow(non_snake_case)] +pub unsafe fn onStop(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Stopped, + }); +} + +#[allow(non_snake_case)] +pub unsafe fn onDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); + + let is_changing_configurations = + jni_call_method!(env, &activity, "isChangingConfigurations", "()Z", z).unwrap(); + + // keep our Rust window references alive when the activity is going to be recreated due to configuration changes + // e.g. rotation, multi window mode change etc + if !is_changing_configurations { + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Destroyed, + }); + CONTEXTS.lock().unwrap().remove(&activity_id); + WINDOW_MANAGER.lock().unwrap().remove(&activity_id); + RESUMED_ACTIVITIES.lock().unwrap().remove(&activity_id); + } +} + +/////////////////////////////////////////////// +// Events below are not used by event loop yet. +/////////////////////////////////////////////// + +#[allow(non_snake_case)] +pub unsafe fn onLowMemory(_: JNIEnv, _: JClass) { + wake(Event::LowMemory); +} + +/* +unsafe extern "C" fn on_window_resized( + activity: *mut ANativeActivity, + _window: *mut ANativeWindow, +) { + wake(activity, Event::WindowResized); +} + +unsafe extern "C" fn on_input_queue_created( + activity: *mut ANativeActivity, + queue: *mut AInputQueue, +) { + let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap()); + let locked_looper = LOOPER.lock().unwrap(); + // The looper should always be `Some` after `fn init()` returns, unless + // future code cleans it up and sets it back to `None` again. + let looper = locked_looper.as_ref().expect("Looper does not exist"); + input_queue.attach_looper(looper, NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT); + *INPUT_QUEUE.write().unwrap() = Some(input_queue); + wake(activity, Event::InputQueueCreated); +} + +unsafe extern "C" fn on_input_queue_destroyed( + activity: *mut ANativeActivity, + queue: *mut AInputQueue, +) { + wake(activity, Event::InputQueueDestroyed); + let mut input_queue_guard = INPUT_QUEUE.write().unwrap(); + assert_eq!(input_queue_guard.as_ref().unwrap().ptr().as_ptr(), queue); + let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap()); + input_queue.detach_looper(); + *input_queue_guard = None; +} + +unsafe extern "C" fn on_content_rect_changed(activity: *mut ANativeActivity, rect: *const ARect) { + let rect = Rect { + left: (*rect).left as _, + top: (*rect).top as _, + right: (*rect).right as _, + bottom: (*rect).bottom as _, + }; + *CONTENT_RECT.write().unwrap() = rect; + wake(activity, Event::ContentRectChanged); +} +*/ diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs new file mode 100644 index 000000000..d46e076f4 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs @@ -0,0 +1,1146 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![deny(unused_results)] + +use std::{ + cell::{RefCell, RefMut}, + collections::HashSet, + mem, + os::raw::c_void, + ptr, + time::Instant, +}; + +use objc2::{rc::Retained, runtime::AnyObject, MainThreadMarker, Message}; +use objc2_ui_kit::{UIApplication, UIScene, UISceneConnectionOptions, UIWindowScene}; + +use crate::{ + dpi::LogicalSize, + event::{Event, StartCause, WindowEvent}, + event_loop::ControlFlow, + platform_impl::platform::{ + event_loop::{EventHandler, EventProxy, EventWrapper}, + ffi::{ + id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, + CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, + CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, + NSOperatingSystemVersion, NSUInteger, + }, + scene::scene_lifecycle_enabled, + }, + window::WindowId as RootWindowId, +}; + +macro_rules! bug { + ($($msg:tt)*) => { + panic!("tao iOS bug, file an issue: {}", format!($($msg)*)) + }; +} + +macro_rules! bug_assert { + ($test:expr, $($msg:tt)*) => { + assert!($test, "tao iOS bug, file an issue: {}", format!($($msg)*)) + }; +} + +enum UserCallbackTransitionResult<'a> { + Success { + event_handler: Box, + active_control_flow: ControlFlow, + processing_redraws: bool, + }, + ReentrancyPrevented { + queued_events: &'a mut Vec, + }, +} + +// this is the state machine for the app lifecycle +#[derive(Debug)] +#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] +enum AppStateImpl { + NotLaunched { + queued_windows: Vec, + queued_events: Vec, + queued_gpu_redraws: HashSet, + }, + Launching { + queued_windows: Vec, + queued_events: Vec, + queued_event_handler: Box, + queued_gpu_redraws: HashSet, + }, + ProcessingEvents { + event_handler: Box, + queued_gpu_redraws: HashSet, + active_control_flow: ControlFlow, + }, + // special state to deal with reentrancy and prevent mutable aliasing. + InUserCallback { + queued_events: Vec, + queued_gpu_redraws: HashSet, + }, + ProcessingRedraws { + event_handler: Box, + active_control_flow: ControlFlow, + }, + Waiting { + waiting_event_handler: Box, + start: Instant, + }, + PollFinished { + waiting_event_handler: Box, + }, + Terminated, +} + +struct AppState { + // This should never be `None`, except for briefly during a state transition. + app_state: Option, + control_flow: ControlFlow, + waker: EventLoopWaker, + // Stores the UIWindow instances that do not have a scene assigned yet + // Window::new might create a window before a scene is ready for it, + // requesting a new scene to be activated and deferring the setWindowScene call to the scene delegate + windows_for_next_scenes: Vec, + // Stores the UIWindow instances that were created while the app had no connected scene. + // The system can run an app on the scene lifecycle even when its Info.plist does not + // adopt it, in which case the windows are created before the first scene connects + scene_less_windows: Vec, + did_first_scene_connect: bool, + // set once the queued windows were flushed and `StartCause::Init` was emitted, + // either from the application delegate or from the first scene connection + is_app_ready: bool, +} + +impl Drop for AppState { + fn drop(&mut self) { + match self.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_windows, + .. + } + | &mut AppStateImpl::Launching { + ref mut queued_windows, + .. + } => { + for &mut window in queued_windows { + unsafe { + let () = msg_send![window, release]; + } + } + } + _ => {} + } + } +} + +impl AppState { + // requires main thread + unsafe fn get_mut() -> RefMut<'static, AppState> { + // basically everything in UIKit requires the main thread, so it's pointless to use the + // std::sync APIs. + // must be mut because plain `static` requires `Sync` + static mut APP_STATE: RefCell> = RefCell::new(None); + + if cfg!(debug_assertions) { + assert_main_thread!( + "bug in tao: `AppState::get_mut()` can only be called on the main thread" + ); + } + let mut guard = APP_STATE.borrow_mut(); + if guard.is_none() { + #[inline(never)] + #[cold] + unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { + let waker = EventLoopWaker::new(CFRunLoopGetMain()); + **guard = Some(AppState { + app_state: Some(AppStateImpl::NotLaunched { + queued_windows: Vec::new(), + queued_events: Vec::new(), + queued_gpu_redraws: HashSet::new(), + }), + control_flow: ControlFlow::default(), + waker, + windows_for_next_scenes: Vec::new(), + scene_less_windows: Vec::new(), + did_first_scene_connect: false, + is_app_ready: false, + }); + } + init_guard(&mut guard) + } + RefMut::map(guard, |state| state.as_mut().unwrap()) + } + + fn state(&self) -> &AppStateImpl { + match &self.app_state { + Some(ref state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn state_mut(&mut self) -> &mut AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn take_state(&mut self) -> AppStateImpl { + match self.app_state.take() { + Some(state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn set_state(&mut self, new_state: AppStateImpl) { + bug_assert!( + self.app_state.is_none(), + "attempted to set an `AppState` without calling `take_state` first {:?}", + self.app_state + ); + self.app_state = Some(new_state) + } + + fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => mem::replace(state, new_state), + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn has_launched(&self) -> bool { + !matches!( + self.state(), + &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } + ) + } + + fn will_launch_transition(&mut self, queued_event_handler: Box) { + let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { + AppStateImpl::NotLaunched { + queued_windows, + queued_events, + queued_gpu_redraws, + } => (queued_windows, queued_events, queued_gpu_redraws), + s => bug!("unexpected state {:?}", s), + }; + self.set_state(AppStateImpl::Launching { + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + }); + } + + fn did_finish_launching_transition(&mut self) -> (Vec, Vec) { + let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { + AppStateImpl::Launching { + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + } => ( + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + ), + s => bug!("unexpected state {:?}", s), + }; + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Poll, + queued_gpu_redraws, + }); + (windows, events) + } + + fn wakeup_transition(&mut self) -> Option { + // before `AppState::did_finish_launching` is called, pretend there is no running + // event loop. + if !self.has_launched() { + return None; + } + + let (event_handler, event) = match (self.control_flow, self.take_state()) { + ( + ControlFlow::Poll, + AppStateImpl::PollFinished { + waiting_event_handler, + }, + ) => ( + waiting_event_handler, + EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), + ), + ( + ControlFlow::Wait, + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => ( + waiting_event_handler, + EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: None, + })), + ), + ( + ControlFlow::WaitUntil(requested_resume), + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => { + let event = if Instant::now() >= requested_resume { + EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + })) + } else { + EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + })) + }; + (waiting_event_handler, event) + } + (ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"), + s => bug!("`EventHandler` unexpectedly woke up {:?}", s), + }; + + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws: Default::default(), + active_control_flow: self.control_flow, + }); + Some(event) + } + + fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { + // If we're not able to process an event due to recursion or `Init` not having been sent out + // yet, then queue the events up. + match self.state_mut() { + &mut AppStateImpl::Launching { + ref mut queued_events, + .. + } + | &mut AppStateImpl::NotLaunched { + ref mut queued_events, + .. + } + | &mut AppStateImpl::InUserCallback { + ref mut queued_events, + .. + } => { + // A lifetime cast: early returns are not currently handled well with NLL, but + // polonius handles them well. This transmute is a safe workaround. + return unsafe { + mem::transmute::, UserCallbackTransitionResult<'_>>( + UserCallbackTransitionResult::ReentrancyPrevented { queued_events }, + ) + }; + } + + &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {} + + s @ &mut AppStateImpl::PollFinished { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::Terminated => { + bug!("unexpected attempted to process an event {:?}", s) + } + } + + let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) = + match self.take_state() { + AppStateImpl::Launching { .. } + | AppStateImpl::NotLaunched { .. } + | AppStateImpl::InUserCallback { .. } => unreachable!(), + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => ( + event_handler, + queued_gpu_redraws, + active_control_flow, + false, + ), + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } => (event_handler, Default::default(), active_control_flow, true), + AppStateImpl::PollFinished { .. } + | AppStateImpl::Waiting { .. } + | AppStateImpl::Terminated => unreachable!(), + }; + self.set_state(AppStateImpl::InUserCallback { + queued_events: Vec::new(), + queued_gpu_redraws, + }); + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } + } + + fn main_events_cleared_transition(&mut self) -> HashSet { + let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => (event_handler, queued_gpu_redraws, active_control_flow), + s => bug!("unexpected state {:?}", s), + }; + self.set_state(AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + }); + queued_gpu_redraws + } + + fn events_cleared_transition(&mut self) { + if !self.has_launched() { + return; + } + let (waiting_event_handler, old) = match self.take_state() { + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } => (event_handler, active_control_flow), + s => bug!("unexpected state {:?}", s), + }; + + let new = self.control_flow; + match (old, new) { + (ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }), + (ControlFlow::Wait, ControlFlow::Wait) => { + let start = Instant::now(); + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + } + (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) + if old_instant == new_instant => + { + let start = Instant::now(); + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + } + (_, ControlFlow::Wait) => { + let start = Instant::now(); + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.stop() + } + (_, ControlFlow::WaitUntil(new_instant)) => { + let start = Instant::now(); + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.start_at(new_instant) + } + (_, ControlFlow::Poll) => { + self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }); + self.waker.start() + } + (_, ControlFlow::ExitWithCode(_)) => { + // https://developer.apple.com/library/archive/qa/qa1561/_index.html + // it is not possible to quit an iOS app gracefully and programatically + warn!("`ControlFlow::Exit` ignored on iOS"); + self.control_flow = old + } + } + } + + fn terminated_transition(&mut self) -> Box { + match self.replace_state(AppStateImpl::Terminated) { + AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler, + s => bug!( + "`LoopDestroyed` happened while not processing events {:?}", + s + ), + } + } +} + +// tries to find an unitialized scene (no windows) +pub unsafe fn unitialized_scene() -> Option> { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + for scene in application.connectedScenes().iter() { + if let Some(window_scene) = scene.downcast_ref::() { + if window_scene.windows().count() == 0 { + return Some(window_scene.retain()); + } + } + } + + None +} + +// the first connected scene that is able to display windows +pub unsafe fn first_window_scene() -> Option> { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application + .connectedScenes() + .iter() + .find_map(|scene| scene.downcast_ref::().map(|s| s.retain())) +} + +pub unsafe fn scene_by_id(id: &str) -> Option> { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + for scene in application.connectedScenes().iter() { + let scene_id = scene.session().persistentIdentifier().to_string(); + if scene_id == id { + return Some(scene); + } + } + None +} + +pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions) { + let did_first_scene_connect = AppState::get_mut().did_first_scene_connect; + let is_first_scene = !did_first_scene_connect; + // on scene mode, we run on_app_ready() when the main scene is connected + // instead of on AppDelegate::didFinishLaunching + // this optimizes app startup, since the first created window can immediately see the main scene + // instead of having to create a new one (since it can't synchronously wait for it to be connected) + if !did_first_scene_connect { + AppState::get_mut().did_first_scene_connect = true; + on_app_ready(); + } + + if let Some(window_scene) = scene.downcast_ref::() { + let window = { + let mut this = AppState::get_mut(); + if this.windows_for_next_scenes.is_empty() { + None + } else { + Some(this.windows_for_next_scenes.remove(0)) + } + }; + if let Some(window) = window { + let () = msg_send![window, setWindowScene: window_scene]; + let () = msg_send![window, release]; + } else if !is_first_scene { + // only emit the SceneRequest event for scenes that were not requested by a tao window + // also ignore the main scene + handle_nonuser_event(EventWrapper::StaticEvent(Event::SceneRequested { + scene: scene.retain(), + options: options.retain(), + })); + } + + // windows that were created before any scene existed are only ever displayed once + // they join one, so the first scene to connect adopts all of them + let scene_less_windows = mem::take(&mut AppState::get_mut().scene_less_windows); + for window in scene_less_windows { + // the system might have attached the window to a scene on its own already + let assigned_scene: id = msg_send![window, windowScene]; + if assigned_scene.is_null() { + let () = msg_send![window, setWindowScene: window_scene]; + } + let () = msg_send![window, release]; + } + } +} + +pub unsafe fn register_window_for_scene(window: id) { + AppState::get_mut() + .windows_for_next_scenes + .push(msg_send![window, retain]); +} + +// retains window +pub unsafe fn register_scene_less_window(window: id) { + AppState::get_mut() + .scene_less_windows + .push(msg_send![window, retain]); +} + +pub unsafe fn did_first_scene_connect() -> bool { + AppState::get_mut().did_first_scene_connect +} + +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn set_key_window(window: id) { + bug_assert!( + msg_send![window, isKindOfClass: class!(UIWindow)], + "set_key_window called with an incorrect type" + ); + let mut this = AppState::get_mut(); + match this.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_windows, + .. + } => return queued_windows.push(msg_send![window, retain]), + &mut AppStateImpl::ProcessingEvents { .. } + | &mut AppStateImpl::InUserCallback { .. } + | &mut AppStateImpl::ProcessingRedraws { .. } => {} + s @ &mut AppStateImpl::Launching { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); + msg_send![window, makeKeyAndVisible] +} + +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn queue_gl_or_metal_redraw(window: id) { + bug_assert!( + msg_send![window, isKindOfClass: class!(UIWindow)], + "set_key_window called with an incorrect type" + ); + let mut this = AppState::get_mut(); + match this.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::Launching { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::ProcessingEvents { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::InUserCallback { + ref mut queued_gpu_redraws, + .. + } => drop(queued_gpu_redraws.insert(window)), + s @ &mut AppStateImpl::ProcessingRedraws { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); +} + +// requires main thread +pub unsafe fn will_launch(queued_event_handler: Box) { + AppState::get_mut().will_launch_transition(queued_event_handler) +} + +// requires main thread +pub unsafe fn did_finish_launching() { + // when app is run in scenes lifecycle mode, we defer the did_finish_launching call to the first scene setup + if !scene_lifecycle_enabled() { + on_app_ready(); + } +} + +unsafe fn on_app_ready() { + let mut this = AppState::get_mut(); + // an app whose Info.plist does not declare a scene manifest is set up on + // `didFinishLaunchingWithOptions:` but can still be given a scene by the system, + // in which case the scene connection must not run this a second time + if this.is_app_ready { + return; + } + this.is_app_ready = true; + let windows = match this.state_mut() { + AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), + s => bug!("unexpected state {:?}", s), + }; + + // start waking up the event loop now! + bug_assert!( + this.control_flow == ControlFlow::Poll, + "unexpectedly not setup to `Poll` on launch!" + ); + this.waker.start(); + + // have to drop RefMut because the window setup code below can trigger new events + drop(this); + + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + // Do a little screen dance here to account for windows being created before + // `UIApplicationMain` is called. This fixes visual issues such as being + // offcenter and sized incorrectly. Additionally, to fix orientation issues, we + // gotta reset the `rootViewController`. + // + // relevant iOS log: + // ``` + // [ApplicationLifecycle] Windows were created before application initialization + // completed. This may result in incorrect visual appearance. + // ``` + let screen: id = msg_send![window, screen]; + let _: id = msg_send![screen, retain]; + let () = msg_send![window, setScreen:0 as id]; + let () = msg_send![window, setScreen: screen]; + let () = msg_send![screen, release]; + let controller: id = msg_send![window, rootViewController]; + let () = msg_send![window, setRootViewController: ptr::null::()]; + let () = msg_send![window, setRootViewController: controller]; + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } + + let (windows, events) = AppState::get_mut().did_finish_launching_transition(); + + let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))) + .chain(events); + handle_nonuser_events(events); + + // the above window dance hack, could possibly trigger new windows to be created. + // we can just set those windows up normally, as they were created after didFinishLaunching + // so the app window can attach to it directly + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } +} + +// requires main thread +// AppState::did_finish_launching handles the special transition `Init` +pub unsafe fn handle_wakeup_transition() { + let mut this = AppState::get_mut(); + let wakeup_event = match this.wakeup_transition() { + None => return, + Some(wakeup_event) => wakeup_event, + }; + drop(this); + + handle_nonuser_event(wakeup_event) +} + +// requires main thread +pub unsafe fn handle_nonuser_event(event: EventWrapper) { + handle_nonuser_events(std::iter::once(event)) +} + +// requires main thread +pub unsafe fn handle_nonuser_events>(events: I) { + let mut this = AppState::get_mut(); + let (mut event_handler, active_control_flow, processing_redraws) = + match this.try_user_callback_transition() { + UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { + queued_events.extend(events); + return; + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + let mut control_flow = this.control_flow; + drop(this); + + for wrapper in events { + match wrapper { + EventWrapper::StaticEvent(event) => { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } + } + } + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::take(queued_events), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(if processing_redraws { + bug_assert!( + queued_gpu_redraws.is_empty(), + "redraw queued while processing redraws" + ); + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } + } else { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } + }); + this.control_flow = control_flow; + break; + } + drop(this); + + for wrapper in queued_events { + match wrapper { + EventWrapper::StaticEvent(event) => { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } + } + } + } +} + +// requires main thread +unsafe fn handle_user_events() { + let mut this = AppState::get_mut(); + let mut control_flow = this.control_flow; + let (mut event_handler, active_control_flow, processing_redraws) = + match this.try_user_callback_transition() { + UserCallbackTransitionResult::ReentrancyPrevented { .. } => { + bug!("unexpected attempted to process an event") + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + if processing_redraws { + bug!("user events attempted to be sent out while `ProcessingRedraws`"); + } + drop(this); + + event_handler.handle_user_events(&mut control_flow); + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::take(queued_events), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + }); + this.control_flow = control_flow; + break; + } + drop(this); + + for wrapper in queued_events { + match wrapper { + EventWrapper::StaticEvent(event) => { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } + } + } + event_handler.handle_user_events(&mut control_flow); + } +} + +// requires main thread +pub unsafe fn handle_main_events_cleared() { + let mut this = AppState::get_mut(); + if !this.has_launched() { + return; + } + match this.state_mut() { + &mut AppStateImpl::ProcessingEvents { .. } => {} + _ => bug!("`ProcessingRedraws` happened unexpectedly"), + }; + drop(this); + + // User events are always sent out at the end of the "MainEventLoop" + handle_user_events(); + handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); + + let mut this = AppState::get_mut(); + let mut redraw_events: Vec = this + .main_events_cleared_transition() + .into_iter() + .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) + .collect(); + + if !redraw_events.is_empty() { + redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); + } + drop(this); + + handle_nonuser_events(redraw_events); +} + +// requires main thread +pub unsafe fn handle_events_cleared() { + AppState::get_mut().events_cleared_transition(); +} + +// requires main thread +pub unsafe fn is_terminated() -> bool { + matches!(AppState::get_mut().state(), AppStateImpl::Terminated) +} + +// requires main thread +pub unsafe fn terminated() { + let mut this = AppState::get_mut(); + let mut event_handler = this.terminated_transition(); + let mut control_flow = this.control_flow; + drop(this); + + event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) +} + +fn handle_event_proxy( + event_handler: &mut Box, + control_flow: ControlFlow, + proxy: EventProxy, +) { + match proxy { + EventProxy::DpiChangedProxy { + suggested_size, + scale_factor, + window_id, + } => handle_hidpi_proxy( + event_handler, + control_flow, + suggested_size, + scale_factor, + window_id, + ), + } +} + +fn handle_hidpi_proxy( + event_handler: &mut Box, + mut control_flow: ControlFlow, + suggested_size: LogicalSize, + scale_factor: f64, + window_id: id, +) { + let mut size = suggested_size.to_physical(scale_factor); + let new_inner_size = &mut size; + let event = Event::WindowEvent { + window_id: RootWindowId(window_id.into()), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + }; + event_handler.handle_nonuser_event(event, &mut control_flow); + let (view, screen_frame) = get_view_and_screen_frame(window_id); + let physical_size = *new_inner_size; + let logical_size = physical_size.to_logical(scale_factor); + let size = CGSize::new(logical_size); + let new_frame: CGRect = CGRect::new(screen_frame.origin, size); + unsafe { + let () = msg_send![view, setFrame: new_frame]; + } +} + +fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) { + unsafe { + let view_controller: id = msg_send![window_id, rootViewController]; + let view: id = msg_send![view_controller, view]; + let bounds: CGRect = msg_send![window_id, bounds]; + let screen: id = msg_send![window_id, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = + msg_send![window_id, convertRect:bounds, toCoordinateSpace:screen_space]; + (view, screen_frame) + } +} + +struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl EventLoopWaker { + fn new(rl: CFRunLoopRef) -> EventLoopWaker { + extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. + // It is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediately in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); + + EventLoopWaker { timer } + } + } + + fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } + } + + fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } + } + + fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } +} + +macro_rules! os_capabilities { + ( + $( + $(#[$attr:meta])* + $error_name:ident: $objc_call:literal, + $name:ident: $major:literal-$minor:literal + ),* + $(,)* + ) => { + #[derive(Clone, Debug)] + pub struct OSCapabilities { + $( + pub $name: bool, + )* + + os_version: NSOperatingSystemVersion, + } + + impl From for OSCapabilities { + fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities { + $(let $name = os_version.meets_requirements($major, $minor);)* + OSCapabilities { $($name,)* os_version, } + } + } + + impl OSCapabilities {$( + $(#[$attr])* + pub fn $error_name(&self, extra_msg: &str) { + log::warn!( + concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), + $major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch, + extra_msg + ) + } + )*} + }; +} + +os_capabilities! { + /// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[allow(unused)] // error message unused + safe_area_err_msg: "-[UIView safeAreaInsets]", + safe_area: 11-0, + /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc + home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", + home_indicator_hidden: 11-0, + /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc + defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", + defer_system_gestures: 11-0, + /// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc + maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", + maximum_frames_per_second: 10-3, + /// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc + #[allow(unused)] // error message unused + force_touch_err_msg: "-[UITouch force]", + force_touch: 9-0, +} + +impl NSOperatingSystemVersion { + fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool { + (self.major, self.minor) >= (required_major, required_minor) + } +} + +pub fn os_capabilities() -> OSCapabilities { + use std::sync::LazyLock; + static OS_CAPABILITIES: LazyLock = LazyLock::new(|| { + let version: NSOperatingSystemVersion = unsafe { + let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; + let atleast_ios_8: bool = msg_send![ + process_info, + respondsToSelector: sel!(operatingSystemVersion) + ]; + // tao requires atleast iOS 8 because no one has put the time into supporting earlier os versions. + // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support + // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS + // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 + // has been tested to not even run on macOS 10.15 - Xcode 8 might? + // + // The minimum required iOS version is likely to grow in the future. + assert!(atleast_ios_8, "`tao` requires iOS version 8 or greater"); + msg_send![process_info, operatingSystemVersion] + }; + version.into() + }); + OS_CAPABILITIES.clone() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs new file mode 100644 index 000000000..4a1f3b3d7 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs @@ -0,0 +1,15 @@ +use std::ffi::CStr; + +use objc2::{ + msg_send, + runtime::{AnyClass, AnyObject}, +}; + +pub fn set_badge_count(count: i32) { + unsafe { + let ui_application = AnyClass::get(CStr::from_bytes_with_nul(b"UIApplication\0").unwrap()) + .expect("Failed to get UIApplication class"); + let app: *mut AnyObject = msg_send![ui_application, sharedApplication]; + let _: () = msg_send![app, setApplicationIconBadgeNumber:count]; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs new file mode 100644 index 000000000..9c3985440 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs @@ -0,0 +1,385 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::VecDeque, + ffi::c_void, + fmt::{self, Debug}, + marker::PhantomData, + mem, ptr, +}; + +use crossbeam_channel::{self as channel, Receiver, Sender}; + +use crate::{ + dpi::{LogicalSize, PhysicalPosition}, + error::ExternalError, + event::Event, + event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget}, + monitor::MonitorHandle as RootMonitorHandle, + platform::ios::Idiom, +}; + +use crate::platform_impl::platform::{ + app_state, + ffi::{ + id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, + kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease, + CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, + CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, + CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, + NSStringRust, UIApplicationMain, UIUserInterfaceIdiom, + }, + monitor, set_badge_count, view, MonitorHandle, +}; + +#[non_exhaustive] +#[derive(Debug)] +pub enum EventWrapper { + StaticEvent(Event<'static, Never>), + EventProxy(EventProxy), +} + +#[non_exhaustive] +#[derive(Debug, PartialEq)] +pub enum EventProxy { + DpiChangedProxy { + window_id: id, + suggested_size: LogicalSize, + scale_factor: f64, + }, +} + +#[derive(Clone)] +pub struct EventLoopWindowTarget { + receiver: Receiver, + sender_to_clone: Sender, +} + +impl EventLoopWindowTarget { + pub fn available_monitors(&self) -> VecDeque { + // guaranteed to be on main thread + unsafe { monitor::uiscreens() } + } + + #[inline] + pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { + warn!("`Window::monitor_from_point` is ignored on iOS"); + None + } + + pub fn primary_monitor(&self) -> Option { + // guaranteed to be on main thread + let monitor = unsafe { monitor::main_uiscreen() }; + + Some(RootMonitorHandle { inner: monitor }) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::UiKit( + rwh_06::UiKitDisplayHandle::new(), + )) + } + + pub fn cursor_position(&self) -> Result, ExternalError> { + debug!("`EventLoopWindowTarget::cursor_position` is ignored on iOS"); + Ok((0, 0).into()) + } + + /// Sets badge count on iOS launcher. 0 hides the count + pub fn set_badge_count(&self, count: i32) { + set_badge_count(count); + } +} + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + +pub struct EventLoop { + window_target: RootEventLoopWindowTarget, +} + +impl EventLoop { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop { + static mut SINGLETON_INIT: bool = false; + unsafe { + assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); + assert!( + !SINGLETON_INIT, + "Only one `EventLoop` is supported on iOS. \ + `EventLoopProxy` might be helpful" + ); + SINGLETON_INIT = true; + view::create_delegate_class(); + } + + let (sender_to_clone, receiver) = channel::unbounded(); + + // this line sets up the main run loop before `UIApplicationMain` + setup_control_flow_observers(); + + EventLoop { + window_target: RootEventLoopWindowTarget { + p: EventLoopWindowTarget { + receiver, + sender_to_clone, + }, + _marker: PhantomData, + }, + } + } + + pub fn run(self, event_handler: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + unsafe { + let application: id = msg_send![class!(UIApplication), sharedApplication]; + assert_eq!( + application, + ptr::null_mut(), + "\ + `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ + Note: `EventLoop::run` calls `UIApplicationMain` on iOS" + ); + app_state::will_launch(Box::new(EventLoopHandler { + f: event_handler, + event_loop: self.window_target, + })); + + UIApplicationMain( + 0, + ptr::null(), + nil, + NSStringRust::alloc(nil).init_str("AppDelegate"), + ); + unreachable!() + } + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) + } + + pub fn window_target(&self) -> &RootEventLoopWindowTarget { + &self.window_target + } +} + +// EventLoopExtIOS +impl EventLoop { + pub fn idiom(&self) -> Idiom { + // guaranteed to be on main thread + unsafe { self::get_idiom() } + } +} + +pub struct EventLoopProxy { + sender: Sender, + source: CFRunLoopSourceRef, +} + +unsafe impl Send for EventLoopProxy {} +unsafe impl Sync for EventLoopProxy {} + +impl Clone for EventLoopProxy { + fn clone(&self) -> EventLoopProxy { + EventLoopProxy::new(self.sender.clone()) + } +} + +impl Drop for EventLoopProxy { + fn drop(&mut self) { + unsafe { + CFRunLoopSourceInvalidate(self.source); + CFRelease(self.source as _); + } + } +} + +impl EventLoopProxy { + fn new(sender: Sender) -> EventLoopProxy { + unsafe { + // just wake up the eventloop + extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} + + // adding a Source to the main CFRunLoop lets us wake it up and + // process user events through the normal OS EventLoop mechanisms. + let rl = CFRunLoopGetMain(); + // we want all the members of context to be zero/null, except one + let mut context: CFRunLoopSourceContext = mem::zeroed(); + context.perform = Some(event_loop_proxy_handler); + let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); + CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); + CFRunLoopWakeUp(rl); + + EventLoopProxy { sender, source } + } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self + .sender + .send(event) + .map_err(|channel::SendError(x)| EventLoopClosed(x))?; + unsafe { + // let the main thread know there's a new event + CFRunLoopSourceSignal(self.source); + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); + } + Ok(()) + } +} + +fn setup_control_flow_observers() { + unsafe { + // begin is queued with the highest priority to ensure it is processed before other observers + extern "C" fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } + } + + // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in + // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end + // priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was + // chosen conservatively to guard against apple using different priorities for their redraw + // observers in different OS's or on different devices. If it so happens that it's too + // conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`. + // + // The value of `0x1e8480` was determined by inspecting stack traces and the associated + // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. + // + // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. + extern "C" fn control_flow_main_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), + kCFRunLoopExit => (), // Mode is changed to others like `UITrackingRunLoopMode` + _ => unreachable!(), + } + } + } + + // end is queued with the lowest priority to ensure it is processed after other observers + extern "C" fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), + kCFRunLoopExit => (), // Mode is changed to others like `UITrackingRunLoopMode` + _ => unreachable!(), + } + } + } + + let main_loop = CFRunLoopGetMain(); + + let begin_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + 1, // repeat = true + CFIndex::MIN, + control_flow_begin_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); + + let main_end_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + 1, // repeat = true + 0, // see comment on `control_flow_main_end_handler` + control_flow_main_end_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); + + let end_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + 1, // repeat = true + CFIndex::MAX, + control_flow_end_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); + } +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum Never {} + +pub trait EventHandler: Debug { + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); + fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + f: F, + event_loop: RootEventLoopWindowTarget, +} + +impl Debug for EventLoopHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EventLoopHandler") + .field("event_loop", &self.event_loop) + .finish() + } +} + +impl EventHandler for EventLoopHandler +where + F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + T: 'static, +{ + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { + (self.f)( + event.map_nonuser_event().unwrap(), + &self.event_loop, + control_flow, + ); + } + + fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + for event in self.event_loop.p.receiver.try_iter() { + (self.f)(Event::UserEvent(event), &self.event_loop, control_flow); + } + } +} + +// must be called on main thread +pub unsafe fn get_idiom() -> Idiom { + let device: id = msg_send![class!(UIDevice), currentDevice]; + let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom]; + raw_idiom.into() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs new file mode 100644 index 000000000..3fcff99a2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs @@ -0,0 +1,433 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] + +use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*}; + +use objc2::{ + encode::{Encode, Encoding}, + runtime::{AnyObject, Bool}, +}; + +use crate::{ + dpi::LogicalSize, + platform::ios::{Idiom, ScreenEdge, ValidOrientations}, +}; + +pub type id = *mut AnyObject; +pub const nil: id = 0 as id; + +#[allow(non_camel_case_types)] +pub type BOOL = Bool; +#[allow(deprecated)] +pub const YES: Bool = Bool::YES; +#[allow(deprecated)] +pub const NO: Bool = Bool::NO; + +#[cfg(target_pointer_width = "32")] +pub type CGFloat = f32; +#[cfg(target_pointer_width = "64")] +pub type CGFloat = f64; + +pub type NSInteger = isize; +pub type NSUInteger = usize; + +#[repr(C)] +#[derive(Clone, Debug)] +pub struct NSOperatingSystemVersion { + pub major: NSInteger, + pub minor: NSInteger, + pub patch: NSInteger, +} + +unsafe impl Encode for NSOperatingSystemVersion { + const ENCODING: Encoding = Encoding::Struct( + "?", + &[ + NSInteger::ENCODING, + NSInteger::ENCODING, + NSInteger::ENCODING, + ], + ); +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CGPoint { + pub x: CGFloat, + pub y: CGFloat, +} + +unsafe impl Encode for CGPoint { + const ENCODING: Encoding = Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]); +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CGSize { + pub width: CGFloat, + pub height: CGFloat, +} + +impl CGSize { + pub fn new(size: LogicalSize) -> CGSize { + CGSize { + width: size.width as _, + height: size.height as _, + } + } +} + +unsafe impl Encode for CGSize { + const ENCODING: Encoding = Encoding::Struct("CGSize", &[CGFloat::ENCODING, CGFloat::ENCODING]); +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CGRect { + pub origin: CGPoint, + pub size: CGSize, +} + +impl CGRect { + pub fn new(origin: CGPoint, size: CGSize) -> CGRect { + CGRect { origin, size } + } +} + +unsafe impl Encode for CGRect { + const ENCODING: Encoding = Encoding::Struct("CGRect", &[CGPoint::ENCODING, CGSize::ENCODING]); +} + +#[derive(Debug)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UITouchPhase { + Began = 0, + Moved, + Stationary, + Ended, + Cancelled, +} + +unsafe impl Encode for UITouchPhase { + const ENCODING: Encoding = isize::ENCODING; +} + +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UIForceTouchCapability { + Unknown = 0, + Unavailable, + Available, +} + +unsafe impl Encode for UIForceTouchCapability { + const ENCODING: Encoding = isize::ENCODING; +} + +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UITouchType { + Direct = 0, + Indirect, + Pencil, +} + +unsafe impl Encode for UITouchType { + const ENCODING: Encoding = isize::ENCODING; +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct UIEdgeInsets { + pub top: CGFloat, + pub left: CGFloat, + pub bottom: CGFloat, + pub right: CGFloat, +} + +unsafe impl Encode for UIEdgeInsets { + const ENCODING: Encoding = Encoding::Struct( + "UIEdgeInsets", + &[ + CGFloat::ENCODING, + CGFloat::ENCODING, + CGFloat::ENCODING, + CGFloat::ENCODING, + ], + ); +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIUserInterfaceIdiom(NSInteger); + +unsafe impl Encode for UIUserInterfaceIdiom { + const ENCODING: Encoding = NSInteger::ENCODING; +} + +impl UIUserInterfaceIdiom { + pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1); + pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0); + pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1); + pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2); + pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3); +} + +impl From for UIUserInterfaceIdiom { + fn from(idiom: Idiom) -> UIUserInterfaceIdiom { + match idiom { + Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified, + Idiom::Phone => UIUserInterfaceIdiom::Phone, + Idiom::Pad => UIUserInterfaceIdiom::Pad, + Idiom::TV => UIUserInterfaceIdiom::TV, + Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay, + } + } +} + +impl Into for UIUserInterfaceIdiom { + fn into(self) -> Idiom { + match self { + UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified, + UIUserInterfaceIdiom::Phone => Idiom::Phone, + UIUserInterfaceIdiom::Pad => Idiom::Pad, + UIUserInterfaceIdiom::TV => Idiom::TV, + UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay, + _ => unreachable!(), + } + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct UIInterfaceOrientationMask(NSUInteger); + +unsafe impl Encode for UIInterfaceOrientationMask { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +impl UIInterfaceOrientationMask { + pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1); + pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2); + pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4); + pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3); + pub const Landscape: UIInterfaceOrientationMask = + UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0); + pub const AllButUpsideDown: UIInterfaceOrientationMask = + UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0); + pub const All: UIInterfaceOrientationMask = + UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0); +} + +impl BitOr for UIInterfaceOrientationMask { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self { + UIInterfaceOrientationMask(self.0 | rhs.0) + } +} + +impl UIInterfaceOrientationMask { + pub fn from_valid_orientations_idiom( + valid_orientations: ValidOrientations, + idiom: Idiom, + ) -> UIInterfaceOrientationMask { + match (valid_orientations, idiom) { + (ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => { + UIInterfaceOrientationMask::AllButUpsideDown + } + (ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All, + (ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape, + (ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait, + (ValidOrientations::Portrait, _) => { + UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown + } + } + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIRectEdge(NSUInteger); + +unsafe impl Encode for UIRectEdge { + const ENCODING: Encoding = NSUInteger::ENCODING; +} + +impl From for UIRectEdge { + fn from(screen_edge: ScreenEdge) -> UIRectEdge { + assert_eq!( + screen_edge.bits() & !ScreenEdge::ALL.bits(), + 0, + "invalid `ScreenEdge`" + ); + UIRectEdge(screen_edge.bits().into()) + } +} + +impl Into for UIRectEdge { + fn into(self) -> ScreenEdge { + let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`"); + ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`") + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIScreenOverscanCompensation(NSInteger); + +unsafe impl Encode for UIScreenOverscanCompensation { + const ENCODING: Encoding = NSInteger::ENCODING; +} + +#[allow(dead_code)] +impl UIScreenOverscanCompensation { + pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0); + pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1); + pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2); +} + +#[allow(clippy::duplicated_attributes)] +#[link(name = "UIKit", kind = "framework")] +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + pub static kCFRunLoopDefaultMode: CFRunLoopMode; + pub static kCFRunLoopCommonModes: CFRunLoopMode; + + pub fn UIApplicationMain( + argc: c_int, + argv: *const c_char, + principalClassName: id, + delegateClassName: id, + ) -> c_int; + + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode); + pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); +} + +pub type Boolean = u8; +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = + extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void); +pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void); + +pub enum CFRunLoopObserverContext {} +pub enum CFRunLoopTimerContext {} + +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, + pub equal: Option Boolean>, + pub hash: Option CFHashCode>, + pub schedule: Option, + pub cancel: Option, + pub perform: Option, +} + +// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of +// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy +// so please test if you change the name back to NSString. +pub trait NSStringRust: Sized { + unsafe fn alloc(_: Self) -> id { + msg_send![class!(NSString), alloc] + } + + unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id; + unsafe fn stringByAppendingString_(self, other: id) -> id; + unsafe fn init_str(self, string: &str) -> Self; + unsafe fn UTF8String(self) -> *const c_char; +} + +impl NSStringRust for id { + unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id { + msg_send![self, initWithUTF8String: c_string] + } + + unsafe fn stringByAppendingString_(self, other: id) -> id { + msg_send![self, stringByAppendingString: other] + } + + unsafe fn init_str(self, string: &str) -> id { + let cstring = CString::new(string).unwrap(); + self.initWithUTF8String_(cstring.as_ptr()) + } + + unsafe fn UTF8String(self) -> *const c_char { + msg_send![self, UTF8String] + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs new file mode 100644 index 000000000..e84dbac6c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs @@ -0,0 +1,13 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::keyboard::{KeyCode, NativeKeyCode}; + +pub fn keycode_to_scancode(_code: KeyCode) -> Option { + None +} + +pub fn keycode_from_scancode(_scancode: u32) -> KeyCode { + KeyCode::Unidentified(NativeKeyCode::Unidentified) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs new file mode 100644 index 000000000..08a994426 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs @@ -0,0 +1,126 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! iOS support +//! +//! # Building app +//! To build ios app you will need rustc built for this targets: +//! +//! - armv7-apple-ios +//! - armv7s-apple-ios +//! - i386-apple-ios +//! - aarch64-apple-ios +//! - x86_64-apple-ios +//! +//! Then +//! +//! ``` +//! cargo build --target=... +//! ``` +//! The simplest way to integrate your app into xcode environment is to build it +//! as a static library. Wrap your main function and export it. +//! +//! ```rust, ignore +//! #[no_mangle] +//! pub extern fn start_tao_app() { +//! start_inner() +//! } +//! +//! fn start_inner() { +//! ... +//! } +//! ``` +//! +//! Compile project and then drag resulting .a into Xcode project. Add tao.h to xcode. +//! +//! ```ignore +//! void start_tao_app(); +//! ``` +//! +//! Use start_tao_app inside your xcode's main function. +//! +//! +//! # App lifecycle and events +//! +//! iOS environment is very different from other platforms and you must be very +//! careful with it's events. Familiarize yourself with +//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). +//! +//! +//! This is how those event are represented in tao: +//! +//! - sceneWillEnterForeground is WindowEvent::Resumed for each window in the scene +//! - sceneWillResignActive is WindowEvent::Suspended for each window in the scene +//! - applicationWillEnterForeground is WindowEvent::Resumed for each window in non-scene apps +//! - applicationWillResignActive is WindowEvent::Suspended for each window in non-scene apps +//! - applicationWillTerminate is LoopDestroyed +//! +//! Keep in mind that after LoopDestroyed event is received every attempt to draw with +//! opengl will result in segfault. +//! +//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed. + +// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be +// worked around in the future by using GCD (grand central dispatch) and/or caching of values like +// window size/position. +macro_rules! assert_main_thread { + ($($t:tt)*) => { + let is_main_thread: bool = msg_send![class!(NSThread), isMainThread]; + if !is_main_thread { + panic!($($t)*); + } + }; +} + +mod app_state; +mod badge; +mod event_loop; +mod ffi; +mod keycode; +mod monitor; +mod scene; +mod view; +mod window; + +use std::fmt; + +pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; +pub use self::{ + event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, + keycode::{keycode_from_scancode, keycode_to_scancode}, + monitor::{MonitorHandle, VideoMode}, + window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, +}; +pub(crate) use crate::icon::NoIcon as PlatformIcon; +pub(crate) use badge::set_badge_count; + +// todo: implement iOS keyboard event +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra {} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId { + uiscreen: ffi::id, +} + +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId { + uiscreen: std::ptr::null_mut(), + } + } +} + +unsafe impl Send for DeviceId {} +unsafe impl Sync for DeviceId {} + +#[non_exhaustive] +#[derive(Debug)] +pub enum OsError {} + +impl fmt::Display for OsError { + fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { + unreachable!() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs new file mode 100644 index 000000000..cc1bafb13 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs @@ -0,0 +1,300 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::{BTreeSet, VecDeque}, + fmt, + ops::{Deref, DerefMut}, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::platform::{ + app_state, + ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, + }, +}; + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) screen_mode: NativeDisplayMode, + pub(crate) monitor: MonitorHandle, +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct NativeDisplayMode(pub id); + +unsafe impl Send for NativeDisplayMode {} + +impl Drop for NativeDisplayMode { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.0, release]; + } + } +} + +impl Clone for NativeDisplayMode { + fn clone(&self) -> Self { + unsafe { + let _: id = msg_send![self.0, retain]; + } + NativeDisplayMode(self.0) + } +} + +impl Clone for VideoMode { + fn clone(&self) -> VideoMode { + VideoMode { + size: self.size, + bit_depth: self.bit_depth, + refresh_rate: self.refresh_rate, + screen_mode: self.screen_mode.clone(), + monitor: self.monitor.clone(), + } + } +} + +impl VideoMode { + unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode { + assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); + let os_capabilities = app_state::os_capabilities(); + let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second { + msg_send![uiscreen, maximumFramesPerSecond] + } else { + // https://developer.apple.com/library/archive/technotes/tn2460/_index.html + // https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison + // + // All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not + // supported, they are all guaranteed to have 60hz refresh rates. This does not + // correctly handle external displays. ProMotion displays support 120fps, but they were + // introduced at the same time as the `maximumFramesPerSecond` API. + // + // FIXME: earlier OSs could calculate the refresh rate using + // `-[CADisplayLink duration]`. + os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); + 60 + }; + let size: CGSize = msg_send![screen_mode, size]; + let screen_mode: id = msg_send![screen_mode, retain]; + let screen_mode = NativeDisplayMode(screen_mode); + VideoMode { + size: (size.width as u32, size.height as u32), + bit_depth: 32, + refresh_rate: refresh_rate as u16, + screen_mode, + monitor: MonitorHandle::retained_new(uiscreen), + } + } + + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Inner { + uiscreen: id, +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.uiscreen, release]; + } + } +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct MonitorHandle { + inner: Inner, +} + +impl Deref for MonitorHandle { + type Target = Inner; + + fn deref(&self) -> &Inner { + unsafe { + assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); + } + &self.inner + } +} + +impl DerefMut for MonitorHandle { + fn deref_mut(&mut self) -> &mut Inner { + unsafe { + assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); + } + &mut self.inner + } +} + +unsafe impl Send for MonitorHandle {} +unsafe impl Sync for MonitorHandle {} + +impl Clone for MonitorHandle { + fn clone(&self) -> MonitorHandle { + MonitorHandle::retained_new(self.uiscreen) + } +} + +impl Drop for MonitorHandle { + fn drop(&mut self) { + unsafe { + assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS"); + } + } +} + +impl fmt::Debug for MonitorHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[derive(Debug)] + #[allow(unused)] + struct MonitorHandle { + name: Option, + size: PhysicalSize, + position: PhysicalPosition, + scale_factor: f64, + } + + let monitor_id_proxy = MonitorHandle { + name: self.name(), + size: self.size(), + position: self.position(), + scale_factor: self.scale_factor(), + }; + + monitor_id_proxy.fmt(f) + } +} + +impl MonitorHandle { + pub fn retained_new(uiscreen: id) -> MonitorHandle { + unsafe { + assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS"); + let _: id = msg_send![uiscreen, retain]; + } + MonitorHandle { + inner: Inner { uiscreen }, + } + } +} + +impl Inner { + pub fn name(&self) -> Option { + unsafe { + let main = main_uiscreen(); + if self.uiscreen == main.uiscreen { + Some("Primary".to_string()) + } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen { + Some("Mirrored".to_string()) + } else { + uiscreens() + .iter() + .position(|rhs| rhs.uiscreen == self.uiscreen) + .map(|idx| idx.to_string()) + } + } + } + + pub fn size(&self) -> PhysicalSize { + unsafe { + let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; + PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) + } + } + + pub fn position(&self) -> PhysicalPosition { + unsafe { + let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; + (bounds.origin.x as f64, bounds.origin.y as f64).into() + } + } + + pub fn scale_factor(&self) -> f64 { + unsafe { + let scale: CGFloat = msg_send![self.ui_screen(), nativeScale]; + scale as f64 + } + } + + pub fn video_modes(&self) -> impl Iterator { + let mut modes = BTreeSet::new(); + unsafe { + let available_modes: id = msg_send![self.uiscreen, availableModes]; + let available_mode_count: NSUInteger = msg_send![available_modes, count]; + + for i in 0..available_mode_count { + let mode: id = msg_send![available_modes, objectAtIndex: i]; + modes.insert(RootVideoMode { + video_mode: VideoMode::retained_new(self.uiscreen, mode), + }); + } + } + + modes.into_iter() + } +} + +// MonitorHandleExtIOS +impl Inner { + pub fn ui_screen(&self) -> id { + self.uiscreen + } + + pub fn preferred_video_mode(&self) -> RootVideoMode { + unsafe { + let mode: id = msg_send![self.uiscreen, preferredMode]; + RootVideoMode { + video_mode: VideoMode::retained_new(self.uiscreen, mode), + } + } + } +} + +// requires being run on main thread +pub unsafe fn main_uiscreen() -> MonitorHandle { + let uiscreen: id = msg_send![class!(UIScreen), mainScreen]; + MonitorHandle::retained_new(uiscreen) +} + +// requires being run on main thread +unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle { + let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen]; + MonitorHandle::retained_new(uiscreen) +} + +// requires being run on main thread +pub unsafe fn uiscreens() -> VecDeque { + let screens: id = msg_send![class!(UIScreen), screens]; + let count: NSUInteger = msg_send![screens, count]; + let mut result = VecDeque::with_capacity(count as _); + let screens_enum: id = msg_send![screens, objectEnumerator]; + loop { + let screen: id = msg_send![screens_enum, nextObject]; + if screen == nil { + break result; + } + result.push_back(MonitorHandle::retained_new(screen)); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs new file mode 100644 index 000000000..0077367b3 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs @@ -0,0 +1,221 @@ +// Copyright 2021-2025 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; +use objc2_foundation::{ + NSBundle, NSError, NSObject, NSObjectProtocol, NSSet, NSString, NSUserActivity, +}; +use objc2_ui_kit::{ + UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, + UISceneSession, UISceneWindowingControlStyle, UIWindowScene, UIWindowSceneDelegate, +}; + +use crate::{ + event::{Event, WindowEvent}, + platform_impl::platform::{app_state, event_loop::EventWrapper}, + window::WindowId as RootWindowId, +}; + +// true when the app enabled `UIApplicationSupportsMultipleScenes` in its Info.plist +// and the device allows it to display multiple scenes +// https://developer.apple.com/documentation/uikit/uiapplication/supportsmultiplescenes?language=objc +pub unsafe fn app_supports_multiple_scenes() -> bool { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application.supportsMultipleScenes() +} + +// check whether the app adopted the scene lifecycle by declaring a +// `UIApplicationSceneManifest` in its Info.plist +// +// such apps get their windows and their lifecycle callbacks from a `UISceneDelegate` +// instead of the application delegate, so a `UIWindow` is only visible once it is +// attached to a scene +// https://developer.apple.com/documentation/bundleresources/information-property-list/uiapplicationscenemanifest +pub unsafe fn scene_lifecycle_enabled() -> bool { + let bundle = NSBundle::mainBundle(); + let Some(info) = bundle.infoDictionary() else { + return false; + }; + + let key = NSString::from_str("UIApplicationSceneManifest"); + (*info).objectForKey(&key).is_some() +} + +unsafe fn handle_scene_window_events(scene: &UIScene, event: impl Fn() -> WindowEvent<'static>) { + if let Some(window_scene) = scene.downcast_ref::() { + let windows = window_scene.windows(); + + if windows.is_empty() { + log::debug!("scene has no windows; no window events were emitted"); + } + + for window in windows { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } +} + +define_class!( + #[unsafe(super(NSObject))] + #[name = "TaoSceneDelegate"] + #[thread_kind = MainThreadOnly] + pub struct TaoSceneDelegate; + + unsafe impl NSObjectProtocol for TaoSceneDelegate {} + + #[allow(non_snake_case)] + unsafe impl UISceneDelegate for TaoSceneDelegate { + #[unsafe(method(scene:willConnectToSession:options:))] + fn scene_willConnectToSession_options( + &self, + scene: &UIScene, + _session: &UISceneSession, + connection_options: &UISceneConnectionOptions, + ) { + unsafe { + app_state::connect_scene(scene, connection_options); + } + } + + // iOS may disconnect a scene because the user closed it, or because + // the system discarded a backgrounded scene to free resources. A + // restored scene goes through `scene:willConnectToSession:` again + // and gets a new `UIWindow` and `WindowId`. + #[unsafe(method(sceneDidDisconnect:))] + fn sceneDidDisconnect(&self, scene: &UIScene) { + unsafe { + if app_state::is_terminated() { + log::debug!("ignoring `sceneDidDisconnect` after application termination"); + return; + } + + handle_scene_window_events(scene, || WindowEvent::Destroyed); + } + } + + #[unsafe(method(sceneDidBecomeActive:))] + fn sceneDidBecomeActive(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Focused(true)); + } + } + + #[unsafe(method(sceneWillResignActive:))] + fn sceneWillResignActive(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Focused(false)); + handle_scene_window_events(scene, || WindowEvent::Suspended); + } + } + + #[unsafe(method(sceneWillEnterForeground:))] + fn sceneWillEnterForeground(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Resumed); + } + } + + #[unsafe(method(sceneDidEnterBackground:))] + fn sceneDidEnterBackground(&self, _scene: &UIScene) {} + + #[unsafe(method(scene:openURLContexts:))] + fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet) { + unsafe { + let urls: Vec = url_contexts + .iter() + .filter_map(|ctx| { + ctx.URL().absoluteString().and_then(|url| { + let url = url.to_string(); + url + .parse() + .map_err(|e| { + log::error!("failed to parse URL {url} from scene:openURLContexts: {e}"); + e + }) + .ok() + }) + }) + .collect(); + if !urls.is_empty() { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); + } + } + } + + #[unsafe(method(stateRestorationActivityForScene:))] + fn stateRestorationActivityForScene( + &self, + _scene: &UIScene, + ) -> Option> { + None + } + + #[unsafe(method(scene:restoreInteractionStateWithUserActivity:))] + fn scene_restoreInteractionStateWithUserActivity( + &self, + _scene: &UIScene, + _state_restoration_activity: &NSUserActivity, + ) { + } + + #[unsafe(method(scene:willContinueUserActivityWithType:))] + fn scene_willContinueUserActivityWithType( + &self, + _scene: &UIScene, + _user_activity_type: &NSString, + ) { + } + + #[unsafe(method(scene:continueUserActivity:))] + fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) { + unsafe { + // universal app links + if let Some(url) = user_activity + .webpageURL() + .and_then(|url| url.absoluteString()) + { + let url = url.to_string().parse::().unwrap(); + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { + urls: vec![url], + })); + } + } + } + + #[unsafe(method(scene:didFailToContinueUserActivityWithType:error:))] + fn scene_didFailToContinueUserActivityWithType_error( + &self, + _scene: &UIScene, + _user_activity_type: &NSString, + _error: &NSError, + ) { + } + + #[unsafe(method(scene:didUpdateUserActivity:))] + fn scene_didUpdateUserActivity(&self, _scene: &UIScene, _user_activity: &NSUserActivity) {} + } + + #[allow(non_snake_case)] + unsafe impl UIWindowSceneDelegate for TaoSceneDelegate {} + + // Registered from a plain `impl` because objc2 panics in debug builds when a + // protocol-block selector is missing from the runtime protocol, and this one is + // iOS 26+. Both paths register the same method, so iOS 26 still resolves it. + // + // TODO: Move back into the protocol block once objc2 0.7.0 fixes the check, + // ref . + #[allow(non_snake_case)] + impl TaoSceneDelegate { + #[unsafe(method_id(preferredWindowingControlStyleForScene:))] + fn preferredWindowingControlStyleForScene( + &self, + _window_scene: &UIWindowScene, + ) -> Retained { + UISceneWindowingControlStyle::minimalStyle() + } + } +); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs new file mode 100644 index 000000000..fb1f83c32 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs @@ -0,0 +1,802 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::HashMap, + ffi::{c_char, CStr, CString}, +}; + +use objc2::{ + rc::Retained, + runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, + ClassType, MainThreadMarker, +}; +use objc2_ui_kit::{ + UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, UISceneSession, + UISceneSessionActivationRequest, +}; + +use crate::{ + dpi::PhysicalPosition, + event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, + platform::ios::{operating_system_version, MonitorHandleExtIOS}, + platform_impl::platform::{ + app_state::{self, OSCapabilities}, + event_loop::{self, EventProxy, EventWrapper}, + ffi::{ + id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, + UIRectEdge, UITouchPhase, UITouchType, BOOL, NO, YES, + }, + scene::app_supports_multiple_scenes, + window::PlatformSpecificWindowBuilderAttributes, + DeviceId, + }, + window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, +}; + +macro_rules! add_property { + ( + $decl:ident, + $name:ident: $t:ty, + $setter_name:ident: |$object:ident| $after_set:expr, + $getter_name:ident, + ) => { + add_property!( + $decl, + $name: $t, + $setter_name: true, |_, _|{}; |$object| $after_set, + $getter_name, + ) + }; + ( + $decl:ident, + $name:ident: $t:ty, + $setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr, + $getter_name:ident, + ) => { + { + const VAR_NAME: &'static str = concat!("_", stringify!($name)); + $decl.add_ivar::<$t>(&CString::new(VAR_NAME).unwrap()); + let setter = if $capability { + #[allow(non_snake_case)] + extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { + #[allow(deprecated)] // TODO: define_class! + unsafe { + *$object.get_mut_ivar::<$t>(VAR_NAME) = value; + } + $after_set + } + $setter_name + } else { + #[allow(non_snake_case)] + extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { + #[allow(deprecated)] // TODO: define_class! + unsafe { + *$object.get_mut_ivar::<$t>(VAR_NAME) = value; + } + $err(&app_state::os_capabilities(), "ignoring") + } + $setter_name + }; + #[allow(non_snake_case)] + extern "C" fn $getter_name($object: &Object, _: Sel) -> $t { + #[allow(deprecated)] // TODO: define_class! + unsafe { *$object.get_ivar::<$t>(VAR_NAME) } + } + $decl.add_method( + sel!($setter_name:), + setter as extern "C" fn(_, _, _), + ); + $decl.add_method( + sel!($getter_name), + $getter_name as extern "C" fn(_, _) -> _, + ); + } + }; +} + +// requires main thread +unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { + static mut CLASSES: Option> = None; + static mut ID: usize = 0; + + if CLASSES.is_none() { + CLASSES = Some(HashMap::default()); + } + + let classes = CLASSES.as_mut().unwrap(); + + classes.entry(root_view_class).or_insert_with(move || { + let uiview_class = class!(UIView); + let is_uiview: bool = msg_send![root_view_class, isSubclassOfClass: uiview_class]; + assert!(is_uiview, "`root_view_class` must inherit from `UIView`"); + + extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { + unsafe { + let window: id = msg_send![object, window]; + assert!(!window.is_null()); + app_state::handle_nonuser_events( + std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested( + RootWindowId(window.into()), + ))) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::RedrawEventsCleared, + ))), + ); + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), drawRect: rect]; + } + } + + extern "C" fn layout_subviews(object: &Object, _: Sel) { + unsafe { + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), layoutSubviews]; + + let window: id = msg_send![object, window]; + // UIKit can lay out a view while it is detached during window and view-controller + // transitions. There is no window ID or screen geometry to report until reattachment. + if window.is_null() { + return; + } + let window_bounds: CGRect = msg_send![window, bounds]; + let screen: id = msg_send![window, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = + msg_send![object, convertRect:window_bounds, toCoordinateSpace:screen_space]; + let scale_factor: CGFloat = msg_send![screen, scale]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, + } + .to_physical(scale_factor.into()); + + // If the app is started in landscape, the view frame and window bounds can be mismatched. + // The view frame will be in portrait and the window bounds in landscape. So apply the + // window bounds to the view frame to make it consistent. + let view_frame: CGRect = msg_send![object, frame]; + if view_frame != window_bounds { + let () = msg_send![object, setFrame: window_bounds]; + } + + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size), + })); + } + } + + extern "C" fn set_content_scale_factor( + object: &Object, + _: Sel, + untrusted_scale_factor: CGFloat, + ) { + unsafe { + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![ + super(object, superclass), + setContentScaleFactor: untrusted_scale_factor + ]; + + let window: id = msg_send![object, window]; + // `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow + // makeKeyAndVisible]` at window creation time (either manually or internally by + // UIKit when the `UIView` is first created), in which case we send no events here + if window.is_null() { + return; + } + // `setContentScaleFactor` may be called with a value of 0, which means "reset the + // content scale factor to a device-specific default value", so we can't use the + // parameter here. We can query the actual factor using the getter + let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; + assert!( + !scale_factor.is_nan() + && scale_factor.is_finite() + && scale_factor.is_sign_positive() + && scale_factor > 0.0, + "invalid scale_factor set on UIView", + ); + let scale_factor: f64 = scale_factor.into(); + let bounds: CGRect = msg_send![object, bounds]; + let screen: id = msg_send![window, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = + msg_send![object, convertRect:bounds, toCoordinateSpace:screen_space]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width as _, + height: screen_frame.size.height as _, + }; + app_state::handle_nonuser_events( + std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + window_id: window, + scale_factor, + suggested_size: size, + })) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size.to_physical(scale_factor)), + }, + ))), + ); + } + } + + extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { + unsafe { + let window: id = msg_send![object, window]; + assert!(!window.is_null()); + let uiscreen: id = msg_send![window, screen]; + let touches_enum: id = msg_send![touches, objectEnumerator]; + let mut touch_events = Vec::new(); + let os_supports_force = app_state::os_capabilities().force_touch; + loop { + let touch: id = msg_send![touches_enum, nextObject]; + if touch == nil { + break; + } + let logical_location: CGPoint = msg_send![touch, locationInView: nil]; + let touch_type: UITouchType = msg_send![touch, type]; + let force = if os_supports_force { + let trait_collection: id = msg_send![object, traitCollection]; + let touch_capability: UIForceTouchCapability = + msg_send![trait_collection, forceTouchCapability]; + // Both the OS _and_ the device need to be checked for force touch support. + if touch_capability == UIForceTouchCapability::Available { + let force: CGFloat = msg_send![touch, force]; + let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle: CGFloat = msg_send![touch, altitudeAngle]; + Some(angle as _) + } else { + None + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) + } else { + None + } + } else { + None + }; + let touch_id = touch as u64; + let phase: UITouchPhase = msg_send![touch, phase]; + let phase = match phase { + UITouchPhase::Began => TouchPhase::Started, + UITouchPhase::Moved => TouchPhase::Moved, + // 2 is UITouchPhase::Stationary and is not expected here + UITouchPhase::Ended => TouchPhase::Ended, + UITouchPhase::Cancelled => TouchPhase::Cancelled, + _ => panic!("unexpected touch phase: {:?}", phase as i32), + }; + + let physical_location = { + let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; + PhysicalPosition::from_logical::<(f64, f64), f64>( + (logical_location.x as _, logical_location.y as _), + scale_factor, + ) + }; + touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Touch(Touch { + device_id: RootDeviceId(DeviceId { uiscreen }), + id: touch_id, + location: physical_location, + force, + phase, + }), + })); + } + app_state::handle_nonuser_events(touch_events); + } + } + + let mut decl = ClassDecl::new( + &CString::new(format!("TaoUIView{}", ID)).unwrap(), + root_view_class, + ) + .expect("Failed to declare class `TaoUIView`"); + ID += 1; + decl.add_method(sel!(drawRect:), draw_rect as extern "C" fn(_, _, _)); + decl.add_method(sel!(layoutSubviews), layout_subviews as extern "C" fn(_, _)); + decl.add_method( + sel!(setContentScaleFactor:), + set_content_scale_factor as extern "C" fn(_, _, _), + ); + + decl.add_method( + sel!(touchesBegan:withEvent:), + handle_touches as extern "C" fn(_, _, _, _), + ); + decl.add_method( + sel!(touchesMoved:withEvent:), + handle_touches as extern "C" fn(_, _, _, _), + ); + decl.add_method( + sel!(touchesEnded:withEvent:), + handle_touches as extern "C" fn(_, _, _, _), + ); + decl.add_method( + sel!(touchesCancelled:withEvent:), + handle_touches as extern "C" fn(_, _, _, _), + ); + + decl.register() + }) +} + +// requires main thread +unsafe fn get_view_controller_class() -> &'static Class { + static mut CLASS: Option<&'static Class> = None; + if CLASS.is_none() { + let os_capabilities = app_state::os_capabilities(); + + let uiviewcontroller_class = class!(UIViewController); + + extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { + YES + } + + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoUIViewController\0").unwrap(), + uiviewcontroller_class, + ) + .expect("Failed to declare class `TaoUIViewController`"); + decl.add_method( + sel!(shouldAutorotate), + should_autorotate as extern "C" fn(_, _) -> _, + ); + add_property! { + decl, + prefers_status_bar_hidden: BOOL, + setPrefersStatusBarHidden: |object| { + unsafe { + let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; + } + }, + prefersStatusBarHidden, + } + add_property! { + decl, + prefers_home_indicator_auto_hidden: BOOL, + setPrefersHomeIndicatorAutoHidden: + os_capabilities.home_indicator_hidden, + OSCapabilities::home_indicator_hidden_err_msg; + |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; + } + }, + prefersHomeIndicatorAutoHidden, + } + add_property! { + decl, + supported_orientations: UIInterfaceOrientationMask, + setSupportedInterfaceOrientations: |object| { + unsafe { + let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; + } + }, + supportedInterfaceOrientations, + } + add_property! { + decl, + preferred_screen_edges_deferring_system_gestures: UIRectEdge, + setPreferredScreenEdgesDeferringSystemGestures: + os_capabilities.defer_system_gestures, + OSCapabilities::defer_system_gestures_err_msg; + |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + } + }, + preferredScreenEdgesDeferringSystemGestures, + } + CLASS = Some(decl.register()); + } + CLASS.unwrap() +} + +// requires main thread +unsafe fn get_window_class() -> &'static Class { + static mut CLASS: Option<&'static Class> = None; + if CLASS.is_none() { + let uiwindow_class = class!(UIWindow); + + extern "C" fn become_key_window(object: &Object, _: Sel) { + unsafe { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Focused(true), + })); + let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; + } + } + + extern "C" fn resign_key_window(object: &Object, _: Sel) { + unsafe { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(object.into()), + event: WindowEvent::Focused(false), + })); + let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; + } + } + + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoUIWindow\0").unwrap(), + uiwindow_class, + ) + .expect("Failed to declare class `TaoUIWindow`"); + decl.add_method( + sel!(becomeKeyWindow), + become_key_window as extern "C" fn(_, _), + ); + decl.add_method( + sel!(resignKeyWindow), + resign_key_window as extern "C" fn(_, _), + ); + + CLASS = Some(decl.register()); + } + CLASS.unwrap() +} + +// requires main thread +pub unsafe fn create_view( + _window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + frame: CGRect, +) -> id { + let class = get_view_class(platform_attributes.root_view_class); + + let view: id = msg_send![class, alloc]; + assert!(!view.is_null(), "Failed to create `UIView` instance"); + let view: id = msg_send![view, initWithFrame: frame]; + assert!(!view.is_null(), "Failed to initialize `UIView` instance"); + let () = msg_send![view, setMultipleTouchEnabled: YES]; + if let Some(scale_factor) = platform_attributes.scale_factor { + let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat]; + } + + view +} + +// requires main thread +pub unsafe fn create_view_controller( + _window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + view: id, +) -> id { + let class = get_view_controller_class(); + + let view_controller: id = msg_send![class, alloc]; + assert!( + !view_controller.is_null(), + "Failed to create `UIViewController` instance" + ); + let view_controller: id = msg_send![view_controller, init]; + assert!( + !view_controller.is_null(), + "Failed to initialize `UIViewController` instance" + ); + let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden { + YES + } else { + NO + }; + let idiom = event_loop::get_idiom(); + let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom( + platform_attributes.valid_orientations, + idiom, + ); + let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden { + YES + } else { + NO + }; + let edges: UIRectEdge = platform_attributes + .preferred_screen_edges_deferring_system_gestures + .into(); + let () = msg_send![ + view_controller, + setPrefersStatusBarHidden: status_bar_hidden + ]; + let () = msg_send![ + view_controller, + setSupportedInterfaceOrientations: supported_orientations + ]; + let () = msg_send![ + view_controller, + setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden + ]; + let () = msg_send![ + view_controller, + setPreferredScreenEdgesDeferringSystemGestures: edges + ]; + let () = msg_send![view_controller, setView: view]; + view_controller +} + +// requires main thread +pub unsafe fn create_window( + window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + frame: CGRect, + view_controller: id, +) -> id { + let class = get_window_class(); + + let window: id = msg_send![class, alloc]; + assert!(!window.is_null(), "Failed to create `UIWindow` instance"); + let window: id = msg_send![window, initWithFrame: frame]; + assert!( + !window.is_null(), + "Failed to initialize `UIWindow` instance" + ); + + // on the scene lifecycle a window is only displayed once it belongs to a scene, so we + // must always assign one - the app runs on it whenever the system connected a scene, + // which it may do even when the Info.plist does not declare a scene manifest + if let Some(scene) = app_state::unitialized_scene() { + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else if app_supports_multiple_scenes() { + // only request a new scene if the system actually supports it + // otherwise it is silently ignored + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + app_state::register_window_for_scene(window); + + let options = UISceneActivationRequestOptions::new(mtm); + if let Some(scene) = platform_attributes + .requesting_scene_identifier + .as_ref() + .and_then(|id| app_state::scene_by_id(id)) + { + options.setRequestingScene(Some(&scene)); + } + + let error_handler = block2::RcBlock::new(move |error| { + log::error!("error activating scene: {error:?}"); + }); + + if operating_system_version().0 >= 17 { + let request = UISceneSessionActivationRequest::request(); + request.setOptions(Some(&options)); + application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); + } else { + #[allow(deprecated)] + application.requestSceneSessionActivation_userActivity_options_errorHandler( + None, + None, + Some(&options), + Some(&error_handler), + ); + } + } + } else if let Some(scene) = app_state::first_window_scene() { + // if there's no unitialized scene and the app does not support multiple scenes + // we need to move this window to the main scene, otherwise it won't be visible + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else { + // no scene is connected yet, either because the app runs on the legacy lifecycle + // - where the system attaches the window to a scene on its own - or because it was + // created before the first scene connected, which then adopts it + app_state::register_scene_less_window(window); + } + + let () = msg_send![window, setRootViewController: view_controller]; + match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(ref video_mode)) => { + let uiscreen = video_mode.monitor().ui_screen() as id; + let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; + msg_send![window, setScreen:video_mode.monitor().ui_screen()] + } + Some(Fullscreen::Borderless(ref monitor)) => { + let uiscreen: id = match &monitor { + Some(monitor) => monitor.ui_screen() as id, + None => { + let uiscreen: id = msg_send![window, screen]; + uiscreen + } + }; + + msg_send![window, setScreen: uiscreen] + } + None => (), + } + + window +} + +pub fn create_delegate_class() { + extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id, _: id) -> BOOL { + unsafe { + app_state::did_finish_launching(); + } + YES + } + + extern "C" fn configuration_for_connecting_scene_session( + _: &Object, + _: Sel, + _application: id, + connecting_scene_session: id, + _options: id, + ) -> id { + unsafe { + let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); + // keep the role of the session that is connecting instead of assuming the + // window scene role, so scenes such as external displays still get their own. + // a `None` name picks the app's own configuration for that role when its + // Info.plist declares one, and the default configuration when it doesn't + let session: &UISceneSession = &*connecting_scene_session.cast(); + let config = + UISceneConfiguration::configurationWithName_sessionRole(None, &session.role(), mtm); + + // Dynamically set the delegate class name + config.setDelegateClass(Some(super::scene::TaoSceneDelegate::class())); + Retained::autorelease_ptr(config) as _ + } + } + + fn handle_deep_link(url: id) { + unsafe { + let absolute_url: id = msg_send![url, absoluteString]; + let bytes = { + let bytes: *const c_char = msg_send![absolute_url, UTF8String]; + bytes as *const u8 + }; + + // 4 represents utf8 encoding + let len = msg_send![absolute_url, lengthOfBytesUsingEncoding: 4]; + let bytes = std::slice::from_raw_parts(bytes, len); + + let url = url::Url::parse(std::str::from_utf8(bytes).unwrap()).unwrap(); + + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls: vec![url] })); + } + } + + unsafe fn handle_tao_window_events(event: impl Fn() -> WindowEvent<'static>) { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break; + } + let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; + if is_tao_window { + events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } + app_state::handle_nonuser_events(events); + } + + // custom URL schemes + // https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app + extern "C" fn application_open_url( + _self: &Object, + _cmd: Sel, + _app: id, + url: id, + _options: id, + ) -> BOOL { + handle_deep_link(url); + + YES + } + + // universal links + // https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app + extern "C" fn application_continue( + _: &Object, + _: Sel, + _application: id, + user_activity: id, + _restoration_handler: id, + ) -> BOOL { + unsafe { + let webpage_url: id = msg_send![user_activity, webpageURL]; + if webpage_url == nil { + return NO; + } + + handle_deep_link(webpage_url); + + YES + } + } + + extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Suspended) + } + } + + extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) { + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Resumed) + } + } + + extern "C" fn will_terminate(_: &Object, _: Sel, _: id) { + unsafe { + handle_tao_window_events(|| WindowEvent::Destroyed); + app_state::terminated(); + } + } + + let ui_responder = class!(UIResponder); + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"AppDelegate\0").unwrap(), + ui_responder, + ) + .expect("Failed to declare class `AppDelegate`"); + + unsafe { + // register the scene delegate class in the runtime upfront, the system looks it up by + // name when it decodes a scene session that was encoded on a previous launch + let _ = super::scene::TaoSceneDelegate::class(); + + decl.add_method( + sel!(application:didFinishLaunchingWithOptions:), + did_finish_launching as extern "C" fn(_, _, _, _) -> _, + ); + + // always answer the scene configuration request, even when the Info.plist does not + // declare a scene manifest: UIKit only asks for it when the app runs on the scene + // lifecycle, and answering it is the only way to get our scene delegate installed + // there. Apps that never run on scenes simply never receive this callback. + decl.add_method( + sel!(application:configurationForConnectingSceneSession:options:), + configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, + ); + + decl.add_method( + sel!(application:openURL:options:), + application_open_url as extern "C" fn(_, _, _, _, _) -> _, + ); + + decl.add_method( + sel!(application:continueUserActivity:restorationHandler:), + application_continue as extern "C" fn(_, _, _, _, _) -> _, + ); + + decl.add_method( + sel!(applicationWillResignActive:), + will_resign_active as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(applicationWillEnterForeground:), + will_enter_foreground as extern "C" fn(_, _, _), + ); + + decl.add_method( + sel!(applicationWillTerminate:), + will_terminate as extern "C" fn(_, _, _), + ); + + decl.register(); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs new file mode 100644 index 000000000..7407a4b0a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs @@ -0,0 +1,877 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::VecDeque, + ops::{Deref, DerefMut}, +}; + +use objc2::{ + rc::Retained, + runtime::{AnyClass, AnyObject}, + MainThreadMarker, +}; +use objc2_ui_kit::{ + UIApplication, UISceneActivationState, UISceneSessionActivationRequest, UIWindow, +}; + +use crate::{ + dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + event::{Event, WindowEvent}, + icon::Icon, + monitor::MonitorHandle as RootMonitorHandle, + platform::ios::{operating_system_version, MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, + platform_impl::platform::{ + app_state, + event_loop::{self, EventProxy, EventWrapper}, + ffi::{ + id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge, + UIScreenOverscanCompensation, NO, YES, + }, + monitor, set_badge_count, view, EventLoopWindowTarget, MonitorHandle, + }, + window::{ + CursorIcon, Fullscreen, ResizeDirection, Theme, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, WindowSizeConstraints, + }, +}; + +pub struct Inner { + pub window: id, + pub view_controller: id, + pub view: id, + gl_or_metal_backed: bool, +} + +impl Drop for Inner { + fn drop(&mut self) { + unsafe { + let window = self.window(); + if let Some(scene) = window.windowScene() { + // our windows are tied to scenes - so when this is the last window of the scene, + // request the scene to be destroyed + if scene.windows().count() == 1 { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application.requestSceneSessionDestruction_options_errorHandler( + &scene.session(), + None, + None, + ); + } + } + let () = msg_send![self.view, release]; + let () = msg_send![self.view_controller, release]; + let () = msg_send![self.window, release]; + + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(self.window.into()), + event: WindowEvent::Focused(false), + })); + } + } +} + +impl Inner { + pub fn set_title(&self, _title: &str) { + debug!("`Window::set_title` is ignored on iOS") + } + + pub fn title(&self) -> String { + String::new() + } + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { + let () = msg_send![self.window, setHidden: NO]; + }, + false => unsafe { + let () = msg_send![self.window, setHidden: YES]; + }, + } + } + + pub fn set_focus(&self) { + unsafe { + let window = self.window(); + // only call makeKeyAndVisible() when Info.plist was not set up to support scenes + let Some(scene) = window.windowScene() else { + window.makeKeyAndVisible(); + return; + }; + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + + let error_handler = block2::RcBlock::new(move |error| { + log::error!("error activating scene: {error:?}"); + }); + + // when we support multiple scenes, request the activation of this window's scene + if application.supportsMultipleScenes() { + if operating_system_version().0 >= 17 { + application.activateSceneSessionForRequest_errorHandler( + &UISceneSessionActivationRequest::request(), + Some(&error_handler), + ); + } else { + #[allow(deprecated)] + application.requestSceneSessionActivation_userActivity_options_errorHandler( + Some(&scene.session()), + None, + None, + Some(&error_handler), + ); + } + } else { + window.makeKeyAndVisible(); + } + } + } + + pub fn set_focusable(&self, _focusable: bool) { + warn!("set_focusable not yet implemented on iOS"); + } + + pub fn is_focused(&self) -> bool { + unsafe { + self + .window() + .windowScene() + .map(|scene| scene.activationState() == UISceneActivationState::ForegroundActive) + .unwrap_or_default() + } + } + + pub fn is_always_on_top(&self) -> bool { + log::warn!("`Window::is_always_on_top` is ignored on iOS"); + false + } + + pub fn request_redraw(&self) { + unsafe { + if self.gl_or_metal_backed { + // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. + // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using + // raw or gl/metal for drawing this work is completely avoided. + // + // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via + // testing. + // + // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc + app_state::queue_gl_or_metal_redraw(self.window); + } else { + let () = msg_send![self.view, setNeedsDisplay]; + } + } + } + + pub fn inner_position(&self) -> Result, NotSupportedError> { + unsafe { + let safe_area = self.safe_area_screen_space(); + let position = LogicalPosition { + x: safe_area.origin.x as f64, + y: safe_area.origin.y as f64, + }; + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) + } + } + + pub fn outer_position(&self) -> Result, NotSupportedError> { + unsafe { + let screen_frame = self.screen_frame(); + let position = LogicalPosition { + x: screen_frame.origin.x as f64, + y: screen_frame.origin.y as f64, + }; + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) + } + } + + pub fn set_outer_position(&self, physical_position: Position) { + unsafe { + let scale_factor = self.scale_factor(); + let position = physical_position.to_logical::(scale_factor); + let screen_frame = self.screen_frame(); + let new_screen_frame = CGRect { + origin: CGPoint { + x: position.x as _, + y: position.y as _, + }, + size: screen_frame.size, + }; + let bounds = self.from_screen_space(new_screen_frame); + let () = msg_send![self.window, setBounds: bounds]; + } + } + + pub fn inner_size(&self) -> PhysicalSize { + unsafe { + let scale_factor = self.scale_factor(); + let safe_area = self.safe_area_screen_space(); + let size = LogicalSize { + width: safe_area.size.width as f64, + height: safe_area.size.height as f64, + }; + size.to_physical(scale_factor) + } + } + + pub fn outer_size(&self) -> PhysicalSize { + unsafe { + let scale_factor = self.scale_factor(); + let screen_frame = self.screen_frame(); + let size = LogicalSize { + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, + }; + size.to_physical(scale_factor) + } + } + + pub fn set_inner_size(&self, _size: Size) { + warn!("not clear what `Window::set_inner_size` means on iOS"); + } + + pub fn set_min_inner_size(&self, _: Option) { + warn!("`Window::set_min_inner_size` is ignored on iOS") + } + pub fn set_max_inner_size(&self, _: Option) { + warn!("`Window::set_max_inner_size` is ignored on iOS") + } + pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) { + warn!("`Window::set_inner_size_constraints` is ignored on iOS") + } + + pub fn set_resizable(&self, _resizable: bool) { + warn!("`Window::set_resizable` is ignored on iOS") + } + + pub fn set_minimizable(&self, _minimizable: bool) { + warn!("`Window::set_minimizable` is ignored on iOS") + } + + pub fn set_maximizable(&self, _maximizable: bool) { + warn!("`Window::set_maximizable` is ignored on iOS") + } + + pub fn set_closable(&self, _closable: bool) { + warn!("`Window::set_closable` is ignored on iOS") + } + + pub fn scale_factor(&self) -> f64 { + unsafe { + let hidpi: CGFloat = msg_send![self.view, contentScaleFactor]; + hidpi as _ + } + } + + pub fn set_cursor_icon(&self, _cursor: CursorIcon) { + debug!("`Window::set_cursor_icon` ignored on iOS") + } + + pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_cursor_visible(&self, _visible: bool) { + debug!("`Window::set_cursor_visible` is ignored on iOS") + } + + pub fn cursor_position(&self) -> Result, ExternalError> { + debug!("`Window::cursor_position` is ignored on iOS"); + Ok((0, 0).into()) + } + + pub fn drag_window(&self) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + pub fn set_minimized(&self, _minimized: bool) { + warn!("`Window::set_minimized` is ignored on iOS") + } + + pub fn set_maximized(&self, _maximized: bool) { + warn!("`Window::set_maximized` is ignored on iOS") + } + + pub fn is_maximized(&self) -> bool { + warn!("`Window::is_maximized` is ignored on iOS"); + false + } + + pub fn is_minimized(&self) -> bool { + warn!("`Window::is_minimized` is ignored on iOS"); + false + } + + pub fn is_visible(&self) -> bool { + !self.window().isHidden() + } + + pub fn is_resizable(&self) -> bool { + warn!("`Window::is_resizable` is ignored on iOS"); + false + } + + pub fn is_minimizable(&self) -> bool { + warn!("`Window::is_minimizable` is ignored on iOS"); + false + } + + pub fn is_maximizable(&self) -> bool { + warn!("`Window::is_maximizable` is ignored on iOS"); + false + } + + pub fn is_closable(&self) -> bool { + warn!("`Window::is_closable` is ignored on iOS"); + false + } + + pub fn is_decorated(&self) -> bool { + warn!("`Window::is_decorated` is ignored on iOS"); + false + } + + pub fn set_fullscreen(&self, monitor: Option) { + unsafe { + let uiscreen = match monitor { + Some(Fullscreen::Exclusive(video_mode)) => { + let uiscreen = video_mode.video_mode.monitor.ui_screen() as id; + let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; + uiscreen + } + Some(Fullscreen::Borderless(monitor)) => monitor + .unwrap_or_else(|| self.current_monitor_inner()) + .ui_screen() as id, + None => { + warn!("`Window::set_fullscreen(None)` ignored on iOS"); + return; + } + }; + + // this is pretty slow on iOS, so avoid doing it if we can + let current: id = msg_send![self.window, screen]; + if uiscreen != current { + let () = msg_send![self.window, setScreen: uiscreen]; + } + + let bounds: CGRect = msg_send![uiscreen, bounds]; + let () = msg_send![self.window, setFrame: bounds]; + + // For external displays, we must disable overscan compensation or + // the displayed image will have giant black bars surrounding it on + // each side + let () = msg_send![ + uiscreen, + setOverscanCompensation: UIScreenOverscanCompensation::None + ]; + } + } + + pub fn fullscreen(&self) -> Option { + unsafe { + let monitor = self.current_monitor_inner(); + let uiscreen = monitor.inner.ui_screen(); + let screen_space_bounds = self.screen_frame(); + let screen_bounds: CGRect = msg_send![uiscreen, bounds]; + + // TODO: track fullscreen instead of relying on brittle float comparisons + if screen_space_bounds.origin.x == screen_bounds.origin.x + && screen_space_bounds.origin.y == screen_bounds.origin.y + && screen_space_bounds.size.width == screen_bounds.size.width + && screen_space_bounds.size.height == screen_bounds.size.height + { + Some(Fullscreen::Borderless(Some(monitor))) + } else { + None + } + } + } + + pub fn set_decorations(&self, _decorations: bool) { + warn!("`Window::set_decorations` is ignored on iOS") + } + + pub fn set_always_on_bottom(&self, _always_on_bottom: bool) { + warn!("`Window::set_always_on_bottom` is ignored on iOS") + } + + pub fn set_always_on_top(&self, _always_on_top: bool) { + warn!("`Window::set_always_on_top` is ignored on iOS") + } + + pub fn set_window_icon(&self, _icon: Option) { + warn!("`Window::set_window_icon` is ignored on iOS") + } + + pub fn set_ime_position(&self, _position: Position) { + warn!("`Window::set_ime_position` is ignored on iOS") + } + + pub fn request_user_attention(&self, _request_type: Option) { + warn!("`Window::request_user_attention` is ignored on iOS") + } + + pub fn set_background_color(&self, _color: Option) {} + + // Allow directly accessing the current monitor internally without unwrapping. + fn current_monitor_inner(&self) -> RootMonitorHandle { + unsafe { + let uiscreen: id = msg_send![self.window, screen]; + RootMonitorHandle { + inner: MonitorHandle::retained_new(uiscreen), + } + } + } + + pub fn current_monitor(&self) -> Option { + Some(self.current_monitor_inner()) + } + + pub fn available_monitors(&self) -> VecDeque { + unsafe { monitor::uiscreens() } + } + + #[inline] + pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { + warn!("`Window::monitor_from_point` is ignored on iOS"); + None + } + + pub fn primary_monitor(&self) -> Option { + let monitor = unsafe { monitor::main_uiscreen() }; + Some(RootMonitorHandle { inner: monitor }) + } + + pub fn id(&self) -> WindowId { + self.window.into() + } + + #[cfg(feature = "rwh_04")] + pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { + let mut window_handle = rwh_04::UiKitHandle::empty(); + window_handle.ui_window = self.window as _; + window_handle.ui_view = self.view as _; + window_handle.ui_view_controller = self.view_controller as _; + rwh_04::RawWindowHandle::UiKit(window_handle) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { + let mut window_handle = rwh_05::UiKitWindowHandle::empty(); + window_handle.ui_window = self.window as _; + window_handle.ui_view = self.view as _; + window_handle.ui_view_controller = self.view_controller as _; + rwh_05::RawWindowHandle::UiKit(window_handle) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + pub fn raw_window_handle_rwh_06(&self) -> Result { + let mut window_handle = rwh_06::UiKitWindowHandle::new({ + std::ptr::NonNull::new(self.view as _).expect("self.view should never be null") + }); + window_handle.ui_view_controller = std::ptr::NonNull::new(self.view_controller as _); + Ok(rwh_06::RawWindowHandle::UiKit(window_handle)) + } + + #[cfg(feature = "rwh_06")] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::UiKit( + rwh_06::UiKitDisplayHandle::new(), + )) + } + + pub fn theme(&self) -> Theme { + Theme::Light + } + + /// Sets badge count on iOS launcher. 0 hides the count + pub fn set_badge_count(&self, count: i32) { + set_badge_count(count); + } + + // instead of returning an Option here, we default to an empty string + // scene lifecycle will be enforced anyway soon (iOS 27) + pub fn scene_identifier(&self) -> String { + unsafe { + let window = self.window(); + let Some(scene) = window.windowScene() else { + return "".into(); + }; + scene.session().persistentIdentifier().to_string() + } + } +} + +pub struct Window { + pub inner: Inner, +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + assert_main_thread!("`Window::drop` can only be run on the main thread on iOS"); + } + } +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Deref for Window { + type Target = Inner; + + fn deref(&self) -> &Inner { + unsafe { + assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); + } + &self.inner + } +} + +impl DerefMut for Window { + fn deref_mut(&mut self) -> &mut Inner { + unsafe { + assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); + } + &mut self.inner + } +} + +impl Window { + pub fn new( + _event_loop: &EventLoopWindowTarget, + window_attributes: WindowAttributes, + platform_attributes: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + if window_attributes.always_on_top { + warn!("`WindowAttributes::always_on_top` is unsupported on iOS"); + } + // TODO: transparency, visible + + unsafe { + let screen = match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(ref video_mode)) => { + video_mode.video_mode.monitor.ui_screen() as id + } + Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(), + Some(Fullscreen::Borderless(None)) | None => monitor::main_uiscreen().ui_screen() as id, + }; + + let screen_bounds: CGRect = msg_send![screen, bounds]; + + let frame = match window_attributes.inner_size { + Some(dim) => { + let scale_factor = msg_send![screen, scale]; + let size = dim.to_logical::(scale_factor); + CGRect { + origin: screen_bounds.origin, + size: CGSize { + width: size.width as _, + height: size.height as _, + }, + } + } + None => screen_bounds, + }; + + let view = view::create_view(&window_attributes, &platform_attributes, frame); + + let gl_or_metal_backed = { + let view_class: *const AnyClass = msg_send![view, class]; + let layer_class: *const AnyClass = msg_send![view_class, layerClass]; + let is_metal: bool = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; + let is_gl: bool = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; + is_metal || is_gl + }; + + let view_controller = + view::create_view_controller(&window_attributes, &platform_attributes, view); + let window = view::create_window( + &window_attributes, + &platform_attributes, + frame, + view_controller, + ); + + let result = Window { + inner: Inner { + window, + view_controller, + view, + gl_or_metal_backed, + }, + }; + app_state::set_key_window(window); + + // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` + // event on window creation if the DPI factor != 1.0 + let scale_factor: CGFloat = msg_send![view, contentScaleFactor]; + let scale_factor: f64 = scale_factor.into(); + if scale_factor != 1.0 { + let bounds: CGRect = msg_send![view, bounds]; + let screen: id = msg_send![window, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = + msg_send![view, convertRect:bounds, toCoordinateSpace:screen_space]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width as _, + height: screen_frame.size.height as _, + }; + app_state::handle_nonuser_events( + std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + window_id: window, + scale_factor, + suggested_size: size, + })) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size.to_physical(scale_factor)), + }, + ))), + ); + } + + Ok(result) + } + } +} + +// WindowExtIOS +impl Inner { + pub fn ui_window(&self) -> id { + self.window + } + pub fn ui_view_controller(&self) -> id { + self.view_controller + } + pub fn ui_view(&self) -> id { + self.view + } + pub fn window(&self) -> Retained { + unsafe { Retained::::retain(self.window as _).unwrap() } + } + + pub fn set_scale_factor(&self, scale_factor: f64) { + unsafe { + assert!( + dpi::validate_scale_factor(scale_factor), + "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" + ); + let scale_factor = scale_factor as CGFloat; + let () = msg_send![self.view, setContentScaleFactor: scale_factor]; + } + } + + pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { + unsafe { + let idiom = event_loop::get_idiom(); + let supported_orientations = + UIInterfaceOrientationMask::from_valid_orientations_idiom(valid_orientations, idiom); + msg_send![ + self.view_controller, + setSupportedInterfaceOrientations: supported_orientations + ] + } + } + + pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { + unsafe { + let prefers_home_indicator_hidden = if hidden { YES } else { NO }; + let () = msg_send![ + self.view_controller, + setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden + ]; + } + } + + pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { + let edges: UIRectEdge = edges.into(); + unsafe { + let () = msg_send![ + self.view_controller, + setPreferredScreenEdgesDeferringSystemGestures: edges + ]; + } + } + + pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { + unsafe { + let status_bar_hidden = if hidden { YES } else { NO }; + let () = msg_send![ + self.view_controller, + setPrefersStatusBarHidden: status_bar_hidden + ]; + } + } +} + +impl Inner { + // requires main thread + unsafe fn screen_frame(&self) -> CGRect { + self.to_screen_space(msg_send![self.window, bounds]) + } + + // requires main thread + unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect { + let screen: id = msg_send![self.window, screen]; + if !screen.is_null() { + let screen_space: id = msg_send![screen, coordinateSpace]; + msg_send![self.window, convertRect:rect, toCoordinateSpace:screen_space] + } else { + rect + } + } + + // requires main thread + unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect { + let screen: id = msg_send![self.window, screen]; + if !screen.is_null() { + let screen_space: id = msg_send![screen, coordinateSpace]; + msg_send![self.window, convertRect:rect, fromCoordinateSpace:screen_space] + } else { + rect + } + } + + // requires main thread + unsafe fn safe_area_screen_space(&self) -> CGRect { + let bounds: CGRect = msg_send![self.window, bounds]; + if app_state::os_capabilities().safe_area { + let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; + let safe_bounds = CGRect { + origin: CGPoint { + x: bounds.origin.x + safe_area.left, + y: bounds.origin.y + safe_area.top, + }, + size: CGSize { + width: bounds.size.width - safe_area.left - safe_area.right, + height: bounds.size.height - safe_area.top - safe_area.bottom, + }, + }; + self.to_screen_space(safe_bounds) + } else { + let screen_frame = self.to_screen_space(bounds); + let status_bar_frame: CGRect = { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + assert!( + !app.is_null(), + "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS" + ); + msg_send![app, statusBarFrame] + }; + let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height { + (screen_frame.origin.y, screen_frame.size.height) + } else { + let y = status_bar_frame.size.height; + let height = + screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y); + (y, height) + }; + CGRect { + origin: CGPoint { + x: screen_frame.origin.x, + y, + }, + size: CGSize { + width: screen_frame.size.width, + height, + }, + } + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId { + window: id, +} + +impl WindowId { + pub unsafe fn dummy() -> Self { + WindowId { + window: std::ptr::null_mut(), + } + } +} + +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} + +impl From<&AnyObject> for WindowId { + fn from(window: &AnyObject) -> WindowId { + WindowId { + window: window as *const _ as _, + } + } +} + +impl From<&mut AnyObject> for WindowId { + fn from(window: &mut AnyObject) -> WindowId { + WindowId { + window: window as _, + } + } +} + +impl From for WindowId { + fn from(window: id) -> WindowId { + WindowId { window } + } +} + +impl From> for WindowId { + fn from(window: Retained) -> WindowId { + WindowId { + window: Retained::as_ptr(&window) as _, + } + } +} + +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub root_view_class: &'static AnyClass, + pub scale_factor: Option, + pub valid_orientations: ValidOrientations, + pub prefers_home_indicator_hidden: bool, + pub prefers_status_bar_hidden: bool, + pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, + pub requesting_scene_identifier: Option, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> PlatformSpecificWindowBuilderAttributes { + PlatformSpecificWindowBuilderAttributes { + root_view_class: class!(UIView), + scale_factor: None, + valid_orientations: Default::default(), + prefers_home_indicator_hidden: false, + prefers_status_bar_hidden: false, + preferred_screen_edges_deferring_system_gestures: Default::default(), + requesting_scene_identifier: None, + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs new file mode 100644 index 000000000..f31971d73 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs @@ -0,0 +1,77 @@ +use std::{ + os::raw::{c_int, c_uchar}, + ptr, +}; + +use gtk::glib; +use x11_dl::{xinput2, xlib}; + +use crate::event::{DeviceEvent, ElementState, RawKeyEvent}; + +use super::keycode_from_scancode; + +/// Spawn Device event thread. Only works on x11 since wayland doesn't have such global events. +pub fn spawn(device_tx: glib::Sender) { + std::thread::spawn(move || unsafe { + let xlib = xlib::Xlib::open().unwrap(); + let xinput2 = xinput2::XInput2::open().unwrap(); + let display = (xlib.XOpenDisplay)(ptr::null()); + let root = (xlib.XDefaultRootWindow)(display); + // TODO Add more device event mask + let mask = xinput2::XI_RawKeyPressMask | xinput2::XI_RawKeyReleaseMask; + let mut event_mask = xinput2::XIEventMask { + deviceid: xinput2::XIAllMasterDevices, + mask: &mask as *const _ as *mut c_uchar, + mask_len: std::mem::size_of_val(&mask) as c_int, + }; + (xinput2.XISelectEvents)(display, root, &mut event_mask as *mut _, 1); + + #[allow(clippy::uninit_assumed_init)] + let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init(); + loop { + (xlib.XNextEvent)(display, &mut event); + + // XFilterEvent tells us when an event has been discarded by the input method. + // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, + // along with an extra copy of the KeyRelease events. This also prevents backspace and + // arrow keys from being detected twice. + if xlib::True == { + (xlib.XFilterEvent)(&mut event, { + let xev: &xlib::XAnyEvent = event.as_ref(); + xev.window + }) + } { + continue; + } + + let event_type = event.get_type(); + if event_type == xlib::GenericEvent { + let mut xev = event.generic_event_cookie; + if (xlib.XGetEventData)(display, &mut xev) == xlib::True { + match xev.evtype { + xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { + let xev: &xinput2::XIRawEvent = &*(xev.data as *const _); + let physical_key = keycode_from_scancode(xev.detail as u32); + let state = match xev.evtype { + xinput2::XI_RawKeyPress => ElementState::Pressed, + xinput2::XI_RawKeyRelease => ElementState::Released, + _ => unreachable!(), + }; + + let event = RawKeyEvent { + physical_key, + state, + }; + + if let Err(e) = device_tx.send(DeviceEvent::Key(event)) { + log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e); + break; + } + } + _ => {} + } + } + } + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs new file mode 100644 index 000000000..0859023b6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs @@ -0,0 +1,1322 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + cell::{Cell, RefCell}, + collections::{HashSet, VecDeque}, + error::Error, + process, + rc::Rc, + sync::atomic::{AtomicBool, Ordering}, + time::Instant, +}; + +use cairo::{RectangleInt, Region}; +use crossbeam_channel::SendError; +use gdk::{Cursor, CursorType, EventKey, EventMask, ScrollDirection, WindowEdge, WindowState}; +use gio::Cancellable; +use glib::{source::Priority, MainContext}; +use gtk::{ + cairo, gdk, gio, + glib::{self}, + prelude::*, + Settings, +}; + +#[cfg(feature = "x11")] +use crate::platform_impl::platform::device; +use crate::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, + error::ExternalError, + event::{ + ElementState, Event, MouseButton, MouseScrollDelta, StartCause, TouchPhase, WindowEvent, + }, + event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + keyboard::ModifiersState, + monitor::MonitorHandle as RootMonitorHandle, + platform_impl::platform::DEVICE_ID, + window::{ + CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, WindowId as RootWindowId, + }, +}; + +use super::{ + keyboard, + monitor::{self, MonitorHandle}, + taskbar, util, + window::{WindowId, WindowRequest}, +}; + +use taskbar::TaskbarIndicator; + +#[derive(Clone)] +pub struct EventLoopWindowTarget { + /// Gdk display + pub(crate) display: gdk::Display, + /// Gtk application + pub(crate) app: gtk::Application, + /// Window Ids of the application + pub(crate) windows: Rc>>, + /// Window requests sender + pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, + /// Draw event sender + pub(crate) draw_tx: crossbeam_channel::Sender, + _marker: std::marker::PhantomData, +} + +impl EventLoopWindowTarget { + #[inline] + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + monitor::from_point(&self.display, x, y) + } + #[inline] + pub fn available_monitors(&self) -> VecDeque { + let mut handles = VecDeque::new(); + let display = &self.display; + let numbers = display.n_monitors(); + + for i in 0..numbers { + let monitor = MonitorHandle::new(display, i); + handles.push_back(monitor); + } + + handles + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + let monitor = self.display.primary_monitor(); + monitor.map(|monitor| RootMonitorHandle { + inner: MonitorHandle { monitor }, + }) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + if self.is_wayland() { + let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); + display_handle.display = unsafe { + gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) + }; + rwh_05::RawDisplayHandle::Wayland(display_handle) + } else { + let mut display_handle = rwh_05::XlibDisplayHandle::empty(); + unsafe { + if let Ok(xlib) = x11_dl::xlib::Xlib::open() { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + display_handle.display = display as _; + display_handle.screen = (xlib.XDefaultScreen)(display) as _; + } + } + + rwh_05::RawDisplayHandle::Xlib(display_handle) + } + } + + #[cfg(feature = "rwh_06")] + pub fn raw_display_handle_rwh_06(&self) -> Result { + if self.is_wayland() { + let display = unsafe { + gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) + }; + let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; + let display_handle = rwh_06::WaylandDisplayHandle::new(display); + Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) + } else { + #[cfg(feature = "x11")] + unsafe { + if let Ok(xlib) = x11_dl::xlib::Xlib::open() { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + let screen = (xlib.XDefaultScreen)(display) as _; + let display = std::ptr::NonNull::new_unchecked(display as _); + let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); + Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) + } else { + Err(rwh_06::HandleError::Unavailable) + } + } + #[cfg(not(feature = "x11"))] + Err(rwh_06::HandleError::Unavailable) + } + } + + pub fn is_wayland(&self) -> bool { + self.display.backend().is_wayland() + } + + #[cfg(feature = "x11")] + pub fn is_x11(&self) -> bool { + self.display.backend().is_x11() + } + + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position(self.is_wayland()) + } + + #[inline] + pub fn set_progress_bar(&self, progress: ProgressBarState) { + if let Err(e) = self + .window_requests_tx + .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) + { + log::warn!("Fail to send update progress bar request: {e}"); + } + } + + #[inline] + pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { + if let Err(e) = self.window_requests_tx.send(( + WindowId::dummy(), + WindowRequest::BadgeCount(count, desktop_filename), + )) { + log::warn!("Fail to send update progress bar request: {e}"); + } + } + + #[inline] + pub fn set_theme(&self, theme: Option) { + if let Err(e) = self + .window_requests_tx + .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) + { + log::warn!("Fail to send update theme request: {e}"); + } + } +} + +pub struct EventLoop { + /// Window target. + window_target: RootELW, + /// User event sender for EventLoopProxy + pub(crate) user_event_tx: crossbeam_channel::Sender>, + /// Event queue of EventLoop + events: crossbeam_channel::Receiver>, + /// Draw queue of EventLoop + draws: crossbeam_channel::Receiver, + /// Boolean to control device event thread + run_device_thread: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) any_thread: bool, + pub(crate) app_id: Option, +} + +impl EventLoop { + pub(crate) fn new(attrs: &PlatformSpecificEventLoopAttributes) -> EventLoop { + if !attrs.any_thread { + assert_is_main_thread("new_any_thread"); + } + + let context = MainContext::default(); + context + .with_thread_default(|| { + EventLoop::new_gtk(attrs.app_id.as_deref()).expect("Failed to initialize gtk backend!") + }) + .expect("Failed to initialize gtk backend!") + } + + fn new_gtk(app_id: Option<&str>) -> Result, Box> { + // This should be done by gtk::Application::new, but does not work properly + gtk::init()?; + let context = MainContext::default(); + let app = gtk::Application::new(app_id, gio::ApplicationFlags::empty()); + let app_ = app.clone(); + let cancellable: Option<&Cancellable> = None; + app.register(cancellable)?; + + // Send StartCause::Init event + let (event_tx, event_rx) = crossbeam_channel::unbounded(); + let (draw_tx, draw_rx) = crossbeam_channel::unbounded(); + let event_tx_ = event_tx.clone(); + app.connect_activate(move |_| { + if let Err(e) = event_tx_.send(Event::NewEvents(StartCause::Init)) { + log::warn!("Failed to send init event to event channel: {}", e); + } + }); + let draw_tx_ = draw_tx.clone(); + let user_event_tx = event_tx.clone(); + + // Create event loop window target. + let (window_requests_tx, window_requests_rx) = glib::MainContext::channel(Priority::default()); + let display = gdk::Display::default() + .expect("GdkDisplay not found. This usually means `gkt_init` hasn't called yet."); + let window_target = EventLoopWindowTarget { + display, + app, + windows: Rc::new(RefCell::new(HashSet::new())), + window_requests_tx, + draw_tx: draw_tx_, + _marker: std::marker::PhantomData, + }; + + // Spawn x11 thread to receive Device events. + #[cfg(feature = "x11")] + let run_device_thread = if window_target.is_x11() { + let (device_tx, device_rx) = glib::MainContext::channel(glib::Priority::default()); + let user_event_tx = user_event_tx.clone(); + let run_device_thread = Rc::new(AtomicBool::new(true)); + let run = run_device_thread.clone(); + device::spawn(device_tx); + device_rx.attach(Some(&context), move |event| { + if let Err(e) = user_event_tx.send(Event::DeviceEvent { + device_id: DEVICE_ID, + event, + }) { + log::warn!("Fail to send device event to event channel: {}", e); + } + if run.load(Ordering::Relaxed) { + glib::ControlFlow::Continue + } else { + glib::ControlFlow::Break + } + }); + Some(run_device_thread) + } else { + None + }; + #[cfg(not(feature = "x11"))] + let run_device_thread = None; + + let mut taskbar = TaskbarIndicator::new(); + let is_wayland = window_target.is_wayland(); + + // Receive portal events + #[cfg(feature = "dbus")] + { + let tx_requests_clone = window_target.window_requests_tx.clone(); + if let Err(e) = super::portal::receive_theme_changed(tx_requests_clone) { + log::debug!("Unable to receive theme changed events: {e}"); + } + } + + // Window Request + window_requests_rx.attach(Some(&context), move |(id, request)| { + if let Some(window) = app_.window_by_id(id.0) { + match request { + WindowRequest::Title(title) => window.set_title(&title), + WindowRequest::Position((x, y)) => window.move_(x, y), + WindowRequest::Size((w, h)) => window.resize(w, h), + WindowRequest::SizeConstraints(constraints) => { + util::set_size_constraints(&window, constraints); + } + WindowRequest::Visible(visible) => { + if visible { + window.show_all(); + } else { + window.hide(); + } + } + WindowRequest::Focus => { + window.present_with_time(gdk::ffi::GDK_CURRENT_TIME as _); + } + WindowRequest::Resizable(resizable) => window.set_resizable(resizable), + WindowRequest::Closable(closable) => window.set_deletable(closable), + WindowRequest::Minimized(minimized) => { + if minimized { + window.iconify(); + } else { + window.deiconify(); + } + } + WindowRequest::Maximized(maximized, resizable) => { + if maximized { + let maximize_process = util::WindowMaximizeProcess::new(window.clone(), resizable); + glib::idle_add_local_full(glib::Priority::DEFAULT_IDLE, move || { + let mut maximize_process = maximize_process.borrow_mut(); + maximize_process.next_step() + }); + } else { + window.unmaximize(); + } + } + WindowRequest::DragWindow => { + if let Some(cursor) = window + .display() + .default_seat() + .and_then(|seat| seat.pointer()) + { + let (_, x, y) = cursor.position(); + window.begin_move_drag(1, x, y, 0); + } + } + WindowRequest::DragResizeWindow(direction) => { + if let Some(cursor) = window + .display() + .default_seat() + .and_then(|seat| seat.pointer()) + { + let (_, x, y) = cursor.position(); + window.begin_resize_drag( + direction.to_gtk_edge(), + 1, + x, + y, + gtk::gdk::ffi::GDK_CURRENT_TIME as _, + ); + } + } + WindowRequest::Fullscreen(fullscreen) => match fullscreen { + Some(f) => { + if let Fullscreen::Borderless(m) = f { + if let Some(monitor) = m { + let display = window.display(); + let monitor = monitor.inner; + let monitors = display.n_monitors(); + for i in 0..monitors { + let m = display.monitor(i).unwrap(); + if m == monitor.monitor { + let screen = display.default_screen(); + window.fullscreen_on_monitor(&screen, i); + } + } + } else { + window.fullscreen(); + } + } + } + None => window.unfullscreen(), + }, + WindowRequest::Decorations(decorations) => window.set_decorated(decorations), + WindowRequest::AlwaysOnBottom(always_on_bottom) => { + window.set_keep_below(always_on_bottom) + } + WindowRequest::AlwaysOnTop(always_on_top) => window.set_keep_above(always_on_top), + WindowRequest::WindowIcon(window_icon) => { + if let Some(icon) = window_icon { + window.set_icon(Some(&icon.inner.into())); + } + } + WindowRequest::UserAttention(request_type) => { + window.set_urgency_hint(request_type.is_some()) + } + WindowRequest::SetSkipTaskbar(skip) => { + window.set_skip_taskbar_hint(skip); + window.set_skip_pager_hint(skip) + } + WindowRequest::BackgroundColor(css_provider, color) => { + unsafe { window.set_data("background_color", color) }; + + let style_context = window.style_context(); + style_context.remove_provider(&css_provider); + + if let Some(color) = color { + let theme = format!( + r#" + window {{ + background-color: rgba({},{},{},{}); + }} + "#, + color.0, + color.1, + color.2, + color.3 as f64 / 255.0 + ); + let _ = css_provider.load_from_data(theme.as_bytes()); + style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + }; + } + WindowRequest::SetVisibleOnAllWorkspaces(visible) => { + if visible { + window.stick(); + } else { + window.unstick(); + } + } + WindowRequest::CursorIcon(cursor) => { + if let Some(gdk_window) = window.window() { + let display = window.display(); + match cursor { + Some(cr) => { + gdk_window.set_cursor(Cursor::from_name(&display, cr.to_str()).as_ref()) + } + None => gdk_window + .set_cursor(Cursor::for_display(&display, CursorType::BlankCursor).as_ref()), + } + }; + } + WindowRequest::CursorPosition((x, y)) => { + if let Some(cursor) = window + .display() + .default_seat() + .and_then(|seat| seat.pointer()) + { + if let Some(screen) = GtkWindowExt::screen(&window) { + cursor.warp(&screen, x, y); + } + } + } + WindowRequest::CursorIgnoreEvents(ignore) => { + if ignore { + let empty_region = Region::create_rectangle(&RectangleInt::new(0, 0, 1, 1)); + window + .window() + .unwrap() + .input_shape_combine_region(&empty_region, 0, 0); + } else { + window.input_shape_combine_region(None) + }; + } + WindowRequest::ProgressBarState(_) => unreachable!(), + WindowRequest::BadgeCount(_, _) => unreachable!(), + WindowRequest::SetTheme(_) => unreachable!(), + WindowRequest::WireUpEvents { + transparent, + fullscreen, + cursor_moved, + } => { + window.add_events( + EventMask::POINTER_MOTION_MASK + | EventMask::BUTTON1_MOTION_MASK + | EventMask::BUTTON_PRESS_MASK + | EventMask::TOUCH_MASK + | EventMask::STRUCTURE_MASK + | EventMask::FOCUS_CHANGE_MASK + | EventMask::SCROLL_MASK, + ); + + let fullscreen = Rc::new(AtomicBool::new(fullscreen)); + let fullscreen_ = fullscreen.clone(); + window.connect_window_state_event(move |_window, event| { + let state = event.changed_mask(); + if state.contains(WindowState::FULLSCREEN) { + fullscreen_.store( + event.new_window_state().contains(WindowState::FULLSCREEN), + Ordering::Relaxed, + ); + } + glib::Propagation::Proceed + }); + + // Allow resizing unmaximized non-fullscreen undecorated window + let fullscreen_ = fullscreen.clone(); + window.connect_motion_notify_event(move |window, event| { + if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { + if let Some(window) = window.window() { + let (cx, cy) = event.root(); + let (left, top) = window.position(); + let (w, h) = (window.width(), window.height()); + let (right, bottom) = (left + w, top + h); + let border = window.scale_factor() * 5; + let edge = crate::window::hit_test( + (left, top, right, bottom), + cx as _, + cy as _, + border, + border, + ); + + let edge = match &edge { + Some(e) if !fullscreen_.load(Ordering::Relaxed) => e.to_cursor_str(), + _ => "default", + }; + window.set_cursor(Cursor::from_name(&window.display(), edge).as_ref()); + } + } + glib::Propagation::Proceed + }); + window.connect_button_press_event(move |window, event| { + const LMB: u32 = 1; + if (is_wayland || !window.is_decorated()) + && window.is_resizable() + && !window.is_maximized() + && event.button() == LMB + { + let (cx, cy) = event.root(); + let (left, top) = window.position(); + let (w, h) = window.size(); + let (right, bottom) = (left + w, top + h); + let border = window.scale_factor() * 5; + let edge = crate::window::hit_test( + (left, top, right, bottom), + cx as _, + cy as _, + border, + border, + ) + .map(|d| d.to_gtk_edge()) + // we return `WindowEdge::__Unknown` to be ignored later. + // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. + .unwrap_or(WindowEdge::__Unknown(8)); + // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. + match edge { + WindowEdge::__Unknown(_) => (), + _ => { + // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead + window.begin_resize_drag(edge, LMB as i32, cx as i32, cy as i32, event.time()) + } + } + } + + glib::Propagation::Proceed + }); + window.connect_touch_event(move |window, event| { + if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { + if let Some(window) = window.window() { + if let Some((cx, cy)) = event.root_coords() { + if let Some(device) = event.device() { + let (left, top) = window.position(); + let (w, h) = (window.width(), window.height()); + let (right, bottom) = (left + w, top + h); + let border = window.scale_factor() * 5; + let edge = crate::window::hit_test( + (left, top, right, bottom), + cx as _, + cy as _, + border, + border, + ) + .map(|d| d.to_gtk_edge()) + // we return `WindowEdge::__Unknown` to be ignored later. + // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. + .unwrap_or(WindowEdge::__Unknown(8)); + + // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. + match edge { + WindowEdge::__Unknown(_) => (), + _ => window.begin_resize_drag_for_device( + edge, + &device, + 0, + cx as i32, + cy as i32, + event.time(), + ), + } + } + } + } + } + + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_delete_event(move |_, _| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CloseRequested, + }) { + log::warn!("Failed to send window close event to event channel: {}", e); + } + glib::Propagation::Stop + }); + + let tx_clone = event_tx.clone(); + window.connect_configure_event(move |window, event| { + let scale_factor = window.scale_factor(); + + let (x, y) = window + .window() + .map(|w| w.root_origin()) + .unwrap_or_else(|| event.position()); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Moved( + LogicalPosition::new(x, y).to_physical(scale_factor as f64), + ), + }) { + log::warn!("Failed to send window moved event to event channel: {}", e); + } + + let (w, h) = event.size(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized( + LogicalSize::new(w, h).to_physical(scale_factor as f64), + ), + }) { + log::warn!( + "Failed to send window resized event to event channel: {}", + e + ); + } + false + }); + + let tx_clone = event_tx.clone(); + window.connect_focus_in_event(move |_, _| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(true), + }) { + log::warn!( + "Failed to send window focus-in event to event channel: {}", + e + ); + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_focus_out_event(move |_, _| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(false), + }) { + log::warn!( + "Failed to send window focus-out event to event channel: {}", + e + ); + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_destroy(move |_| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Destroyed, + }) { + log::warn!( + "Failed to send window destroyed event to event channel: {}", + e + ); + } + }); + + let tx_clone = event_tx.clone(); + window.connect_enter_notify_event(move |_, _| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorEntered { + device_id: DEVICE_ID, + }, + }) { + log::warn!( + "Failed to send cursor entered event to event channel: {}", + e + ); + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + let last_motion_time = Rc::new(Cell::new(0u32)); + window.connect_motion_notify_event(move |window, motion| { + let last = last_motion_time.replace(motion.time()); + if cursor_moved && motion.time() != last { + if let Some(cursor) = motion.device() { + let scale_factor = window.scale_factor(); + let (_, x, y) = cursor.window_at_position(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { + position: LogicalPosition::new(x, y).to_physical(scale_factor as f64), + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), + }, + }) { + log::warn!("Failed to send cursor moved event to event channel: {}", e); + } + } + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_leave_notify_event(move |_, _| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorLeft { + device_id: DEVICE_ID, + }, + }) { + log::warn!("Failed to send cursor left event to event channel: {}", e); + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + let last_press_time = Rc::new(Cell::new(0u32)); + window.connect_button_press_event(move |_, event| { + let last = last_press_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Pressed, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), + }, + }) { + log::warn!( + "Failed to send mouse input pressed event to event channel: {}", + e + ); + } + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + let last_release_time = Rc::new(Cell::new(0u32)); + window.connect_button_release_event(move |_, event| { + let last = last_release_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Released, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), + }, + }) { + log::warn!( + "Failed to send mouse input released event to event channel: {}", + e + ); + } + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_scroll_event(move |_, event| { + let (x, y) = event.delta(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta: MouseScrollDelta::LineDelta(-x as f32, -y as f32), + phase: match event.direction() { + ScrollDirection::Smooth => TouchPhase::Moved, + _ => TouchPhase::Ended, + }, + modifiers: ModifiersState::empty(), + }, + }) { + log::warn!("Failed to send scroll event to event channel: {}", e); + } + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + let keyboard_handler = Rc::new(move |event_key: EventKey, element_state| { + // if we have a modifier lets send it + let mut mods = keyboard::get_modifiers(event_key.clone()); + if !mods.is_empty() { + // if we release the modifier tell the world + if ElementState::Released == element_state { + mods = ModifiersState::empty(); + } + + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(mods), + }) { + log::warn!( + "Failed to send modifiers changed event to event channel: {}", + e + ); + } else { + // stop here we don't want to send the key event + // as we emit the `ModifiersChanged` + return glib::ControlFlow::Continue; + } + } + + // todo: implement repeat? + let event = keyboard::make_key_event(&event_key, false, None, element_state); + + if let Some(event) = event { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }, + }) { + log::warn!("Failed to send keyboard event to event channel: {}", e); + } + } + glib::ControlFlow::Continue + }); + + let tx_clone = event_tx.clone(); + // TODO Add actual IME from system + let ime = gtk::IMContextSimple::default(); + ime.set_client_window(window.window().as_ref()); + ime.focus_in(); + ime.connect_commit(move |_, s| { + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ReceivedImeText(s.to_string()), + }) { + log::warn!( + "Failed to send received IME text event to event channel: {}", + e + ); + } + }); + + let handler = keyboard_handler.clone(); + window.connect_key_press_event(move |_, event_key| { + handler(event_key.to_owned(), ElementState::Pressed); + ime.filter_keypress(event_key); + + glib::Propagation::Proceed + }); + + let handler = keyboard_handler.clone(); + window.connect_key_release_event(move |_, event_key| { + handler(event_key.to_owned(), ElementState::Released); + glib::Propagation::Proceed + }); + + let tx_clone = event_tx.clone(); + window.connect_window_state_event(move |window, event| { + let state = event.changed_mask(); + if state.contains(WindowState::ICONIFIED) || state.contains(WindowState::MAXIMIZED) { + let scale_factor = window.scale_factor(); + + let (x, y) = window.position(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Moved( + LogicalPosition::new(x, y).to_physical(scale_factor as f64), + ), + }) { + log::warn!("Failed to send window moved event to event channel: {}", e); + } + + let (w, h) = window.size(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Resized( + LogicalSize::new(w, h).to_physical(scale_factor as f64), + ), + }) { + log::warn!( + "Failed to send window resized event to event channel: {}", + e + ); + } + } + glib::Propagation::Proceed + }); + + // Receive draw events of the window. + let draw_clone = draw_tx.clone(); + window.connect_draw(move |window, cr| { + if let Err(e) = draw_clone.send(id) { + log::warn!("Failed to send redraw event to event channel: {}", e); + } + + if transparent { + let background_color = unsafe { + window + .data::>("background_color") + .and_then(|c| *c.as_ref()) + }; + + let rgba = background_color + .map(|(r, g, b, a)| (r as f64, g as f64, b as f64, a as f64 / 255.0)) + .unwrap_or((0., 0., 0., 0.)); + + let rect = window + .child() + .map(|c| c.allocation()) + .unwrap_or_else(|| window.allocation()); + + cr.rectangle( + rect.x() as _, + rect.y() as _, + rect.width() as _, + rect.height() as _, + ); + cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3); + cr.set_operator(cairo::Operator::Source); + let _ = cr.fill(); + cr.set_operator(cairo::Operator::Over); + } + + glib::Propagation::Proceed + }); + } + } + } else if id == WindowId::dummy() { + match request { + WindowRequest::ProgressBarState(state) => { + taskbar.update(state); + } + WindowRequest::BadgeCount(count, desktop_filename) => { + taskbar.update_count(count, desktop_filename); + } + WindowRequest::SetTheme(theme) => { + if let Some(settings) = Settings::default() { + settings.set_gtk_application_prefer_dark_theme(theme == Some(Theme::Dark)); + if let Err(e) = event_tx.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ThemeChanged(theme.unwrap_or_default()), + }) { + log::warn!( + "Failed to send window theme changed event to event channel: {}", + e + ); + } + } + } + _ => unreachable!(), + } + } + glib::ControlFlow::Continue + }); + + // Create event loop itself. + let event_loop = Self { + window_target: RootELW { + p: window_target, + _marker: std::marker::PhantomData, + }, + user_event_tx, + events: event_rx, + draws: draw_rx, + run_device_thread, + }; + + Ok(event_loop) + } + + #[inline] + pub fn run(mut self, callback: F) -> ! + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow) + 'static, + { + let exit_code = self.run_return(callback); + process::exit(exit_code) + } + + /// This is the core event loop logic. It basically loops on `gtk_main_iteration` and processes one + /// event along with that iteration. Depends on current control flow and what it should do, an + /// event state is defined. The whole state flow chart runs like following: + /// + /// ```ignore + /// Poll/Wait/WaitUntil + /// +-------------------------------------------------------------------------+ + /// | | + /// | Receiving event from event channel | Receiving event from draw channel + /// | +-------+ | +---+ + /// v v | | v | + /// +----------+ Poll/Wait/WaitUntil +------------+ Poll/Wait/WaitUntil +-----------+ | + /// | NewStart | ---------------------> | EventQueue | ---------------------> | DrawQueue | | + /// +----------+ +------------+ +-----------+ | + /// |ExitWithCode |ExitWithCode ExitWithCode| | | + /// +------------------------------------+------------------------------------+ +---+ + /// | + /// v + /// +---------------+ + /// | LoopDestroyed | + /// +---------------+ + /// ``` + /// + /// There are a dew notibale event will sent to callback when state is transisted: + /// - On any state moves to `LoopDestroyed`, a `LoopDestroyed` event is sent. + /// - On `NewStart` to `EventQueue`, a `NewEvents` with corresponding `StartCause` depends on + /// current control flow is sent. + /// - On `EventQueue` to `DrawQueue`, a `MainEventsCleared` event is sent. + /// - On `DrawQueue` back to `NewStart`, a `RedrawEventsCleared` event is sent. + pub(crate) fn run_return(&mut self, mut callback: F) -> i32 + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + enum EventState { + NewStart, + EventQueue, + DrawQueue, + } + + let context = MainContext::default(); + let run_device_thread = self.run_device_thread.clone(); + + context + .with_thread_default(|| { + let mut control_flow = ControlFlow::default(); + let window_target = &self.window_target; + let events = &self.events; + let draws = &self.draws; + + window_target.p.app.activate(); + + // If this is a secondary (remote) GIO instance, the activate signal + // was forwarded to the primary instance via D-Bus. Exit immediately so + // the primary can handle focus (e.g. bring its window to front). + // Without this, the secondary hangs forever waiting for a StartCause::Init + // event that never arrives (connect_activate only fires on the primary). + if window_target.p.app.is_remote() { + return 0; + } + + let mut state = EventState::NewStart; + let exit_code = loop { + let mut blocking = false; + match state { + EventState::NewStart => match control_flow { + ControlFlow::ExitWithCode(code) => { + callback(Event::LoopDestroyed, window_target, &mut control_flow); + break code; + } + ControlFlow::Wait => { + if !events.is_empty() { + callback( + Event::NewEvents(StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }), + window_target, + &mut control_flow, + ); + state = EventState::EventQueue; + } else { + blocking = true; + } + } + ControlFlow::WaitUntil(requested_resume) => { + let start = Instant::now(); + if start >= requested_resume { + callback( + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }), + window_target, + &mut control_flow, + ); + state = EventState::EventQueue; + } else if !events.is_empty() { + callback( + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + }), + window_target, + &mut control_flow, + ); + state = EventState::EventQueue; + } else { + blocking = true; + } + } + _ => { + callback( + Event::NewEvents(StartCause::Poll), + window_target, + &mut control_flow, + ); + state = EventState::EventQueue; + } + }, + EventState::EventQueue => match control_flow { + ControlFlow::ExitWithCode(code) => { + callback(Event::LoopDestroyed, window_target, &mut control_flow); + break code; + } + _ => match events.try_recv() { + Ok(event) => match event { + Event::LoopDestroyed => control_flow = ControlFlow::ExitWithCode(1), + _ => callback(event, window_target, &mut control_flow), + }, + Err(_) => { + callback(Event::MainEventsCleared, window_target, &mut control_flow); + state = EventState::DrawQueue; + } + }, + }, + EventState::DrawQueue => match control_flow { + ControlFlow::ExitWithCode(code) => { + callback(Event::LoopDestroyed, window_target, &mut control_flow); + break code; + } + _ => { + if let Ok(id) = draws.try_recv() { + callback( + Event::RedrawRequested(RootWindowId(id)), + window_target, + &mut control_flow, + ); + } + callback(Event::RedrawEventsCleared, window_target, &mut control_flow); + state = EventState::NewStart; + } + }, + } + gtk::main_iteration_do(blocking); + }; + if let Some(run_device_thread) = run_device_thread { + run_device_thread.store(false, Ordering::Relaxed); + } + exit_code + }) + .unwrap_or(1) + } + + #[inline] + pub fn window_target(&self) -> &RootELW { + &self.window_target + } + + /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + user_event_tx: self.user_event_tx.clone(), + } + } +} + +/// Used to send custom events to `EventLoop`. +#[derive(Debug)] +pub struct EventLoopProxy { + user_event_tx: crossbeam_channel::Sender>, +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + user_event_tx: self.user_event_tx.clone(), + } + } +} + +impl EventLoopProxy { + /// Send an event to the `EventLoop` from which this proxy was created. This emits a + /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this + /// function. + /// + /// Returns an `Err` if the associated `EventLoop` no longer exists. + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self + .user_event_tx + .send(Event::UserEvent(event)) + .map_err(|SendError(event)| { + if let Event::UserEvent(error) = event { + EventLoopClosed(error) + } else { + unreachable!(); + } + })?; + + let context = MainContext::default(); + context.wakeup(); + + Ok(()) + } +} + +fn assert_is_main_thread(suggested_method: &str) { + assert!( + is_main_thread(), + "Initializing the event loop outside of the main thread is a significant \ + cross-platform compatibility hazard. If you really, absolutely need to create an \ + EventLoop on a different thread, please use the `EventLoopExtUnix::{suggested_method}` function." + ); +} + +#[cfg(target_os = "linux")] +fn is_main_thread() -> bool { + use libc::{c_long, getpid, syscall, SYS_gettid}; + + unsafe { syscall(SYS_gettid) == getpid() as c_long } +} + +#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] +fn is_main_thread() -> bool { + use libc::pthread_main_np; + + unsafe { pthread_main_np() == 1 } +} + +#[cfg(target_os = "netbsd")] +fn is_main_thread() -> bool { + std::thread::current().name() == Some("main") +} + +impl CursorIcon { + fn to_str(&self) -> &str { + match self { + CursorIcon::Crosshair => "crosshair", + CursorIcon::Hand => "pointer", + CursorIcon::Arrow => "arrow", + CursorIcon::Move => "move", + CursorIcon::Text => "text", + CursorIcon::Wait => "wait", + CursorIcon::Help => "help", + CursorIcon::Progress => "progress", + CursorIcon::NotAllowed => "not-allowed", + CursorIcon::ContextMenu => "context-menu", + CursorIcon::Cell => "cell", + CursorIcon::VerticalText => "vertical-text", + CursorIcon::Alias => "alias", + CursorIcon::Copy => "copy", + CursorIcon::NoDrop => "no-drop", + CursorIcon::Grab => "grab", + CursorIcon::Grabbing => "grabbing", + CursorIcon::AllScroll => "all-scroll", + CursorIcon::ZoomIn => "zoom-in", + CursorIcon::ZoomOut => "zoom-out", + CursorIcon::EResize => "e-resize", + CursorIcon::NResize => "n-resize", + CursorIcon::NeResize => "ne-resize", + CursorIcon::NwResize => "nw-resize", + CursorIcon::SResize => "s-resize", + CursorIcon::SeResize => "se-resize", + CursorIcon::SwResize => "sw-resize", + CursorIcon::WResize => "w-resize", + CursorIcon::EwResize => "ew-resize", + CursorIcon::NsResize => "ns-resize", + CursorIcon::NeswResize => "nesw-resize", + CursorIcon::NwseResize => "nwse-resize", + CursorIcon::ColResize => "col-resize", + CursorIcon::RowResize => "row-resize", + CursorIcon::Default => "default", + } + } +} + +impl ResizeDirection { + fn to_cursor_str(&self) -> &str { + match self { + ResizeDirection::East => "e-resize", + ResizeDirection::North => "n-resize", + ResizeDirection::NorthEast => "ne-resize", + ResizeDirection::NorthWest => "nw-resize", + ResizeDirection::South => "s-resize", + ResizeDirection::SouthEast => "se-resize", + ResizeDirection::SouthWest => "sw-resize", + ResizeDirection::West => "w-resize", + } + } + + fn to_gtk_edge(&self) -> WindowEdge { + match self { + ResizeDirection::East => WindowEdge::East, + ResizeDirection::North => WindowEdge::North, + ResizeDirection::NorthEast => WindowEdge::NorthEast, + ResizeDirection::NorthWest => WindowEdge::NorthWest, + ResizeDirection::South => WindowEdge::South, + ResizeDirection::SouthEast => WindowEdge::SouthEast, + ResizeDirection::SouthWest => WindowEdge::SouthWest, + ResizeDirection::West => WindowEdge::West, + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs new file mode 100644 index 000000000..bc1c18df1 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs @@ -0,0 +1,47 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use gtk::gdk_pixbuf::{Colorspace, Pixbuf}; + +use crate::window::BadIcon; + +/// An icon used for the window titlebar, taskbar, etc. +#[derive(Debug, Clone)] +pub struct PlatformIcon { + raw: Vec, + width: i32, + height: i32, + row_stride: i32, +} + +impl From for Pixbuf { + fn from(icon: PlatformIcon) -> Self { + Pixbuf::from_mut_slice( + icon.raw, + Colorspace::Rgb, + true, + 8, + icon.width, + icon.height, + icon.row_stride, + ) + } +} + +impl PlatformIcon { + /// Creates an `Icon` from 32bpp RGBA data. + /// + /// The length of `rgba` must be divisible by 4, and `width * height` must equal + /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + let row_stride = + Pixbuf::calculate_rowstride(Colorspace::Rgb, true, 8, width as i32, height as i32); + Ok(Self { + raw: rgba, + width: width as i32, + height: height as i32, + row_stride, + }) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs new file mode 100644 index 000000000..aa52b14f2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs @@ -0,0 +1,293 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use super::KeyEventExtra; +use crate::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, +}; +use gtk::{ + gdk::{self, keys::constants::*, EventKey}, + glib, +}; +use std::{ + collections::HashSet, + ffi::c_void, + os::raw::{c_int, c_uint}, + ptr, slice, + sync::{LazyLock, Mutex}, +}; + +pub type RawKey = gdk::keys::Key; + +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + +fn insert_or_get_key_str(string: String) -> &'static str { + let mut string_set = KEY_STRINGS.lock().unwrap(); + if let Some(contained) = string_set.get(string.as_str()) { + return contained; + } + let static_str = Box::leak(string.into_boxed_str()); + string_set.insert(static_str); + static_str +} + +#[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] +pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { + match gdk_key { + Escape => Some(Key::Escape), + BackSpace => Some(Key::Backspace), + Tab | ISO_Left_Tab => Some(Key::Tab), + Return => Some(Key::Enter), + Control_L | Control_R => Some(Key::Control), + Alt_L | Alt_R => Some(Key::Alt), + Shift_L | Shift_R => Some(Key::Shift), + // TODO: investigate mapping. Map Meta_[LR]? + Super_L | Super_R => Some(Key::Super), + Caps_Lock => Some(Key::CapsLock), + F1 => Some(Key::F1), + F2 => Some(Key::F2), + F3 => Some(Key::F3), + F4 => Some(Key::F4), + F5 => Some(Key::F5), + F6 => Some(Key::F6), + F7 => Some(Key::F7), + F8 => Some(Key::F8), + F9 => Some(Key::F9), + F10 => Some(Key::F10), + F11 => Some(Key::F11), + F12 => Some(Key::F12), + F13 => Some(Key::F13), + F14 => Some(Key::F14), + F15 => Some(Key::F15), + F16 => Some(Key::F16), + F17 => Some(Key::F17), + F18 => Some(Key::F18), + F19 => Some(Key::F19), + F20 => Some(Key::F20), + F21 => Some(Key::F21), + F22 => Some(Key::F22), + F23 => Some(Key::F23), + F24 => Some(Key::F24), + + Print => Some(Key::PrintScreen), + Scroll_Lock => Some(Key::ScrollLock), + // Pause/Break not audio. + Pause => Some(Key::Pause), + + Insert => Some(Key::Insert), + Delete => Some(Key::Delete), + Home => Some(Key::Home), + End => Some(Key::End), + Page_Up => Some(Key::PageUp), + Page_Down => Some(Key::PageDown), + Num_Lock => Some(Key::NumLock), + + Up => Some(Key::ArrowUp), + Down => Some(Key::ArrowDown), + Left => Some(Key::ArrowLeft), + Right => Some(Key::ArrowRight), + Clear => Some(Key::Clear), + + Menu => Some(Key::ContextMenu), + WakeUp => Some(Key::WakeUp), + Launch0 => Some(Key::LaunchApplication1), + Launch1 => Some(Key::LaunchApplication2), + ISO_Level3_Shift => Some(Key::AltGraph), + + KP_Begin => Some(Key::Clear), + KP_Delete => Some(Key::Delete), + KP_Down => Some(Key::ArrowDown), + KP_End => Some(Key::End), + KP_Enter => Some(Key::Enter), + KP_F1 => Some(Key::F1), + KP_F2 => Some(Key::F2), + KP_F3 => Some(Key::F3), + KP_F4 => Some(Key::F4), + KP_Home => Some(Key::Home), + KP_Insert => Some(Key::Insert), + KP_Left => Some(Key::ArrowLeft), + KP_Page_Down => Some(Key::PageDown), + KP_Page_Up => Some(Key::PageUp), + KP_Right => Some(Key::ArrowRight), + // KP_Separator? What does it map to? + KP_Tab => Some(Key::Tab), + KP_Up => Some(Key::ArrowUp), + + // JIS + Zenkaku_Hankaku => Some(Key::ZenkakuHankaku), + Hiragana_Katakana => Some(Key::HiraganaKatakana), + Henkan => Some(Key::Convert), + Muhenkan => Some(Key::NonConvert), + // TODO: more mappings (media etc) + _ => None, + } +} + +#[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] +pub(crate) fn raw_key_to_location(raw: RawKey) -> KeyLocation { + match raw { + Control_L | Shift_L | Alt_L | Super_L | Meta_L => KeyLocation::Left, + Control_R | Shift_R | Alt_R | Super_R | Meta_R => KeyLocation::Right, + KP_0 | KP_1 | KP_2 | KP_3 | KP_4 | KP_5 | KP_6 | KP_7 | KP_8 | KP_9 | KP_Add | KP_Begin + | KP_Decimal | KP_Delete | KP_Divide | KP_Down | KP_End | KP_Enter | KP_Equal | KP_F1 + | KP_F2 | KP_F3 | KP_F4 | KP_Home | KP_Insert | KP_Left | KP_Multiply | KP_Page_Down + | KP_Page_Up | KP_Right | KP_Separator | KP_Space | KP_Subtract | KP_Tab | KP_Up => { + KeyLocation::Numpad + } + _ => KeyLocation::Standard, + } +} + +const MODIFIER_MAP: &[(Key<'static>, ModifiersState)] = &[ + (Key::Shift, ModifiersState::SHIFT), + (Key::Alt, ModifiersState::ALT), + (Key::Control, ModifiersState::CONTROL), + (Key::Super, ModifiersState::SUPER), +]; + +// we use the EventKey to extract the modifier mainly because +// we need to have the modifier before the second key is entered to follow +// other os' logic -- this way we can emit the new `ModifiersState` before +// we receive the next key, if needed the developer can update his local state. +pub(crate) fn get_modifiers(key: EventKey) -> ModifiersState { + // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. + let scancode = key.hardware_keycode(); + // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. + let keyval = key.keyval(); + // unicode value + let unicode = keyval.to_unicode(); + // translate to tao::keyboard::Key + let key_from_code = raw_key_to_key(keyval).unwrap_or_else(|| { + if let Some(key) = unicode { + if key >= ' ' && key != '\x7f' { + Key::Character(insert_or_get_key_str(key.to_string())) + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + }); + // start with empty state + let mut result = ModifiersState::empty(); + // loop trough our modifier map + for (gdk_mod, modifier) in MODIFIER_MAP { + if key_from_code == *gdk_mod { + result |= *modifier; + } + } + result +} + +pub(crate) fn make_key_event( + key: &EventKey, + is_repeat: bool, + key_override: Option, + state: ElementState, +) -> Option { + // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. + let scancode = key.hardware_keycode(); + // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. + let keyval_without_modifiers = key.keyval(); + let keyval_with_modifiers = + hardware_keycode_to_keyval(scancode).unwrap_or(keyval_without_modifiers); + // get unicode value, with and without modifiers + let text_without_modifiers = keyval_with_modifiers.to_unicode(); + let text_with_modifiers = keyval_without_modifiers.to_unicode(); + // get physical key from the scancode (keycode) + let physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); + + // extract key without modifier + let key_without_modifiers = raw_key_to_key(keyval_with_modifiers).unwrap_or_else(|| { + if let Some(key) = text_without_modifiers { + if key >= ' ' && key != '\x7f' { + Key::Character(insert_or_get_key_str(key.to_string())) + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + }); + + // extract the logical key + let logical_key = raw_key_to_key(keyval_without_modifiers).unwrap_or_else(|| { + if let Some(key) = text_with_modifiers { + if key >= ' ' && key != '\x7f' { + Key::Character(insert_or_get_key_str(key.to_string())) + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + } else { + Key::Unidentified(NativeKeyCode::Gtk(scancode)) + } + }); + + // make sure we have a valid key + if !matches!(key_without_modifiers, Key::Unidentified(_)) { + let location = raw_key_to_location(keyval_with_modifiers); + let text_with_all_modifiers = + text_without_modifiers.map(|text| insert_or_get_key_str(text.to_string())); + return Some(KeyEvent { + location, + logical_key, + physical_key, + repeat: is_repeat, + state, + text: text_with_all_modifiers, + platform_specific: KeyEventExtra { + text_with_all_modifiers, + key_without_modifiers, + }, + }); + } else { + #[cfg(debug_assertions)] + eprintln!("Couldn't get key from code: {physical_key:?}"); + } + None +} + +/// Map a hardware keycode to a keyval by performing a lookup in the keymap and finding the +/// keyval with the lowest group and level +fn hardware_keycode_to_keyval(keycode: u16) -> Option { + use glib::translate::FromGlib; + unsafe { + let keymap = gdk::ffi::gdk_keymap_get_default(); + + let mut nkeys = 0; + let mut keys: *mut gdk::ffi::GdkKeymapKey = ptr::null_mut(); + let mut keyvals: *mut c_uint = ptr::null_mut(); + + // call into gdk to retrieve the keyvals and keymap keys + gdk::ffi::gdk_keymap_get_entries_for_keycode( + keymap, + c_uint::from(keycode), + &mut keys as *mut *mut gdk::ffi::GdkKeymapKey, + &mut keyvals as *mut *mut c_uint, + &mut nkeys as *mut c_int, + ); + + if nkeys > 0 { + let keyvals_slice = slice::from_raw_parts(keyvals, nkeys as usize); + let keys_slice = slice::from_raw_parts(keys, nkeys as usize); + + let resolved_keyval = keys_slice.iter().enumerate().find_map(|(id, gdk_keymap)| { + if gdk_keymap.group == 0 && gdk_keymap.level == 0 { + Some(RawKey::from_glib(keyvals_slice[id])) + } else { + None + } + }); + + // notify glib to free the allocated arrays + glib::ffi::g_free(keyvals as *mut c_void); + glib::ffi::g_free(keys as *mut c_void); + + return resolved_keyval; + } + } + None +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs new file mode 100644 index 000000000..e63e6c951 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs @@ -0,0 +1,325 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::keyboard::{KeyCode, NativeKeyCode}; + +pub fn keycode_to_scancode(code: KeyCode) -> Option { + // See `from_scancode` for more info + match code { + KeyCode::Backquote => Some(0x0031), + KeyCode::Backslash => Some(0x0033), + KeyCode::Backspace => Some(0x0016), + KeyCode::BracketLeft => Some(0x0022), + KeyCode::BracketRight => Some(0x001B), + KeyCode::Comma => Some(0x003B), + KeyCode::Digit0 => Some(0x0013), + KeyCode::Digit1 => Some(0x000A), + KeyCode::Digit2 => Some(0x000B), + KeyCode::Digit3 => Some(0x000C), + KeyCode::Digit4 => Some(0x000D), + KeyCode::Digit5 => Some(0x000E), + KeyCode::Digit6 => Some(0x000F), + KeyCode::Digit7 => Some(0x0010), + KeyCode::Digit8 => Some(0x0011), + KeyCode::Digit9 => Some(0x0012), + KeyCode::Equal => Some(0x0015), + KeyCode::IntlBackslash => Some(0x005E), + KeyCode::IntlRo => Some(0x0061), + KeyCode::IntlYen => Some(0x0084), + KeyCode::KeyA => Some(0x0026), + KeyCode::KeyB => Some(0x0038), + KeyCode::KeyC => Some(0x0036), + KeyCode::KeyD => Some(0x0028), + KeyCode::KeyE => Some(0x001A), + KeyCode::KeyF => Some(0x0029), + KeyCode::KeyG => Some(0x002A), + KeyCode::KeyH => Some(0x002B), + KeyCode::KeyI => Some(0x001F), + KeyCode::KeyJ => Some(0x002C), + KeyCode::KeyK => Some(0x002D), + KeyCode::KeyL => Some(0x002E), + KeyCode::KeyM => Some(0x002E), + KeyCode::KeyN => Some(0x0039), + KeyCode::KeyO => Some(0x0020), + KeyCode::KeyP => Some(0x0021), + KeyCode::KeyQ => Some(0x0018), + KeyCode::KeyR => Some(0x001B), + KeyCode::KeyS => Some(0x0027), + KeyCode::KeyT => Some(0x001C), + KeyCode::KeyU => Some(0x001E), + KeyCode::KeyV => Some(0x0037), + KeyCode::KeyW => Some(0x0019), + KeyCode::KeyX => Some(0x0035), + KeyCode::KeyY => Some(0x001D), + KeyCode::KeyZ => Some(0x0034), + KeyCode::Minus => Some(0x0014), + KeyCode::Period => Some(0x003C), + KeyCode::Quote => Some(0x0030), + KeyCode::Semicolon => Some(0x002F), + KeyCode::Slash => Some(0x003D), + KeyCode::AltLeft => Some(0x0040), + KeyCode::AltRight => Some(0x006C), + KeyCode::CapsLock => Some(0x0042), + KeyCode::ContextMenu => Some(0x0087), + KeyCode::ControlLeft => Some(0x0025), + KeyCode::ControlRight => Some(0x0069), + KeyCode::Enter => Some(0x0024), + KeyCode::SuperLeft => Some(0x0085), + KeyCode::SuperRight => Some(0x0086), + KeyCode::ShiftLeft => Some(0x0032), + KeyCode::ShiftRight => Some(0x003E), + KeyCode::Space => Some(0x0041), + KeyCode::Tab => Some(0x0017), + KeyCode::Convert => Some(0x0064), + KeyCode::Lang1 => Some(0x0082), + KeyCode::Lang2 => Some(0x0083), + KeyCode::KanaMode => Some(0x0065), + KeyCode::NonConvert => Some(0x0066), + KeyCode::Delete => Some(0x0077), + KeyCode::End => Some(0x0073), + KeyCode::Home => Some(0x006E), + KeyCode::Insert => Some(0x0076), + KeyCode::PageDown => Some(0x0075), + KeyCode::PageUp => Some(0x0070), + KeyCode::ArrowDown => Some(0x0074), + KeyCode::ArrowLeft => Some(0x0071), + KeyCode::ArrowRight => Some(0x0072), + KeyCode::ArrowUp => Some(0x006F), + KeyCode::NumLock => Some(0x004D), + KeyCode::Numpad0 => Some(0x005A), + KeyCode::Numpad1 => Some(0x0057), + KeyCode::Numpad2 => Some(0x0058), + KeyCode::Numpad3 => Some(0x0059), + KeyCode::Numpad4 => Some(0x0058), + KeyCode::Numpad5 => Some(0x0053), + KeyCode::Numpad6 => Some(0x0054), + KeyCode::Numpad7 => Some(0x0055), + KeyCode::Numpad8 => Some(0x0050), + KeyCode::Numpad9 => Some(0x0051), + KeyCode::NumpadAdd => Some(0x0056), + KeyCode::NumpadComma => Some(0x0081), + KeyCode::NumpadDecimal => Some(0x005B), + KeyCode::NumpadDivide => Some(0x006A), + KeyCode::NumpadEnter => Some(0x0068), + KeyCode::NumpadEqual => Some(0x007D), + KeyCode::NumpadMultiply => Some(0x003F), + KeyCode::NumpadSubtract => Some(0x0052), + KeyCode::Escape => Some(0x0009), + KeyCode::F1 => Some(0x0043), + KeyCode::F2 => Some(0x0044), + KeyCode::F3 => Some(0x0045), + KeyCode::F4 => Some(0x0046), + KeyCode::F5 => Some(0x0047), + KeyCode::F6 => Some(0x0048), + KeyCode::F7 => Some(0x0049), + KeyCode::F8 => Some(0x004A), + KeyCode::F9 => Some(0x004B), + KeyCode::F10 => Some(0x004C), + KeyCode::F11 => Some(0x005F), + KeyCode::F12 => Some(0x0060), + KeyCode::F13 => Some(0x00BF), + KeyCode::F14 => Some(0x00C0), + KeyCode::F15 => Some(0x00C1), + KeyCode::F16 => Some(0x00C2), + KeyCode::F17 => Some(0x00C3), + KeyCode::F18 => Some(0x00C4), + KeyCode::F19 => Some(0x00C5), + KeyCode::F20 => Some(0x00C6), + KeyCode::F21 => Some(0x00C7), + KeyCode::F22 => Some(0x00C8), + KeyCode::F23 => Some(0x00C9), + KeyCode::F24 => Some(0x00CA), + KeyCode::PrintScreen => Some(0x006B), + KeyCode::ScrollLock => Some(0x004E), + KeyCode::Pause => Some(0x007F), + KeyCode::BrowserBack => Some(0x00A6), + KeyCode::BrowserFavorites => Some(0x00A4), + KeyCode::BrowserForward => Some(0x00A7), + KeyCode::BrowserHome => Some(0x00B4), + KeyCode::BrowserRefresh => Some(0x00B5), + KeyCode::BrowserSearch => Some(0x00E1), + KeyCode::BrowserStop => Some(0x0088), + + KeyCode::LaunchApp1 => Some(0x0098), + KeyCode::LaunchApp2 => Some(0x0094), + KeyCode::LaunchMail => Some(0x00A3), + KeyCode::MediaPlayPause => Some(0x00AC), + KeyCode::MediaSelect => Some(0x00B3), + KeyCode::MediaStop => Some(0x00AE), + KeyCode::MediaTrackNext => Some(0x00AB), + KeyCode::MediaTrackPrevious => Some(0x00AD), + KeyCode::AudioVolumeDown => Some(0x007A), + KeyCode::AudioVolumeMute => Some(0x0079), + KeyCode::AudioVolumeUp => Some(0x007B), + KeyCode::Unidentified(NativeKeyCode::Gtk(scancode)) => Some(scancode as u32), + _ => None, + } +} + +pub fn keycode_from_scancode(scancode: u32) -> KeyCode { + // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // and: https://www.w3.org/TR/uievents-code/ + // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source + match scancode { + 0x0009 => KeyCode::Escape, + 0x000A => KeyCode::Digit1, + 0x000B => KeyCode::Digit2, + 0x000C => KeyCode::Digit3, + 0x000D => KeyCode::Digit4, + 0x000E => KeyCode::Digit5, + 0x000F => KeyCode::Digit6, + 0x0010 => KeyCode::Digit7, + 0x0011 => KeyCode::Digit8, + 0x0012 => KeyCode::Digit9, + 0x0013 => KeyCode::Digit0, + 0x0014 => KeyCode::Minus, + 0x0015 => KeyCode::Equal, + 0x0016 => KeyCode::Backspace, + 0x0017 => KeyCode::Tab, + 0x0018 => KeyCode::KeyQ, + 0x0019 => KeyCode::KeyW, + 0x001A => KeyCode::KeyE, + 0x001B => KeyCode::KeyR, + 0x001C => KeyCode::KeyT, + 0x001D => KeyCode::KeyY, + 0x001E => KeyCode::KeyU, + 0x001F => KeyCode::KeyI, + 0x0020 => KeyCode::KeyO, + 0x0021 => KeyCode::KeyP, + 0x0022 => KeyCode::BracketLeft, + 0x0023 => KeyCode::BracketRight, + 0x0024 => KeyCode::Enter, + 0x0025 => KeyCode::ControlLeft, + 0x0026 => KeyCode::KeyA, + 0x0027 => KeyCode::KeyS, + 0x0028 => KeyCode::KeyD, + 0x0029 => KeyCode::KeyF, + 0x002A => KeyCode::KeyG, + 0x002B => KeyCode::KeyH, + 0x002C => KeyCode::KeyJ, + 0x002D => KeyCode::KeyK, + 0x002E => KeyCode::KeyL, + 0x002F => KeyCode::Semicolon, + 0x0030 => KeyCode::Quote, + 0x0031 => KeyCode::Backquote, + 0x0032 => KeyCode::ShiftLeft, + 0x0033 => KeyCode::Backslash, + 0x0034 => KeyCode::KeyZ, + 0x0035 => KeyCode::KeyX, + 0x0036 => KeyCode::KeyC, + 0x0037 => KeyCode::KeyV, + 0x0038 => KeyCode::KeyB, + 0x0039 => KeyCode::KeyN, + 0x003A => KeyCode::KeyM, + 0x003B => KeyCode::Comma, + 0x003C => KeyCode::Period, + 0x003D => KeyCode::Slash, + 0x003E => KeyCode::ShiftRight, + 0x003F => KeyCode::NumpadMultiply, + 0x0040 => KeyCode::AltLeft, + 0x0041 => KeyCode::Space, + 0x0042 => KeyCode::CapsLock, + 0x0043 => KeyCode::F1, + 0x0044 => KeyCode::F2, + 0x0045 => KeyCode::F3, + 0x0046 => KeyCode::F4, + 0x0047 => KeyCode::F5, + 0x0048 => KeyCode::F6, + 0x0049 => KeyCode::F7, + 0x004A => KeyCode::F8, + 0x004B => KeyCode::F9, + 0x004C => KeyCode::F10, + 0x004D => KeyCode::NumLock, + 0x004E => KeyCode::ScrollLock, + 0x004F => KeyCode::Numpad7, + 0x0050 => KeyCode::Numpad8, + 0x0051 => KeyCode::Numpad9, + 0x0052 => KeyCode::NumpadSubtract, + 0x0053 => KeyCode::Numpad4, + 0x0054 => KeyCode::Numpad5, + 0x0055 => KeyCode::Numpad6, + 0x0056 => KeyCode::NumpadAdd, + 0x0057 => KeyCode::Numpad1, + 0x0058 => KeyCode::Numpad2, + 0x0059 => KeyCode::Numpad3, + 0x005A => KeyCode::Numpad0, + 0x005B => KeyCode::NumpadDecimal, + 0x005E => KeyCode::IntlBackslash, + 0x005F => KeyCode::F11, + 0x0060 => KeyCode::F12, + 0x0061 => KeyCode::IntlRo, + 0x0064 => KeyCode::Convert, + 0x0065 => KeyCode::KanaMode, + 0x0066 => KeyCode::NonConvert, + 0x0068 => KeyCode::NumpadEnter, + 0x0069 => KeyCode::ControlRight, + 0x006A => KeyCode::NumpadDivide, + 0x006B => KeyCode::PrintScreen, + 0x006C => KeyCode::AltRight, + 0x006E => KeyCode::Home, + 0x006F => KeyCode::ArrowUp, + 0x0070 => KeyCode::PageUp, + 0x0071 => KeyCode::ArrowLeft, + 0x0072 => KeyCode::ArrowRight, + 0x0073 => KeyCode::End, + 0x0074 => KeyCode::ArrowDown, + 0x0075 => KeyCode::PageDown, + 0x0076 => KeyCode::Insert, + 0x0077 => KeyCode::Delete, + 0x0079 => KeyCode::AudioVolumeMute, + 0x007A => KeyCode::AudioVolumeDown, + 0x007B => KeyCode::AudioVolumeUp, + 0x007D => KeyCode::NumpadEqual, + 0x007F => KeyCode::Pause, + 0x0081 => KeyCode::NumpadComma, + 0x0082 => KeyCode::Lang1, + 0x0083 => KeyCode::Lang2, + 0x0084 => KeyCode::IntlYen, + 0x0085 => KeyCode::SuperLeft, + 0x0086 => KeyCode::SuperRight, + 0x0087 => KeyCode::ContextMenu, + 0x0088 => KeyCode::BrowserStop, + 0x0089 => KeyCode::Again, + 0x008A => KeyCode::Props, + 0x008B => KeyCode::Undo, + 0x008C => KeyCode::Select, + 0x008D => KeyCode::Copy, + 0x008E => KeyCode::Open, + 0x008F => KeyCode::Paste, + 0x0090 => KeyCode::Find, + 0x0091 => KeyCode::Cut, + 0x0092 => KeyCode::Help, + 0x0094 => KeyCode::LaunchApp2, + 0x0097 => KeyCode::WakeUp, + 0x0098 => KeyCode::LaunchApp1, + // key to right of volume controls on T430s produces 0x9C + // but no documentation of what it should map to :/ + 0x00A3 => KeyCode::LaunchMail, + 0x00A4 => KeyCode::BrowserFavorites, + 0x00A6 => KeyCode::BrowserBack, + 0x00A7 => KeyCode::BrowserForward, + 0x00A9 => KeyCode::Eject, + 0x00AB => KeyCode::MediaTrackNext, + 0x00AC => KeyCode::MediaPlayPause, + 0x00AD => KeyCode::MediaTrackPrevious, + 0x00AE => KeyCode::MediaStop, + 0x00B3 => KeyCode::MediaSelect, + 0x00B4 => KeyCode::BrowserHome, + 0x00B5 => KeyCode::BrowserRefresh, + 0x00BF => KeyCode::F13, + 0x00C0 => KeyCode::F14, + 0x00C1 => KeyCode::F15, + 0x00C2 => KeyCode::F16, + 0x00C3 => KeyCode::F17, + 0x00C4 => KeyCode::F18, + 0x00C5 => KeyCode::F19, + 0x00C6 => KeyCode::F20, + 0x00C7 => KeyCode::F21, + 0x00C8 => KeyCode::F22, + 0x00C9 => KeyCode::F23, + 0x00CA => KeyCode::F24, + 0x00E1 => KeyCode::BrowserSearch, + _ => KeyCode::Unidentified(NativeKeyCode::Gtk(scancode as u16)), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs new file mode 100644 index 000000000..aa317542e --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs @@ -0,0 +1,93 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "x11")] +mod device; +mod event_loop; +mod icon; +mod keyboard; +mod keycode; +mod monitor; +#[cfg(feature = "dbus")] +mod portal; +mod util; +mod window; + +pub mod taskbar; +#[cfg(feature = "x11")] +pub mod x11; + +pub use self::keycode::{keycode_from_scancode, keycode_to_scancode}; +pub(crate) use event_loop::PlatformSpecificEventLoopAttributes; +pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +pub use icon::PlatformIcon; +pub use monitor::{MonitorHandle, VideoMode}; +pub use window::{Window, WindowId}; + +use crate::{event::DeviceId as RootDeviceId, keyboard::Key}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifiers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + +#[non_exhaustive] +#[derive(Clone, Default)] +pub enum Parent { + #[default] + None, + ChildOf(gtk::Window), +} + +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub parent: Parent, + pub skip_taskbar: bool, + pub auto_transparent: bool, + pub double_buffered: bool, + pub app_paintable: bool, + pub rgba_visual: bool, + pub cursor_moved: bool, + pub default_vbox: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + parent: Default::default(), + skip_taskbar: Default::default(), + auto_transparent: true, + double_buffered: true, + app_paintable: false, + rgba_visual: false, + cursor_moved: true, + default_vbox: true, + } + } +} + +unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} +unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} + +#[derive(Debug, Clone)] +pub struct OsError; + +impl std::fmt::Display for OsError { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + Ok(()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(usize); + +impl DeviceId { + pub unsafe fn dummy() -> Self { + Self(0) + } +} + +// FIXME: currently we use a dummy device id, find if we can get device id from gtk +pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs new file mode 100644 index 000000000..ceeea1265 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs @@ -0,0 +1,96 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use gtk::gdk::{self, prelude::MonitorExt, Display}; + +use crate::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MonitorHandle { + pub(crate) monitor: gdk::Monitor, +} + +impl MonitorHandle { + pub fn new(display: &gdk::Display, number: i32) -> Self { + let monitor = display.monitor(number).unwrap(); + Self { monitor } + } + + #[inline] + pub fn name(&self) -> Option { + self.monitor.model().map(|s| s.as_str().to_string()) + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + let rect = self.monitor.geometry(); + LogicalSize { + width: rect.width() as u32, + height: rect.height() as u32, + } + .to_physical(self.scale_factor()) + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + let rect = self.monitor.geometry(); + LogicalPosition { + x: rect.x(), + y: rect.y(), + } + .to_physical(self.scale_factor()) + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + self.monitor.scale_factor() as f64 + } + + #[inline] + pub fn video_modes(&self) -> Box> { + Box::new(Vec::new().into_iter()) + } +} + +unsafe impl Send for MonitorHandle {} +unsafe impl Sync for MonitorHandle {} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VideoMode; + +impl VideoMode { + #[inline] + pub fn size(&self) -> PhysicalSize { + panic!("VideoMode is unsupported on Linux.") + } + + #[inline] + pub fn bit_depth(&self) -> u16 { + panic!("VideoMode is unsupported on Linux.") + } + + #[inline] + pub fn refresh_rate(&self) -> u16 { + panic!("VideoMode is unsupported on Linux.") + } + + #[inline] + pub fn monitor(&self) -> RootMonitorHandle { + panic!("VideoMode is unsupported on Linux.") + } +} + +pub fn from_point(display: &Display, x: f64, y: f64) -> Option { + if let Some(monitor) = display.monitor_at_point(x as i32, y as i32) { + (0..display.n_monitors()) + .map(|i| (i, display.monitor(i).unwrap())) + .find(|cur| cur.1.geometry() == monitor.geometry()) + .map(|x| MonitorHandle::new(display, x.0)) + } else { + None + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs new file mode 100644 index 000000000..c0df6a43d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs @@ -0,0 +1,78 @@ +#[cfg(feature = "dbus")] +use dbus::{ + arg::Variant, + blocking::{Connection, SyncConnection}, + message::MatchRule, + Error, +}; +use gtk::glib::{ControlFlow, MainContext, Priority, Sender}; +use log::warn; +use std::{thread, time::Duration}; + +use crate::{ + platform_impl::{platform::window::WindowRequest, WindowId}, + window::Theme, +}; + +pub fn theme() -> Result { + let conn = Connection::new_session()?; + let proxy = conn.with_proxy( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + Duration::from_secs(5), + ); + + let result: (Variant>,) = proxy.method_call( + "org.freedesktop.portal.Settings", + "Read", + ("org.freedesktop.appearance", "color-scheme"), + )?; + + Ok(color_scheme_to_theme(result.0 .0 .0)) +} + +pub fn receive_theme_changed(window_tx: Sender<(WindowId, WindowRequest)>) -> Result<(), Error> { + let conn = SyncConnection::new_session()?; + let match_rule = MatchRule::new_signal("org.freedesktop.portal.Settings", "SettingChanged"); + let (tx, rx) = MainContext::channel(Priority::DEFAULT); + + conn.add_match(match_rule, move |_: (), _, msg| { + let mut iter = msg.iter_init(); + if let (Ok("org.freedesktop.appearance"), Ok("color-scheme"), Ok(value)) = ( + iter.read::<&str>(), // Namespace + iter.read::<&str>(), // Key + iter.read::>(), // Value + ) { + if let Err(e) = tx.send(color_scheme_to_theme(value.0)) { + warn!("Failed to send theme change via channel: {}", e); + } + } + true + })?; + + rx.attach(None, move |theme| { + if let Err(e) = window_tx.send((WindowId::dummy(), WindowRequest::SetTheme(Some(theme)))) { + warn!("Failed to send theme change request: {}", e); + ControlFlow::Break + } else { + ControlFlow::Continue + } + }); + + thread::spawn(move || loop { + if let Err(e) = conn.process(Duration::from_secs(5)) { + warn!("D-Bus message processing error: {}", e); + break; + } + }); + + Ok(()) +} + +fn color_scheme_to_theme(color_scheme: u32) -> Theme { + match color_scheme { + 1 => Theme::Dark, // Prefer Dark + 2 => Theme::Light, // Prefer Light + _ => Theme::Light, // No Preference, default to Light + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs new file mode 100644 index 000000000..ae7b98584 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs @@ -0,0 +1,161 @@ +use std::ffi::{c_char, CString}; + +use dlopen2::wrapper::{Container, WrapperApi}; + +use crate::window::{ProgressBarState, ProgressState}; + +#[derive(WrapperApi)] +struct UnityLib { + unity_launcher_entry_get_for_desktop_id: unsafe extern "C" fn(id: *const c_char) -> *const isize, + unity_inspector_get_default: unsafe extern "C" fn() -> *const isize, + unity_inspector_get_unity_running: unsafe extern "C" fn(inspector: *const isize) -> i32, + unity_launcher_entry_set_progress: unsafe extern "C" fn(entry: *const isize, value: f64) -> i32, + unity_launcher_entry_set_progress_visible: + unsafe extern "C" fn(entry: *const isize, value: i32) -> i32, + unity_launcher_entry_set_count: unsafe extern "C" fn(entry: *const isize, value: i64) -> i32, + unity_launcher_entry_set_count_visible: + unsafe extern "C" fn(entry: *const isize, value: bool) -> bool, +} + +pub struct TaskbarIndicator { + desktop_filename: Option, + desktop_filename_c_str: Option, + + unity_lib: Option>, + attempted_load: bool, + + unity_inspector: Option<*const isize>, + unity_entry: Option<*const isize>, +} + +impl TaskbarIndicator { + pub fn new() -> Self { + Self { + desktop_filename: None, + desktop_filename_c_str: None, + + unity_lib: None, + attempted_load: false, + + unity_inspector: None, + unity_entry: None, + } + } + + fn ensure_lib_load(&mut self) { + if self.attempted_load { + return; + } + + self.attempted_load = true; + + self.unity_lib = unsafe { + Container::load("libunity.so.4") + .or_else(|_| Container::load("libunity.so.6")) + .or_else(|_| Container::load("libunity.so.9")) + .ok() + }; + + if let Some(unity_lib) = &self.unity_lib { + let handle = unsafe { unity_lib.unity_inspector_get_default() }; + if !handle.is_null() { + self.unity_inspector = Some(handle); + } + } + } + + fn ensure_entry_load(&mut self) { + if let Some(unity_lib) = &self.unity_lib { + if let Some(id) = &self.desktop_filename_c_str { + let handle = unsafe { unity_lib.unity_launcher_entry_get_for_desktop_id(id.as_ptr()) }; + if !handle.is_null() { + self.unity_entry = Some(handle); + } + } + } + } + + fn is_unity_running(&self) -> bool { + if let Some(inspector) = self.unity_inspector { + if let Some(unity_lib) = &self.unity_lib { + return unsafe { unity_lib.unity_inspector_get_unity_running(inspector) } == 1; + } + } + + false + } + + pub fn update(&mut self, progress: ProgressBarState) { + if let Some(uri) = progress.desktop_filename { + self.desktop_filename = Some(uri); + } + + self.ensure_lib_load(); + + if !self.is_unity_running() { + return; + } + + if let Some(uri) = &self.desktop_filename { + self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); + } + + if self.unity_entry.is_none() { + self.ensure_entry_load(); + } + if let Some(unity_lib) = &self.unity_lib { + if let Some(unity_entry) = &self.unity_entry { + if let Some(progress) = progress.progress { + let progress = if progress > 100 { 100 } else { progress }; + let progress = progress as f64 / 100.0; + unsafe { (unity_lib.unity_launcher_entry_set_progress)(*unity_entry, progress) }; + } + + if let Some(state) = progress.state { + let is_visible = !matches!(state, ProgressState::None); + unsafe { + (unity_lib.unity_launcher_entry_set_progress_visible)( + *unity_entry, + if is_visible { 1 } else { 0 }, + ) + }; + } + } + } + } + + pub fn update_count(&mut self, count: Option, desktop_filename: Option) { + if let Some(uri) = desktop_filename { + self.desktop_filename = Some(uri); + } + + self.ensure_lib_load(); + + if !self.is_unity_running() { + return; + } + + if let Some(uri) = &self.desktop_filename { + self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); + } + + if self.unity_entry.is_none() { + self.ensure_entry_load(); + } + + if let Some(unity_lib) = &self.unity_lib { + if let Some(unity_entry) = &self.unity_entry { + // Sets count + if let Some(count) = count { + unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, count) }; + unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, true) }; + } + // removes the count + else { + unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, 0) }; + unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, false) }; + } + } + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs new file mode 100644 index 000000000..30cd8523b --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs @@ -0,0 +1,111 @@ +use crate::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, + error::ExternalError, + window::WindowSizeConstraints, +}; +use gtk::{ + gdk::{ + self, + prelude::{DeviceExt, SeatExt}, + Display, + }, + glib::{self}, + traits::{GtkWindowExt, WidgetExt}, +}; +use std::{cell::RefCell, rc::Rc}; + +#[inline] +pub fn cursor_position(is_wayland: bool) -> Result, ExternalError> { + if is_wayland { + Ok((0, 0).into()) + } else { + Display::default() + .map(|d| { + ( + d.default_seat().and_then(|s| s.pointer()), + d.default_group(), + ) + }) + .map(|(p, g)| { + p.map(|p| { + let (_, x, y) = p.position_double(); + LogicalPosition::new(x, y).to_physical(g.scale_factor() as _) + }) + }) + .map(|p| p.ok_or(ExternalError::Os(os_error!(super::OsError)))) + .ok_or(ExternalError::Os(os_error!(super::OsError)))? + } +} + +pub fn set_size_constraints( + window: &W, + constraints: WindowSizeConstraints, +) { + let mut geom_mask = gdk::WindowHints::empty(); + if constraints.has_min() { + geom_mask |= gdk::WindowHints::MIN_SIZE; + } + if constraints.has_max() { + geom_mask |= gdk::WindowHints::MAX_SIZE; + } + + let scale_factor = window.scale_factor() as f64; + + let min_size: LogicalSize = constraints.min_size_logical(scale_factor); + let max_size: LogicalSize = constraints.max_size_logical(scale_factor); + + let picky_none: Option<>k::Window> = None; + window.set_geometry_hints( + picky_none, + Some(&gdk::Geometry::new( + min_size.width, + min_size.height, + max_size.width, + max_size.height, + 0, + 0, + 0, + 0, + 0f64, + 0f64, + gdk::Gravity::Center, + )), + geom_mask, + ) +} + +pub struct WindowMaximizeProcess { + window: W, + resizable: bool, + step: u8, +} + +impl WindowMaximizeProcess { + pub fn new(window: W, resizable: bool) -> Rc> { + Rc::new(RefCell::new(Self { + window, + resizable, + step: 0, + })) + } + + pub fn next_step(&mut self) -> glib::ControlFlow { + match self.step { + 0 => { + self.window.set_resizable(true); + self.step += 1; + glib::ControlFlow::Continue + } + 1 => { + self.window.maximize(); + self.step += 1; + glib::ControlFlow::Continue + } + 2 => { + self.window.set_resizable(self.resizable); + glib::ControlFlow::Break + } + _ => glib::ControlFlow::Break, + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs new file mode 100644 index 000000000..0308f1bf2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs @@ -0,0 +1,1097 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + cell::RefCell, + collections::VecDeque, + rc::Rc, + sync::{ + atomic::{AtomicBool, AtomicI32, Ordering}, + RwLock, + }, +}; + +use gtk::{ + gdk::WindowState, + glib::{self, translate::ToGlibPtr}, + prelude::*, + CssProvider, EventBox, Settings, +}; + +use crate::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + icon::Icon, + monitor::MonitorHandle as RootMonitorHandle, + window::{ + CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, + WindowAttributes, WindowSizeConstraints, RGBA, + }, +}; + +use super::{ + event_loop::EventLoopWindowTarget, + monitor::{self, MonitorHandle}, + util, Parent, PlatformSpecificWindowBuilderAttributes, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId(pub(crate) u32); + +impl WindowId { + pub fn dummy() -> Self { + WindowId(u32::MAX) + } +} + +pub struct Window { + /// Window id. + pub(crate) window_id: WindowId, + /// Gtk application window. + pub(crate) window: gtk::ApplicationWindow, + pub(crate) default_vbox: Option, + /// Window requests sender + pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, + scale_factor: Rc, + inner_position: Rc<(AtomicI32, AtomicI32)>, + outer_position: Rc<(AtomicI32, AtomicI32)>, + outer_size: Rc<(AtomicI32, AtomicI32)>, + inner_size: Rc<(AtomicI32, AtomicI32)>, + maximized: Rc, + is_always_on_top: Rc, + minimized: Rc, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + fullscreen: RwLock>, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + inner_size_constraints: RwLock, + /// Draw event Sender + draw_tx: crossbeam_channel::Sender, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + preferred_theme: RwLock>, + css_provider: CssProvider, +} + +impl Window { + pub(crate) fn new( + event_loop_window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let app = &event_loop_window_target.app; + let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); + let draw_tx = event_loop_window_target.draw_tx.clone(); + + let mut window_builder = gtk::ApplicationWindow::builder() + .application(app) + .accept_focus(attributes.focusable && attributes.focused); + if let Parent::ChildOf(parent) = pl_attribs.parent { + window_builder = window_builder.transient_for(&parent); + } + + let window = window_builder.build(); + + let window_id = WindowId(window.id()); + event_loop_window_target + .windows + .borrow_mut() + .insert(window_id); + + // Set Width/Height & Resizable + let win_scale_factor = window.scale_factor(); + let (width, height) = attributes + .inner_size + .map(|size| size.to_logical::(win_scale_factor as f64).into()) + .unwrap_or((800, 600)); + + window.set_default_size(width, height); + + // Trying to prevent Wayland Protocol Error about mismatched xdg_surface_buffer sizes + let maximized = attributes.maximized; + let resizable = attributes.resizable; + let configure_handler_id = Rc::new(RefCell::new(None)); + let configure_handler_id_ = configure_handler_id.clone(); + let id = window.connect_configure_event(move |window, _| { + if let Some(id) = configure_handler_id_.take() { + window.disconnect(id); + if maximized { + window.maximize(); + } + window.set_resizable(resizable); + } + false + }); + configure_handler_id.borrow_mut().replace(id); + + window.set_deletable(attributes.closable); + + // Set Min/Max Size + util::set_size_constraints(&window, attributes.inner_size_constraints); + + // Set Position + if let Some(position) = attributes.position { + let (x, y): (i32, i32) = position.to_logical::(win_scale_factor as f64).into(); + window.move_(x, y); + } + + // Set GDK Visual + if pl_attribs.rgba_visual || attributes.transparent { + if let Some(screen) = GtkWindowExt::screen(&window) { + if let Some(visual) = screen.rgba_visual() { + window.set_visual(Some(&visual)); + } + } + } + + if pl_attribs.app_paintable || attributes.transparent { + // Set a few attributes to make the window can be painted. + // See Gtk drawing model for more info: + // https://docs.gtk.org/gtk3/drawing-model.html + window.set_app_paintable(true); + } + + if !pl_attribs.double_buffered { + let widget = window.upcast_ref::(); + if !event_loop_window_target.is_wayland() { + unsafe { + gtk::ffi::gtk_widget_set_double_buffered(widget.to_glib_none().0, 0); + } + } + } + + let default_vbox = if pl_attribs.default_vbox { + let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); + window.add(&box_); + Some(box_) + } else { + None + }; + + // Rest attributes + window.set_title(&attributes.title); + if let Some(Fullscreen::Borderless(m)) = &attributes.fullscreen { + if let Some(monitor) = m { + let display = window.display(); + let monitor = &monitor.inner; + let monitors = display.n_monitors(); + for i in 0..monitors { + let m = display.monitor(i).unwrap(); + if m == monitor.monitor { + let screen = display.default_screen(); + window.fullscreen_on_monitor(&screen, i); + } + } + } else { + window.fullscreen(); + } + } + window.set_visible(attributes.visible); + window.set_decorated(attributes.decorations); + if window.display().backend().is_wayland() && !attributes.decorations { + // Enable CSD to prevent compositor from rendering server-side decorations + let event_box = EventBox::new(); + window.set_titlebar(Some(&event_box)); + } + + if attributes.always_on_bottom { + window.set_keep_below(attributes.always_on_bottom); + } + + if attributes.always_on_top { + window.set_keep_above(attributes.always_on_top); + } + + if attributes.visible_on_all_workspaces { + window.stick(); + } + + // Set initial `preferred_theme` value to current portal color-scheme + #[cfg(feature = "dbus")] + let preferred_theme = super::portal::theme().ok(); + #[cfg(not(feature = "dbus"))] + let preferred_theme = None; + + if let Some(theme) = preferred_theme { + if let Some(settings) = Settings::default() { + settings.set_gtk_application_prefer_dark_theme(theme == Theme::Dark); + } + } + + if attributes.visible { + window.show_all(); + } else { + window.hide(); + } + + // restore accept-focus after the window has been drawn + // if the window was initially created without focus and is supposed to be focusable + if attributes.focusable && !attributes.focused { + let signal_id = Rc::new(RefCell::new(None)); + let signal_id_ = signal_id.clone(); + let id = window.connect_draw(move |window, _| { + if let Some(id) = signal_id_.take() { + window.set_accept_focus(true); + window.disconnect(id); + } + glib::Propagation::Proceed + }); + signal_id.borrow_mut().replace(id); + } + + // Check if we should paint the transparent background ourselves. + let mut transparent = false; + if attributes.transparent && pl_attribs.auto_transparent { + transparent = true; + } + let cursor_moved = pl_attribs.cursor_moved; + if let Err(e) = window_requests_tx.send(( + window_id, + WindowRequest::WireUpEvents { + transparent, + fullscreen: attributes.fullscreen.is_some(), + cursor_moved, + }, + )) { + log::warn!("Fail to send wire up events request: {}", e); + } + + let ( + scale_factor, + outer_position, + inner_position, + outer_size, + inner_size, + maximized, + minimized, + is_always_on_top, + ) = Self::setup_signals(&window, Some(&attributes)); + + if let Some(icon) = attributes.window_icon { + window.set_icon(Some(&icon.inner.into())); + } + + let win = Self { + window_id, + window, + default_vbox, + window_requests_tx, + draw_tx, + scale_factor, + outer_position, + inner_position, + outer_size, + inner_size, + maximized, + minimized, + is_always_on_top, + fullscreen: RwLock::new(attributes.fullscreen), + inner_size_constraints: RwLock::new(attributes.inner_size_constraints), + preferred_theme: RwLock::new(preferred_theme), + css_provider: CssProvider::new(), + }; + + let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); + win.set_background_color(attributes.background_color); + + Ok(win) + } + + fn setup_signals( + window: >k::ApplicationWindow, + attributes: Option<&WindowAttributes>, + ) -> ( + Rc, + Rc<(AtomicI32, AtomicI32)>, + Rc<(AtomicI32, AtomicI32)>, + Rc<(AtomicI32, AtomicI32)>, + Rc<(AtomicI32, AtomicI32)>, + Rc, + Rc, + Rc, + ) { + let win_scale_factor = window.scale_factor(); + + let w_pos = window.position(); + let inner_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_pos.0.into(), w_pos.1.into())); + let inner_position_clone = inner_position.clone(); + + let o_pos = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); + let outer_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_pos.0.into(), o_pos.1.into())); + let outer_position_clone = outer_position.clone(); + + let w_size = window.size(); + let inner_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_size.0.into(), w_size.1.into())); + let inner_size_clone = inner_size.clone(); + + let o_size = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); + let outer_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_size.0.into(), o_size.1.into())); + let outer_size_clone = outer_size.clone(); + + window.connect_configure_event(move |window, event| { + let (x, y) = event.position(); + inner_position_clone.0.store(x, Ordering::Release); + inner_position_clone.1.store(y, Ordering::Release); + + let (w, h) = event.size(); + inner_size_clone.0.store(w as i32, Ordering::Release); + inner_size_clone.1.store(h as i32, Ordering::Release); + + let (x, y, w, h) = window + .window() + .map(|w| { + let rect = w.frame_extents(); + (rect.x(), rect.y(), rect.width(), rect.height()) + }) + .unwrap_or((x, y, w as i32, h as i32)); + + outer_position_clone.0.store(x, Ordering::Release); + outer_position_clone.1.store(y, Ordering::Release); + + outer_size_clone.0.store(w, Ordering::Release); + outer_size_clone.1.store(h, Ordering::Release); + + false + }); + + let w_max = window.is_maximized(); + let maximized: Rc = Rc::new(w_max.into()); + let max_clone = maximized.clone(); + let minimized = Rc::new(AtomicBool::new(false)); + let minimized_clone = minimized.clone(); + let is_always_on_top = Rc::new(AtomicBool::new( + attributes.map(|a| a.always_on_top).unwrap_or(false), + )); + let is_always_on_top_clone = is_always_on_top.clone(); + + window.connect_window_state_event(move |_, event| { + let state = event.new_window_state(); + max_clone.store(state.contains(WindowState::MAXIMIZED), Ordering::Release); + minimized_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release); + is_always_on_top_clone.store(state.contains(WindowState::ABOVE), Ordering::Release); + glib::Propagation::Proceed + }); + + let scale_factor: Rc = Rc::new(win_scale_factor.into()); + let scale_factor_clone = scale_factor.clone(); + window.connect_scale_factor_notify(move |window| { + scale_factor_clone.store(window.scale_factor(), Ordering::Release); + }); + + ( + scale_factor, + outer_position, + inner_position, + outer_size, + inner_size, + maximized, + minimized, + is_always_on_top, + ) + } + + pub(crate) fn new_from_gtk_window( + event_loop_window_target: &EventLoopWindowTarget, + window: gtk::ApplicationWindow, + ) -> Result { + let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); + let draw_tx = event_loop_window_target.draw_tx.clone(); + + let window_id = WindowId(window.id()); + event_loop_window_target + .windows + .borrow_mut() + .insert(window_id); + + let ( + scale_factor, + outer_position, + inner_position, + outer_size, + inner_size, + maximized, + minimized, + is_always_on_top, + ) = Self::setup_signals(&window, None); + + let win = Self { + window_id, + window, + default_vbox: None, + window_requests_tx, + draw_tx, + scale_factor, + outer_position, + inner_position, + outer_size, + inner_size, + maximized, + minimized, + is_always_on_top, + fullscreen: RwLock::new(None), + inner_size_constraints: RwLock::new(WindowSizeConstraints::default()), + preferred_theme: RwLock::new(None), + css_provider: CssProvider::new(), + }; + + Ok(win) + } + + pub fn id(&self) -> WindowId { + self.window_id + } + + pub fn scale_factor(&self) -> f64 { + self.scale_factor.load(Ordering::Acquire) as f64 + } + + pub fn request_redraw(&self) { + if let Err(e) = self.draw_tx.send(self.window_id) { + log::warn!("Failed to send redraw event to event channel: {}", e); + } + } + + pub fn inner_position(&self) -> Result, NotSupportedError> { + let (x, y) = &*self.inner_position; + Ok( + LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) + .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), + ) + } + + pub fn outer_position(&self) -> Result, NotSupportedError> { + let (x, y) = &*self.outer_position; + Ok( + LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) + .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), + ) + } + + pub fn set_outer_position>(&self, position: P) { + let (x, y): (i32, i32) = position + .into() + .to_logical::(self.scale_factor()) + .into(); + + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Position((x, y)))) + { + log::warn!("Fail to send position request: {}", e); + } + } + + pub fn set_background_color(&self, color: Option) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::BackgroundColor(self.css_provider.clone(), color), + )) { + log::warn!("Fail to send size request: {}", e); + } + } + + pub fn inner_size(&self) -> PhysicalSize { + let (width, height) = &*self.inner_size; + + LogicalSize::new( + width.load(Ordering::Acquire) as u32, + height.load(Ordering::Acquire) as u32, + ) + .to_physical(self.scale_factor.load(Ordering::Acquire) as f64) + } + + pub fn set_inner_size>(&self, size: S) { + let (width, height) = size.into().to_logical::(self.scale_factor()).into(); + + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Size((width, height)))) + { + log::warn!("Fail to send size request: {}", e); + } + } + + pub fn outer_size(&self) -> PhysicalSize { + let (width, height) = &*self.outer_size; + + LogicalSize::new( + width.load(Ordering::Acquire) as u32, + height.load(Ordering::Acquire) as u32, + ) + .to_physical(self.scale_factor.load(Ordering::Acquire) as f64) + } + + fn set_size_constraints(&self, constraints: WindowSizeConstraints) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::SizeConstraints(constraints))) + { + log::warn!("Fail to send size constraint request: {}", e); + } + } + + pub fn set_min_inner_size(&self, size: Option) { + let (width, height) = size.map(crate::extract_width_height).unzip(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); + size_constraints.min_width = width; + size_constraints.min_height = height; + self.set_size_constraints(*size_constraints) + } + + pub fn set_max_inner_size(&self, size: Option) { + let (width, height) = size.map(crate::extract_width_height).unzip(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); + size_constraints.max_width = width; + size_constraints.max_height = height; + self.set_size_constraints(*size_constraints) + } + + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + *self.inner_size_constraints.write().unwrap() = constraints; + self.set_size_constraints(constraints) + } + + pub fn set_title(&self, title: &str) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Title(title.to_string()))) + { + log::warn!("Fail to send title request: {}", e); + } + } + + pub fn title(&self) -> String { + self + .window + .title() + .map(|t| t.as_str().to_string()) + .unwrap_or_default() + } + + pub fn set_visible(&self, visible: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Visible(visible))) + { + log::warn!("Fail to send visible request: {}", e); + } + } + + pub fn set_focus(&self) { + if !self.minimized.load(Ordering::Acquire) && self.window.get_visible() { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Focus)) + { + log::warn!("Fail to send visible request: {}", e); + } + } + } + + pub fn set_focusable(&self, focusable: bool) { + self.window.set_accept_focus(focusable); + } + + pub fn is_focused(&self) -> bool { + self.window.is_active() + } + + pub fn set_resizable(&self, resizable: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Resizable(resizable))) + { + log::warn!("Fail to send resizable request: {}", e); + } + } + + pub fn set_minimizable(&self, _minimizable: bool) {} + + pub fn set_maximizable(&self, _maximizable: bool) {} + + pub fn set_closable(&self, closable: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Closable(closable))) + { + log::warn!("Fail to send closable request: {}", e); + } + } + + pub fn set_minimized(&self, minimized: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Minimized(minimized))) + { + log::warn!("Fail to send minimized request: {}", e); + } + } + + pub fn set_maximized(&self, maximized: bool) { + let resizable = self.is_resizable(); + + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::Maximized(maximized, resizable), + )) { + log::warn!("Fail to send maximized request: {}", e); + } + } + + pub fn is_always_on_top(&self) -> bool { + self.is_always_on_top.load(Ordering::Acquire) + } + + pub fn is_maximized(&self) -> bool { + self.maximized.load(Ordering::Acquire) + } + + pub fn is_minimized(&self) -> bool { + self.minimized.load(Ordering::Acquire) + } + + pub fn is_resizable(&self) -> bool { + self.window.is_resizable() + } + + pub fn is_minimizable(&self) -> bool { + true + } + + pub fn is_maximizable(&self) -> bool { + true + } + pub fn is_closable(&self) -> bool { + self.window.is_deletable() + } + + pub fn is_decorated(&self) -> bool { + self.window.is_decorated() + } + + #[inline] + pub fn is_visible(&self) -> bool { + self.window.is_visible() + } + + pub fn drag_window(&self) -> Result<(), ExternalError> { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::DragWindow)) + { + log::warn!("Fail to send drag window request: {}", e); + } + Ok(()) + } + + pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::DragResizeWindow(direction))) + { + log::warn!("Fail to send drag window request: {}", e); + } + Ok(()) + } + + pub fn set_fullscreen(&self, fullscreen: Option) { + *self.fullscreen.write().unwrap() = fullscreen.clone(); + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Fullscreen(fullscreen))) + { + log::warn!("Fail to send fullscreen request: {}", e); + } + } + + pub fn fullscreen(&self) -> Option { + self.fullscreen.read().unwrap().clone() + } + + pub fn set_decorations(&self, decorations: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::Decorations(decorations))) + { + log::warn!("Fail to send decorations request: {}", e); + } + } + + pub fn set_always_on_bottom(&self, always_on_bottom: bool) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::AlwaysOnBottom(always_on_bottom), + )) { + log::warn!("Fail to send always on bottom request: {}", e); + } + } + + pub fn set_always_on_top(&self, always_on_top: bool) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::AlwaysOnTop(always_on_top))) + { + log::warn!("Fail to send always on top request: {}", e); + } + } + + pub fn set_window_icon(&self, window_icon: Option) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::WindowIcon(window_icon))) + { + log::warn!("Fail to send window icon request: {}", e); + } + } + + pub fn set_ime_position>(&self, _position: P) { + //TODO + } + + pub fn request_user_attention(&self, request_type: Option) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::UserAttention(request_type))) + { + log::warn!("Fail to send user attention request: {}", e); + } + } + + pub fn set_visible_on_all_workspaces(&self, visible: bool) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::SetVisibleOnAllWorkspaces(visible), + )) { + log::warn!("Fail to send visible on all workspaces request: {}", e); + } + } + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::CursorIcon(Some(cursor)))) + { + log::warn!("Fail to send cursor icon request: {}", e); + } + } + + pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { + let inner_pos = self.inner_position().unwrap_or_default(); + let (x, y): (i32, i32) = position + .into() + .to_logical::(self.scale_factor()) + .into(); + + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::CursorPosition((x + inner_pos.x, y + inner_pos.y)), + )) { + log::warn!("Fail to send cursor position request: {}", e); + } + + Ok(()) + } + + pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { + Ok(()) + } + + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::CursorIgnoreEvents(ignore))) + { + log::warn!("Fail to send cursor position request: {}", e); + } + + Ok(()) + } + + pub fn set_cursor_visible(&self, visible: bool) { + let cursor = if visible { + Some(CursorIcon::Default) + } else { + None + }; + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::CursorIcon(cursor))) + { + log::warn!("Fail to send cursor visibility request: {}", e); + } + } + + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position(self.is_wayland()) + } + + pub fn current_monitor(&self) -> Option { + let display = self.window.display(); + // `.window()` returns `None` if the window is invisible; + // we fallback to the primary monitor + let monitor = self + .window + .window() + .and_then(|window| display.monitor_at_window(&window)) + .or_else(|| display.primary_monitor()); + + monitor.map(|monitor| RootMonitorHandle { + inner: MonitorHandle { monitor }, + }) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + let mut handles = VecDeque::new(); + let display = self.window.display(); + let numbers = display.n_monitors(); + + for i in 0..numbers { + let monitor = MonitorHandle::new(&display, i); + handles.push_back(monitor); + } + + handles + } + + pub fn primary_monitor(&self) -> Option { + let display = self.window.display(); + display.primary_monitor().map(|monitor| { + let handle = MonitorHandle { monitor }; + RootMonitorHandle { inner: handle } + }) + } + + #[inline] + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + let display = &self.window.display(); + monitor::from_point(display, x, y).map(|inner| RootMonitorHandle { inner }) + } + + fn is_wayland(&self) -> bool { + self.window.display().backend().is_wayland() + } + + #[cfg(feature = "rwh_04")] + #[inline] + pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { + if self.is_wayland() { + let mut window_handle = rwh_04::WaylandHandle::empty(); + if let Some(window) = self.window.window() { + window_handle.surface = + unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; + } + + rwh_04::RawWindowHandle::Wayland(window_handle) + } else { + let mut window_handle = rwh_04::XlibHandle::empty(); + unsafe { + if let Some(window) = self.window.window() { + window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); + } + } + rwh_04::RawWindowHandle::Xlib(window_handle) + } + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { + if self.is_wayland() { + let mut window_handle = rwh_05::WaylandWindowHandle::empty(); + if let Some(window) = self.window.window() { + window_handle.surface = + unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; + } + + rwh_05::RawWindowHandle::Wayland(window_handle) + } else { + let mut window_handle = rwh_05::XlibWindowHandle::empty(); + unsafe { + if let Some(window) = self.window.window() { + window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); + } + } + rwh_05::RawWindowHandle::Xlib(window_handle) + } + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + if self.is_wayland() { + let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); + display_handle.display = unsafe { + gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) + }; + rwh_05::RawDisplayHandle::Wayland(display_handle) + } else { + let mut display_handle = rwh_05::XlibDisplayHandle::empty(); + unsafe { + if let Ok(xlib) = x11_dl::xlib::Xlib::open() { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + display_handle.display = display as _; + display_handle.screen = (xlib.XDefaultScreen)(display) as _; + } + } + + rwh_05::RawDisplayHandle::Xlib(display_handle) + } + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_window_handle_rwh_06(&self) -> Result { + if let Some(window) = self.window.window() { + if self.is_wayland() { + let surface = + unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; + let surface = unsafe { std::ptr::NonNull::new_unchecked(surface) }; + let window_handle = rwh_06::WaylandWindowHandle::new(surface); + Ok(rwh_06::RawWindowHandle::Wayland(window_handle)) + } else { + #[cfg(feature = "x11")] + { + let xid = unsafe { gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _) }; + let window_handle = rwh_06::XlibWindowHandle::new(xid); + Ok(rwh_06::RawWindowHandle::Xlib(window_handle)) + } + #[cfg(not(feature = "x11"))] + Err(rwh_06::HandleError::Unavailable) + } + } else { + Err(rwh_06::HandleError::Unavailable) + } + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + if self.is_wayland() { + let display = unsafe { + gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) + }; + let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; + let display_handle = rwh_06::WaylandDisplayHandle::new(display); + Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) + } else { + #[cfg(feature = "x11")] + if let Ok(xlib) = x11_dl::xlib::Xlib::open() { + unsafe { + let display = (xlib.XOpenDisplay)(std::ptr::null()); + let screen = (xlib.XDefaultScreen)(display) as _; + let display = std::ptr::NonNull::new_unchecked(display as _); + let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); + Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) + } + } else { + Err(rwh_06::HandleError::Unavailable) + } + #[cfg(not(feature = "x11"))] + Err(rwh_06::HandleError::Unavailable) + } + } + + pub fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::SetSkipTaskbar(skip))) + { + log::warn!("Fail to send skip taskbar request: {}", e); + } + + Ok(()) + } + + pub fn set_progress_bar(&self, progress: ProgressBarState) { + if let Err(e) = self + .window_requests_tx + .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) + { + log::warn!("Fail to send update progress bar request: {}", e); + } + } + + pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { + if let Err(e) = self.window_requests_tx.send(( + WindowId::dummy(), + WindowRequest::BadgeCount(count, desktop_filename), + )) { + log::warn!("Fail to send update badge count request: {}", e); + } + } + + pub fn theme(&self) -> Theme { + if let Some(theme) = *self.preferred_theme.read().unwrap() { + return theme; + } + + #[cfg(feature = "dbus")] + if let Ok(portal_theme) = super::portal::theme() { + return portal_theme; + } + + Theme::Light + } + + pub fn set_theme(&self, theme: Option) { + *self.preferred_theme.write().unwrap() = theme; + if let Err(e) = self + .window_requests_tx + .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) + { + log::warn!("Fail to send set theme request: {e}"); + } + } +} + +// We need GtkWindow to initialize WebView, so we have to keep it in the field. +// It is called on any method. +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +#[non_exhaustive] +pub enum WindowRequest { + Title(String), + Position((i32, i32)), + Size((i32, i32)), + SizeConstraints(WindowSizeConstraints), + Visible(bool), + Focus, + Resizable(bool), + Closable(bool), + Minimized(bool), + Maximized(bool, bool), + DragWindow, + DragResizeWindow(ResizeDirection), + Fullscreen(Option), + Decorations(bool), + AlwaysOnBottom(bool), + AlwaysOnTop(bool), + WindowIcon(Option), + UserAttention(Option), + SetSkipTaskbar(bool), + CursorIcon(Option), + CursorPosition((i32, i32)), + CursorIgnoreEvents(bool), + WireUpEvents { + transparent: bool, + fullscreen: bool, + cursor_moved: bool, + }, + SetVisibleOnAllWorkspaces(bool), + ProgressBarState(ProgressBarState), + BadgeCount(Option, Option), + SetTheme(Option), + BackgroundColor(CssProvider, Option), +} + +impl Drop for Window { + fn drop(&mut self) { + unsafe { + self.window.destroy(); + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs new file mode 100644 index 000000000..5d5a2cd72 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs @@ -0,0 +1,8 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +pub use x11_dl::{ + error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, xrandr::*, + xrender::*, +}; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs new file mode 100644 index 000000000..0d5524d57 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs @@ -0,0 +1,8 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +pub mod ffi; +pub mod xdisplay; + +pub use xdisplay::XConnection; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs new file mode 100644 index 000000000..b8f40b317 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs @@ -0,0 +1,169 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; + +use libc; +use parking_lot::Mutex; + +use crate::window::CursorIcon; + +use super::ffi; + +/// A connection to an X server. +pub struct XConnection { + pub xlib: ffi::Xlib, + /// Exposes XRandR functions from version < 1.5 + pub xrandr: ffi::Xrandr_2_2_0, + /// Exposes XRandR functions from version = 1.5 + pub xrandr_1_5: Option, + pub xcursor: ffi::Xcursor, + pub xinput2: ffi::XInput2, + pub xlib_xcb: ffi::Xlib_xcb, + pub xrender: ffi::Xrender, + pub display: *mut ffi::Display, + pub x11_fd: c_int, + pub latest_error: Mutex>, + pub cursor_cache: Mutex, ffi::Cursor>>, +} + +unsafe impl Send for XConnection {} +unsafe impl Sync for XConnection {} + +pub type XErrorHandler = + Option libc::c_int>; + +impl XConnection { + pub fn new(error_handler: XErrorHandler) -> Result { + // opening the libraries + let xlib = ffi::Xlib::open()?; + let xcursor = ffi::Xcursor::open()?; + let xrandr = ffi::Xrandr_2_2_0::open()?; + let xrandr_1_5 = ffi::Xrandr::open().ok(); + let xinput2 = ffi::XInput2::open()?; + let xlib_xcb = ffi::Xlib_xcb::open()?; + let xrender = ffi::Xrender::open()?; + + unsafe { (xlib.XInitThreads)() }; + unsafe { (xlib.XSetErrorHandler)(error_handler) }; + + // calling XOpenDisplay + let display = unsafe { + let display = (xlib.XOpenDisplay)(ptr::null()); + if display.is_null() { + return Err(XNotSupported::XOpenDisplayFailed); + } + display + }; + + // Get X11 socket file descriptor + let fd = unsafe { (xlib.XConnectionNumber)(display) }; + + Ok(XConnection { + xlib, + xrandr, + xrandr_1_5, + xcursor, + xinput2, + xlib_xcb, + xrender, + display, + x11_fd: fd, + latest_error: Mutex::new(None), + cursor_cache: Default::default(), + }) + } + + /// Checks whether an error has been triggered by the previous function calls. + #[inline] + pub fn check_errors(&self) -> Result<(), XError> { + let error = self.latest_error.lock().take(); + if let Some(error) = error { + Err(error) + } else { + Ok(()) + } + } + + /// Ignores any previous error. + #[inline] + pub fn ignore_error(&self) { + *self.latest_error.lock() = None; + } +} + +impl fmt::Debug for XConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display.fmt(f) + } +} + +impl Drop for XConnection { + #[inline] + fn drop(&mut self) { + unsafe { (self.xlib.XCloseDisplay)(self.display) }; + } +} + +/// Error triggered by xlib. +#[derive(Debug, Clone)] +pub struct XError { + pub description: String, + pub error_code: u8, + pub request_code: u8, + pub minor_code: u8, +} + +impl Error for XError {} + +impl fmt::Display for XError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + formatter, + "X error: {} (code: {}, request code: {}, minor code: {})", + self.description, self.error_code, self.request_code, self.minor_code + ) + } +} + +/// Error returned if this system doesn't have XLib or can't create an X connection. +#[derive(Clone, Debug)] +pub enum XNotSupported { + /// Failed to load one or several shared libraries. + LibraryOpenError(ffi::OpenError), + /// Connecting to the X server with `XOpenDisplay` failed. + XOpenDisplayFailed, // TODO: add better message +} + +impl From for XNotSupported { + #[inline] + fn from(err: ffi::OpenError) -> XNotSupported { + XNotSupported::LibraryOpenError(err) + } +} + +impl XNotSupported { + fn description(&self) -> &'static str { + match self { + XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", + XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", + } + } +} + +impl Error for XNotSupported { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + XNotSupported::LibraryOpenError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for XNotSupported { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + formatter.write_str(self.description()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs new file mode 100644 index 000000000..ae5fd266b --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs @@ -0,0 +1,125 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::VecDeque, ffi::CStr}; + +use objc2::runtime::{AnyClass as Class, ClassBuilder as ClassDecl, Sel}; +use objc2_app_kit::{self as appkit, NSApplication, NSEvent, NSEventType}; +use std::sync::LazyLock; + +use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; +use crate::event::{DeviceEvent, ElementState, Event}; + +pub struct AppClass(pub *const Class); +unsafe impl Send for AppClass {} +unsafe impl Sync for AppClass {} + +pub static APP_CLASS: LazyLock = LazyLock::new(|| unsafe { + let superclass = class!(NSApplication); + let mut decl = + ClassDecl::new(CStr::from_bytes_with_nul(b"TaoApp\0").unwrap(), superclass).unwrap(); + + decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _)); + + AppClass(decl.register()) +}); + +// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. +// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) +// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) +extern "C" fn send_event(this: &NSApplication, _sel: Sel, event: &NSEvent) { + unsafe { + // For posterity, there are some undocumented event types + // (https://github.com/servo/cocoa-rs/issues/155) + // but that doesn't really matter here. + let event_type = event.r#type(); + let modifier_flags = event.modifierFlags(); + if event_type == appkit::NSKeyUp + && util::has_flag(modifier_flags, appkit::NSEventModifierFlags::Command) + { + if let Some(key_window) = this.keyWindow() { + key_window.sendEvent(event); + } else { + log::debug!("skip sending CMD keyEvent - app has no keyWindow"); + } + } else { + maybe_dispatch_device_event(event); + let superclass = util::superclass(this); + let _: () = msg_send![super(this, superclass), sendEvent: event]; + } + } +} + +unsafe fn maybe_dispatch_device_event(event: &NSEvent) { + let event_type = event.r#type(); + match event_type { + NSEventType::MouseMoved + | NSEventType::LeftMouseDragged + | NSEventType::OtherMouseDragged + | NSEventType::RightMouseDragged => { + let mut events = VecDeque::with_capacity(3); + + let delta_x = event.deltaX() as f64; + let delta_y = event.deltaY() as f64; + + if delta_x != 0.0 { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { + axis: 0, + value: delta_x, + }, + })); + } + + if delta_y != 0.0 { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { + axis: 1, + value: delta_y, + }, + })); + } + + if delta_x != 0.0 || delta_y != 0.0 { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseMotion { + delta: (delta_x, delta_y), + }, + })); + } + + AppState::queue_events(events); + } + NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { + let mut events = VecDeque::with_capacity(1); + + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Button { + button: event.buttonNumber() as u32, + state: ElementState::Pressed, + }, + })); + + AppState::queue_events(events); + } + NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { + let mut events = VecDeque::with_capacity(1); + + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Button { + button: event.buttonNumber() as u32, + state: ElementState::Released, + }, + })); + + AppState::queue_events(events); + } + _ => (), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs new file mode 100644 index 000000000..41b000477 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs @@ -0,0 +1,221 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + platform::macos::ActivationPolicy, + platform_impl::platform::{ + app_state::AppState, + ffi::{id, BOOL, YES}, + }, +}; + +use objc2::runtime::{ + AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel, +}; +use objc2_foundation::{ + NSArray, NSError, NSString, NSUserActivity, NSUserActivityTypeBrowsingWeb, NSURL, +}; +use std::{ + cell::{RefCell, RefMut}, + ffi::{CStr, CString}, + os::raw::c_void, + sync::{LazyLock, Mutex}, + time::Instant, +}; + +const AUX_DELEGATE_STATE_NAME: &str = "auxState"; + +pub struct AuxDelegateState { + /// We store this value in order to be able to defer setting the activation policy until + /// after the app has finished launching. If the activation policy is set earlier, the + /// menubar is initially unresponsive on macOS 10.15 for example. + pub activation_policy: ActivationPolicy, + + /// Whether the application is visible in the dock. + pub dock_visibility: bool, + pub last_dock_show: Mutex>, + + pub activate_ignoring_other_apps: bool, +} + +pub struct AppDelegateClass(pub *const Class); +unsafe impl Send for AppDelegateClass {} +unsafe impl Sync for AppDelegateClass {} + +pub static APP_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoAppDelegateParent\0").unwrap(), + superclass, + ) + .unwrap(); + + decl.add_class_method(sel!(new), new as extern "C" fn(_, _) -> _); + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); + + decl.add_method( + sel!(applicationDidFinishLaunching:), + did_finish_launching as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(applicationWillTerminate:), + application_will_terminate as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(application:openURLs:), + application_open_urls as extern "C" fn(_, _, _, _), + ); + decl.add_method( + sel!(application:willContinueUserActivityWithType:), + application_will_continue_user_activity_with_type as extern "C" fn(_, _, _, _) -> _, + ); + decl.add_method( + sel!(application:continueUserActivity:restorationHandler:), + application_continue_user_activity as extern "C" fn(_, _, _, _, _) -> _, + ); + decl.add_method( + sel!(applicationShouldHandleReopen:hasVisibleWindows:), + application_should_handle_reopen as extern "C" fn(_, _, _, _) -> _, + ); + decl.add_method( + sel!(applicationSupportsSecureRestorableState:), + application_supports_secure_restorable_state as extern "C" fn(_, _, _) -> _, + ); + decl.add_ivar::<*mut c_void>(&CString::new(AUX_DELEGATE_STATE_NAME).unwrap()); + + AppDelegateClass(decl.register()) +}); + +/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS +#[allow(deprecated)] // TODO: Use define_class! +pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> { + let ptr: *mut c_void = *this.get_ivar(AUX_DELEGATE_STATE_NAME); + // Watch out that this needs to be the correct type + (*(ptr as *mut RefCell)).borrow_mut() +} + +extern "C" fn new(class: &Class, _: Sel) -> id { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let this: id = msg_send![class, alloc]; + let this: id = msg_send![this, init]; + *(*this).get_mut_ivar(AUX_DELEGATE_STATE_NAME) = + Box::into_raw(Box::new(RefCell::new(AuxDelegateState { + activation_policy: ActivationPolicy::Regular, + activate_ignoring_other_apps: true, + dock_visibility: true, + last_dock_show: Mutex::new(None), + }))) as *mut c_void; + this + } +} + +extern "C" fn dealloc(this: &Object, _: Sel) { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME)); + // As soon as the box is constructed it is immediately dropped, releasing the underlying + // memory + drop(Box::from_raw(state_ptr as *mut RefCell)); + } +} + +extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) { + trace!("Triggered `applicationDidFinishLaunching`"); + AppState::launched(this); + trace!("Completed `applicationDidFinishLaunching`"); +} + +extern "C" fn application_will_terminate(_: &Object, _: Sel, _: id) { + trace!("Triggered `applicationWillTerminate`"); + AppState::exit(); + trace!("Completed `applicationWillTerminate`"); +} + +extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: &NSArray) { + trace!("Trigger `application:openURLs:`"); + + let urls = unsafe { + (0..urls.count()) + .flat_map(|i| url::Url::parse(&urls.objectAtIndex(i).absoluteString().unwrap().to_string())) + .collect::>() + }; + trace!("Get `application:openURLs:` URLs: {:?}", urls); + AppState::open_urls(urls); + trace!("Completed `application:openURLs:`"); +} + +extern "C" fn application_will_continue_user_activity_with_type( + _: &Object, + _: Sel, + _: id, + user_activity_type: &NSString, +) -> Bool { + trace!("Trigger `application:willContinueUserActivityWithType:`"); + let result = unsafe { Bool::new(user_activity_type == NSUserActivityTypeBrowsingWeb) }; + trace!("Completed `application:willContinueUserActivityWithType:`"); + result +} + +extern "C" fn application_continue_user_activity( + _: &Object, + _: Sel, + _: id, + user_activity: &NSUserActivity, + _restoration_handler: &block2::Block, +) -> Bool { + trace!("Trigger `application:continueUserActivity:restorationHandler:`"); + let url = unsafe { + if user_activity + .activityType() + .isEqualToString(NSUserActivityTypeBrowsingWeb) + { + match user_activity + .webpageURL() + .and_then(|url| url.absoluteString()) + .and_then(|s| Some(s.to_string())) + { + None => { + error!( + "`application:continueUserActivity:restorationHandler:`: restore webbrowsing activity but url is empty" + ); + return Bool::new(false); + } + Some(url_string) => match url::Url::parse(&url_string) { + Ok(url) => url, + Err(err) => { + error!( + "`application:continueUserActivity:restorationHandler:`: failed to parse url {err}" + ); + return Bool::new(false); + } + }, + } + } else { + return Bool::new(false); + } + }; + + AppState::open_urls(vec![url]); + trace!("Completed `application:continueUserActivity:restorationHandler:`"); + return Bool::new(true); +} + +extern "C" fn application_should_handle_reopen( + _: &Object, + _: Sel, + _: id, + has_visible_windows: BOOL, +) -> BOOL { + trace!("Triggered `applicationShouldHandleReopen`"); + AppState::reopen(has_visible_windows.as_bool()); + trace!("Completed `applicationShouldHandleReopen`"); + has_visible_windows +} + +extern "C" fn application_supports_secure_restorable_state(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `applicationSupportsSecureRestorableState`"); + trace!("Completed `applicationSupportsSecureRestorableState`"); + YES +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs new file mode 100644 index 000000000..a305d47f9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs @@ -0,0 +1,469 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + cell::{RefCell, RefMut}, + collections::VecDeque, + fmt::{self, Debug}, + hint::unreachable_unchecked, + mem, + rc::{Rc, Weak}, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, MutexGuard, + }, + time::Instant, +}; + +use objc2::{msg_send, rc::Retained, runtime::AnyObject as Object}; +use objc2_app_kit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}; +use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSSize}; +use std::sync::LazyLock; + +use crate::{ + dpi::LogicalSize, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, + platform::macos::ActivationPolicy, + platform_impl::{ + get_aux_state_mut, + platform::{ + event::{EventProxy, EventWrapper}, + event_loop::{post_dummy_event, PanicInfo}, + ffi::{id, nil}, + observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker}, + util::{self, Never}, + window::get_window_id, + }, + }, + window::WindowId, +}; + +use super::set_dock_visibility; + +static HANDLER: LazyLock = LazyLock::new(|| Default::default()); + +impl<'a, Never> Event<'a, Never> { + fn userify(self) -> Event<'a, T> { + self + .map_nonuser_event() + // `Never` can't be constructed, so the `UserEvent` variant can't + // be present here. + .unwrap_or_else(|_| unsafe { unreachable_unchecked() }) + } +} + +pub trait EventHandler: Debug { + // Not sure probably it should accept Event<'static, Never> + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); + fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + callback: Weak, &RootWindowTarget, &mut ControlFlow)>>, + window_target: Rc>, +} + +impl EventLoopHandler { + fn with_callback(&mut self, f: F) + where + F: FnOnce( + &mut EventLoopHandler, + RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow)>, + ), + { + if let Some(callback) = self.callback.upgrade() { + let callback = callback.borrow_mut(); + (f)(self, callback); + } else { + panic!( + "Tried to dispatch an event, but the event loop that \ + owned the event handler callback seems to be destroyed" + ); + } + } +} + +impl Debug for EventLoopHandler { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("EventLoopHandler") + .field("window_target", &self.window_target) + .finish() + } +} + +impl EventHandler for EventLoopHandler { + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { + self.with_callback(|this, mut callback| { + if let ControlFlow::ExitWithCode(code) = *control_flow { + let dummy = &mut ControlFlow::ExitWithCode(code); + (callback)(event.userify(), &this.window_target, dummy); + } else { + (callback)(event.userify(), &this.window_target, control_flow); + } + }); + } + + fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + self.with_callback(|this, mut callback| { + for event in this.window_target.p.receiver.try_iter() { + if let ControlFlow::ExitWithCode(code) = *control_flow { + let dummy = &mut ControlFlow::ExitWithCode(code); + (callback)(Event::UserEvent(event), &this.window_target, dummy); + } else { + (callback)(Event::UserEvent(event), &this.window_target, control_flow); + } + } + }); + } +} + +#[derive(Default)] +struct Handler { + ready: AtomicBool, + in_callback: AtomicBool, + control_flow: Mutex, + control_flow_prev: Mutex, + start_time: Mutex>, + callback: Mutex>>, + pending_events: Mutex>, + pending_redraw: Mutex>, + waker: Mutex, +} + +unsafe impl Send for Handler {} +unsafe impl Sync for Handler {} + +impl Handler { + fn events(&self) -> MutexGuard<'_, VecDeque> { + self.pending_events.lock().unwrap() + } + + fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec> { + self.pending_redraw.lock().unwrap() + } + + fn waker(&self) -> MutexGuard<'_, EventLoopWaker> { + self.waker.lock().unwrap() + } + + fn is_ready(&self) -> bool { + self.ready.load(Ordering::Acquire) + } + + fn set_ready(&self) { + self.ready.store(true, Ordering::Release); + } + + fn should_exit(&self) -> bool { + matches!( + *self.control_flow.lock().unwrap(), + ControlFlow::ExitWithCode(_) + ) + } + + fn get_control_flow_and_update_prev(&self) -> ControlFlow { + let control_flow = self.control_flow.lock().unwrap(); + *self.control_flow_prev.lock().unwrap() = *control_flow; + *control_flow + } + + fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { + let old = *self.control_flow_prev.lock().unwrap(); + let new = *self.control_flow.lock().unwrap(); + (old, new) + } + + fn get_start_time(&self) -> Option { + *self.start_time.lock().unwrap() + } + + fn update_start_time(&self) { + *self.start_time.lock().unwrap() = Some(Instant::now()); + } + + fn take_events(&self) -> VecDeque { + mem::take(&mut *self.events()) + } + + fn should_redraw(&self) -> Vec { + mem::take(&mut *self.redraw()) + } + + fn get_in_callback(&self) -> bool { + self.in_callback.load(Ordering::Acquire) + } + + fn set_in_callback(&self, in_callback: bool) { + self.in_callback.store(in_callback, Ordering::Release); + } + + fn handle_nonuser_event(&self, wrapper: EventWrapper) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + match wrapper { + EventWrapper::StaticEvent(event) => { + callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()) + } + EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback), + } + } + } + + fn handle_user_events(&self) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + callback.handle_user_events(&mut self.control_flow.lock().unwrap()); + } + } + + fn handle_scale_factor_changed_event( + &self, + callback: &mut Box, + ns_window: &NSWindow, + suggested_size: LogicalSize, + scale_factor: f64, + ) { + let mut size = suggested_size.to_physical(scale_factor); + let old_size = size; + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(ns_window)), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut size, + }, + }; + + callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()); + + if old_size != size { + let logical_size = size.to_logical(scale_factor); + let size = NSSize::new(logical_size.width, logical_size.height); + NSWindow::setContentSize(ns_window, size); + } + } + + fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { + match proxy { + EventProxy::DpiChangedProxy { + ns_window, + suggested_size, + scale_factor, + } => { + self.handle_scale_factor_changed_event(callback, &ns_window, suggested_size, scale_factor) + } + } + } +} + +pub enum AppState {} + +impl AppState { + pub fn set_callback( + callback: Weak, &RootWindowTarget, &mut ControlFlow)>>, + window_target: Rc>, + ) { + *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { + callback, + window_target, + })); + } + + pub fn exit() -> i32 { + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); + HANDLER.set_in_callback(false); + HANDLER.callback.lock().unwrap().take(); + if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { + code + } else { + 0 + } + } + + pub fn launched(app_delegate: &Object) { + apply_activation_policy(app_delegate); + + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let ns_app = NSApp(mtm); + window_activation_hack(&ns_app); + let ignore = get_aux_state_mut(app_delegate).activate_ignoring_other_apps; + #[allow(deprecated)] + ns_app.activateIgnoringOtherApps(ignore); + + let dock_visible = get_aux_state_mut(app_delegate).dock_visibility; + if !dock_visible { + set_dock_visibility(app_delegate, dock_visible); + } + }; + HANDLER.set_ready(); + HANDLER.waker().start(); + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))); + HANDLER.set_in_callback(false); + } + + pub fn open_urls(urls: Vec) { + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); + } + + pub fn reopen(has_visible_windows: bool) { + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Reopen { + has_visible_windows, + })); + } + + pub fn wakeup(panic_info: Weak) { + let panic_info = panic_info + .upgrade() + .expect("The panic info must exist here. This failure indicates a developer error."); + // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 + if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + return; + } + let start = HANDLER.get_start_time().unwrap(); + let cause = match HANDLER.get_control_flow_and_update_prev() { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(requested_resume) => { + if Instant::now() >= requested_resume { + StartCause::ResumeTimeReached { + start, + requested_resume, + } + } else { + StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + } + } + } + ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), + }; + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); + HANDLER.set_in_callback(false); + } + + // This is called from multiple threads at present + pub fn queue_redraw(window_id: WindowId) { + let mut pending_redraw = HANDLER.redraw(); + if !pending_redraw.contains(&window_id) { + pending_redraw.push(window_id); + } + unsafe { + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); + } + } + + pub fn handle_redraw(window_id: WindowId) { + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); + } + + pub fn queue_event(wrapper: EventWrapper) { + if !util::is_main_thread() { + panic!("Event queued from different thread: {:#?}", wrapper); + } + HANDLER.events().push_back(wrapper); + } + + pub fn queue_events(mut wrappers: VecDeque) { + if !util::is_main_thread() { + panic!("Events queued from different thread: {:#?}", wrappers); + } + HANDLER.events().append(&mut wrappers); + } + + pub fn cleared(panic_info: Weak) { + let panic_info = panic_info + .upgrade() + .expect("The panic info must exist here. This failure indicates a developer error."); + // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 + if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { + return; + } + HANDLER.set_in_callback(true); + HANDLER.handle_user_events(); + for event in HANDLER.take_events() { + HANDLER.handle_nonuser_event(event); + } + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); + for window_id in HANDLER.should_redraw() { + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); + } + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); + HANDLER.set_in_callback(false); + if HANDLER.should_exit() { + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let app = NSApp(mtm); + let _pool = NSAutoreleasePool::new(); + let () = msg_send![&app, stop: nil]; + // To stop event loop immediately, we need to post some event here. + post_dummy_event(&app); + }; + } + HANDLER.update_start_time(); + match HANDLER.get_old_and_new_control_flow() { + (ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (), + (old, new) if old == new => (), + (_, ControlFlow::Wait) => HANDLER.waker().stop(), + (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), + (_, ControlFlow::Poll) => HANDLER.waker().start(), + } + } +} + +/// A hack to make activation of multiple windows work when creating them before +/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`. +/// +/// Alternative to this would be the user calling `window.set_visible(true)` in +/// `StartCause::Init`. +/// +/// If this becomes too bothersome to maintain, it can probably be removed +/// without too much damage. +unsafe fn window_activation_hack(ns_app: &NSApplication) { + // Get the application's windows + // TODO: Proper ordering of the windows + let ns_windows: id = msg_send![ns_app, windows]; + let ns_enumerator: id = msg_send![ns_windows, objectEnumerator]; + loop { + // Enumerate over the windows + let ns_window: Option> = msg_send![ns_enumerator, nextObject]; + if ns_window.is_none() { + break; + } + let ns_window = ns_window.unwrap(); + // And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new` + // This way we preserve the user's desired initial visiblity status + // TODO: Also filter on the type/"level" of the window, and maybe other things? + if ns_window.isVisible() { + trace!("Activating visible window"); + ns_window.makeKeyAndOrderFront(None); + } else { + trace!("Skipping activating invisible window"); + } + } +} +fn apply_activation_policy(app_delegate: &Object) { + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let ns_app = NSApp(mtm); + // We need to delay setting the activation policy and activating the app + // until `applicationDidFinishLaunching` has been called. Otherwise the + // menu bar won't be interactable. + let act_pol = get_aux_state_mut(app_delegate).activation_policy; + ns_app.setActivationPolicy(match act_pol { + ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, + ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, + ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, + }); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs new file mode 100644 index 000000000..5c8933b98 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs @@ -0,0 +1,13 @@ +use super::ffi::id; +use objc2_app_kit::NSApp; +use objc2_foundation::{MainThreadMarker, NSString}; + +pub fn set_badge_label(label: Option) { + // SAFETY: TODO + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + unsafe { + let label = label.map(|label| NSString::from_str(&label)); + let dock_tile: id = msg_send![&NSApp(mtm), dockTile]; + let _: () = msg_send![dock_tile, setBadgeLabel: label.as_deref()]; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs new file mode 100644 index 000000000..31a1f25df --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs @@ -0,0 +1,91 @@ +#![allow(non_snake_case, non_upper_case_globals)] + +use std::{ + sync::Mutex, + time::{Duration, Instant}, +}; + +use objc2::{runtime::AnyObject, MainThreadMarker}; +use objc2_app_kit::NSApplication; + +use super::get_aux_state_mut; + +const DOCK_SHOW_TIMEOUT: Duration = Duration::from_secs(1); + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + fn TransformProcessType(psn: *const ProcessSerialNumber, transformState: i32) -> i32; +} + +#[repr(C)] +struct ProcessSerialNumber { + highLongOfPSN: u32, + lowLongOfPSN: u32, +} + +/// https://developer.apple.com/documentation/applicationservices/1501096-anonymous/kcurrentprocess?language=objc +pub const kCurrentProcess: u32 = 2; +/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtouielementapplication?language=objc +pub const kProcessTransformToUIElementApplication: i32 = 4; +/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtoforegroundapplication?language=objc +pub const kProcessTransformToForegroundApplication: i32 = 1; + +pub fn set_dock_visibility(app_delegate: &AnyObject, visible: bool) { + let last_dock_show = unsafe { &get_aux_state_mut(app_delegate).last_dock_show }; + if visible { + set_dock_show(last_dock_show); + } else { + set_dock_hide(last_dock_show); + } +} + +fn set_dock_hide(last_dock_show: &Mutex>) { + // Transforming application state from UIElement to Foreground is an + // asynchronous operation, and unfortunately there is currently no way to know + // when it is finished. + // So if we call DockHide => DockShow => DockHide => DockShow in a very short + // time, we would trigger a bug of macOS that, there would be multiple dock + // icons of the app left in system. + // To work around this, we make sure DockHide does nothing if it is called + // immediately after DockShow. After some experiments, 1 second seems to be + // a proper interval. + let now = Instant::now(); + let last_dock_show = last_dock_show.lock().unwrap(); + if let Some(last_dock_show_time) = *last_dock_show { + if now.duration_since(last_dock_show_time) < DOCK_SHOW_TIMEOUT { + return; + } + } + + unsafe { + // TODO: Safety. + let mtm = MainThreadMarker::new_unchecked(); + let app = NSApplication::sharedApplication(mtm); + let windows = app.windows(); + + for window in windows { + window.setCanHide(false); + } + + let psn = ProcessSerialNumber { + highLongOfPSN: 0, + lowLongOfPSN: kCurrentProcess, + }; + TransformProcessType(&psn, kProcessTransformToUIElementApplication); + } +} + +fn set_dock_show(last_dock_show: &Mutex>) { + let now = Instant::now(); + let mut last_dock_show = last_dock_show.lock().unwrap(); + *last_dock_show = Some(now); + + unsafe { + let psn = ProcessSerialNumber { + highLongOfPSN: 0, + lowLongOfPSN: kCurrentProcess, + }; + + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs new file mode 100644 index 000000000..c4bb2a9d2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs @@ -0,0 +1,335 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::HashSet, ffi::c_void, os::raw::c_ushort, sync::Mutex}; + +use objc2::{msg_send, rc::Retained}; +use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSWindow}; +use std::sync::LazyLock; + +use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; +use objc2_foundation::NSString; + +use crate::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, + platform_impl::platform::{ffi, util::Never}, +}; + +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + +fn insert_or_get_key_str(string: String) -> &'static str { + let mut string_set = KEY_STRINGS.lock().unwrap(); + if let Some(contained) = string_set.get(string.as_str()) { + return contained; + } + let static_str = Box::leak(string.into_boxed_str()); + string_set.insert(static_str); + static_str +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum EventWrapper { + StaticEvent(Event<'static, Never>), + EventProxy(EventProxy), +} + +#[non_exhaustive] +#[derive(Debug, PartialEq)] +pub enum EventProxy { + #[non_exhaustive] + DpiChangedProxy { + ns_window: Retained, + suggested_size: LogicalSize, + scale_factor: f64, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifiers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + +pub fn get_modifierless_char(scancode: u16) -> Key<'static> { + let mut string = [0; 16]; + let input_source; + let layout; + unsafe { + input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); + if input_source.is_null() { + log::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + let layout_data = + ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); + if layout_data.is_null() { + CFRelease(input_source as *mut c_void); + log::error!("`TISGetInputSourceProperty` returned null ptr"); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + layout = CFDataGetBytePtr(layout_data) as *const ffi::UCKeyboardLayout; + } + let keyboard_type = unsafe { ffi::LMGetKbdType() }; + + let mut result_len = 0; + let mut dead_keys = 0; + let modifiers = 0; + let translate_result = unsafe { + ffi::UCKeyTranslate( + layout, + scancode, + ffi::kUCKeyActionDisplay, + modifiers, + keyboard_type as u32, + ffi::kUCKeyTranslateNoDeadKeysMask, + &mut dead_keys, + string.len() as ffi::UniCharCount, + &mut result_len, + string.as_mut_ptr(), + ) + }; + unsafe { + CFRelease(input_source as *mut c_void); + } + if translate_result != 0 { + log::error!( + "`UCKeyTranslate` returned with the non-zero value: {}", + translate_result + ); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + if result_len == 0 { + log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length."); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + let chars = String::from_utf16_lossy(&string[0..result_len as usize]); + Key::Character(insert_or_get_key_str(chars)) +} + +fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key<'static> { + let characters: Retained = unsafe { msg_send![ns_event, charactersIgnoringModifiers] }; + let string = characters.to_string(); + if string.is_empty() { + // Probably a dead key + let first_char = modifierless_chars.chars().next(); + return Key::Dead(first_char); + } + Key::Character(insert_or_get_key_str(string)) +} + +#[allow(clippy::unnecessary_unwrap)] +pub fn create_key_event( + ns_event: &NSEvent, + is_press: bool, + is_repeat: bool, + in_ime: bool, + key_override: Option, +) -> KeyEvent { + use ElementState::{Pressed, Released}; + let state = if is_press { Pressed } else { Released }; + + let scancode = get_scancode(ns_event); + let mut physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); + + let text_with_all_modifiers: Option<&'static str> = { + if key_override.is_some() { + None + } else { + let characters: Retained = unsafe { msg_send![ns_event, characters] }; + let characters = characters.to_string(); + if characters.is_empty() { + None + } else { + if matches!(physical_key, KeyCode::Unidentified(_)) { + // The key may be one of the funky function keys + physical_key = extra_function_key_to_code(scancode, &characters); + } + Some(insert_or_get_key_str(characters)) + } + } + }; + let key_from_code = code_to_key(physical_key, scancode); + let logical_key; + let key_without_modifiers; + if !matches!(key_from_code, Key::Unidentified(_)) { + logical_key = key_from_code.clone(); + key_without_modifiers = key_from_code; + } else { + //#[cfg(debug_assertions)] println!("Couldn't get key from code: {:?}", physical_key); + key_without_modifiers = get_modifierless_char(scancode); + + let modifiers = unsafe { NSEvent::modifierFlags(ns_event) }; + let has_alt = modifiers.contains(NSEventModifierFlags::Option); + let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); + if has_alt || has_ctrl || text_with_all_modifiers.is_none() || !is_press { + let modifierless_chars = match key_without_modifiers.clone() { + Key::Character(ch) => ch, + _ => "", + }; + logical_key = get_logical_key_char(ns_event, modifierless_chars); + } else { + logical_key = Key::Character(text_with_all_modifiers.unwrap()); + } + } + let text = if in_ime || !is_press { + None + } else { + logical_key.to_text() + }; + KeyEvent { + location: code_to_location(physical_key), + logical_key, + physical_key, + repeat: is_repeat, + state, + text, + platform_specific: KeyEventExtra { + text_with_all_modifiers, + key_without_modifiers, + }, + } +} + +pub fn code_to_key(code: KeyCode, scancode: u16) -> Key<'static> { + match code { + KeyCode::Enter => Key::Enter, + KeyCode::Tab => Key::Tab, + KeyCode::Space => Key::Space, + KeyCode::Backspace => Key::Backspace, + KeyCode::Escape => Key::Escape, + KeyCode::SuperRight => Key::Super, + KeyCode::SuperLeft => Key::Super, + KeyCode::ShiftLeft => Key::Shift, + KeyCode::AltLeft => Key::Alt, + KeyCode::ControlLeft => Key::Control, + KeyCode::ShiftRight => Key::Shift, + KeyCode::AltRight => Key::Alt, + KeyCode::ControlRight => Key::Control, + + KeyCode::NumLock => Key::NumLock, + KeyCode::AudioVolumeUp => Key::AudioVolumeUp, + KeyCode::AudioVolumeDown => Key::AudioVolumeDown, + + // Other numpad keys all generate text on macOS (if I understand correctly) + KeyCode::NumpadEnter => Key::Enter, + + KeyCode::F1 => Key::F1, + KeyCode::F2 => Key::F2, + KeyCode::F3 => Key::F3, + KeyCode::F4 => Key::F4, + KeyCode::F5 => Key::F5, + KeyCode::F6 => Key::F6, + KeyCode::F7 => Key::F7, + KeyCode::F8 => Key::F8, + KeyCode::F9 => Key::F9, + KeyCode::F10 => Key::F10, + KeyCode::F11 => Key::F11, + KeyCode::F12 => Key::F12, + KeyCode::F13 => Key::F13, + KeyCode::F14 => Key::F14, + KeyCode::F15 => Key::F15, + KeyCode::F16 => Key::F16, + KeyCode::F17 => Key::F17, + KeyCode::F18 => Key::F18, + KeyCode::F19 => Key::F19, + KeyCode::F20 => Key::F20, + + KeyCode::Insert => Key::Insert, + KeyCode::Home => Key::Home, + KeyCode::PageUp => Key::PageUp, + KeyCode::Delete => Key::Delete, + KeyCode::End => Key::End, + KeyCode::PageDown => Key::PageDown, + KeyCode::ArrowLeft => Key::ArrowLeft, + KeyCode::ArrowRight => Key::ArrowRight, + KeyCode::ArrowDown => Key::ArrowDown, + KeyCode::ArrowUp => Key::ArrowUp, + _ => Key::Unidentified(NativeKeyCode::MacOS(scancode)), + } +} + +fn code_to_location(code: KeyCode) -> KeyLocation { + match code { + KeyCode::SuperRight => KeyLocation::Right, + KeyCode::SuperLeft => KeyLocation::Left, + KeyCode::ShiftLeft => KeyLocation::Left, + KeyCode::AltLeft => KeyLocation::Left, + KeyCode::ControlLeft => KeyLocation::Left, + KeyCode::ShiftRight => KeyLocation::Right, + KeyCode::AltRight => KeyLocation::Right, + KeyCode::ControlRight => KeyLocation::Right, + + KeyCode::NumLock => KeyLocation::Numpad, + KeyCode::NumpadDecimal => KeyLocation::Numpad, + KeyCode::NumpadMultiply => KeyLocation::Numpad, + KeyCode::NumpadAdd => KeyLocation::Numpad, + KeyCode::NumpadDivide => KeyLocation::Numpad, + KeyCode::NumpadEnter => KeyLocation::Numpad, + KeyCode::NumpadSubtract => KeyLocation::Numpad, + KeyCode::NumpadEqual => KeyLocation::Numpad, + KeyCode::Numpad0 => KeyLocation::Numpad, + KeyCode::Numpad1 => KeyLocation::Numpad, + KeyCode::Numpad2 => KeyLocation::Numpad, + KeyCode::Numpad3 => KeyLocation::Numpad, + KeyCode::Numpad4 => KeyLocation::Numpad, + KeyCode::Numpad5 => KeyLocation::Numpad, + KeyCode::Numpad6 => KeyLocation::Numpad, + KeyCode::Numpad7 => KeyLocation::Numpad, + KeyCode::Numpad8 => KeyLocation::Numpad, + KeyCode::Numpad9 => KeyLocation::Numpad, + + _ => KeyLocation::Standard, + } +} + +// While F1-F20 have scancodes we can match on, we have to check against UTF-16 +// constants for the rest. +// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ +pub fn extra_function_key_to_code(scancode: u16, string: &str) -> KeyCode { + if let Some(ch) = string.encode_utf16().next() { + match ch { + 0xf718 => KeyCode::F21, + 0xf719 => KeyCode::F22, + 0xf71a => KeyCode::F23, + 0xf71b => KeyCode::F24, + _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)), + } + } else { + KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)) + } +} + +pub fn event_mods(event: &NSEvent) -> ModifiersState { + let flags = unsafe { NSEvent::modifierFlags(event) }; + let mut m = ModifiersState::empty(); + m.set( + ModifiersState::SHIFT, + flags.contains(NSEventModifierFlags::Shift), + ); + m.set( + ModifiersState::CONTROL, + flags.contains(NSEventModifierFlags::Control), + ); + m.set( + ModifiersState::ALT, + flags.contains(NSEventModifierFlags::Option), + ); + m.set( + ModifiersState::SUPER, + flags.contains(NSEventModifierFlags::Command), + ); + m +} + +pub fn get_scancode(event: &NSEvent) -> c_ushort { + // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, + // and there is no easy way to navtively retrieve the layout-dependent character. + // In tao, we use keycode to refer to the key's character, and so this function aligns + // AppKit's terminology with ours. + unsafe { msg_send![event, keyCode] } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs new file mode 100644 index 000000000..8179ea91f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs @@ -0,0 +1,356 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::VecDeque, + marker::PhantomData, + mem, + os::raw::c_void, + panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}, + process, ptr, + rc::{Rc, Weak}, +}; + +use crossbeam_channel::{self as channel, Receiver, Sender}; +use objc2::{msg_send, rc::Retained}; +use objc2_app_kit::{NSApp, NSApplication, NSEventModifierFlags, NSEventSubtype, NSEventType}; +use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSInteger, NSPoint, NSTimeInterval}; + +use crate::{ + dpi::PhysicalPosition, + error::ExternalError, + event::Event, + event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, + monitor::MonitorHandle as RootMonitorHandle, + platform_impl::{ + platform::{ + app::APP_CLASS, + app_delegate::APP_DELEGATE_CLASS, + app_state::AppState, + ffi::{id, nil, YES}, + monitor::{self, MonitorHandle}, + observer::*, + util::{self, IdRef}, + }, + set_badge_label, set_progress_indicator, + }, + window::{ProgressBarState, Theme}, +}; + +use super::window::set_ns_theme; + +#[derive(Default)] +pub struct PanicInfo { + inner: Cell>>, +} + +// WARNING: +// As long as this struct is used through its `impl`, it is UnwindSafe. +// (If `get_mut` is called on `inner`, unwind safety may get broken.) +impl UnwindSafe for PanicInfo {} +impl RefUnwindSafe for PanicInfo {} +impl PanicInfo { + pub fn is_panicking(&self) -> bool { + let inner = self.inner.take(); + let result = inner.is_some(); + self.inner.set(inner); + result + } + /// Overwrites the curret state if the current state is not panicking + pub fn set_panic(&self, p: Box) { + if !self.is_panicking() { + self.inner.set(Some(p)); + } + } + pub fn take(&self) -> Option> { + self.inner.take() + } +} + +#[derive(Clone)] +pub struct EventLoopWindowTarget { + pub sender: Sender, // this is only here to be cloned elsewhere + pub receiver: Receiver, +} + +impl Default for EventLoopWindowTarget { + fn default() -> Self { + let (sender, receiver) = channel::unbounded(); + EventLoopWindowTarget { sender, receiver } + } +} + +impl EventLoopWindowTarget { + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + monitor::from_point(x, y) + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::AppKit( + rwh_06::AppKitDisplayHandle::new(), + )) + } + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position() + } + + #[inline] + pub fn set_progress_bar(&self, progress: ProgressBarState) { + set_progress_indicator(progress); + } + + #[inline] + pub fn set_badge_count(&self, count: Option, _desktop_filename: Option) { + set_badge_label(count.map(|c| c.to_string())); + } + + #[inline] + pub fn set_badge_label(&self, label: Option) { + set_badge_label(label); + } + + #[inline] + pub fn set_theme(&self, theme: Option) { + set_ns_theme(theme) + } +} + +pub struct EventLoop { + pub(crate) delegate: IdRef, + + window_target: Rc>, + panic_info: Rc, + + /// We make sure that the callback closure is dropped during a panic + /// by making the event loop own it. + /// + /// Every other reference should be a Weak reference which is only upgraded + /// into a strong reference in order to call the callback but then the + /// strong reference should be dropped as soon as possible. + _callback: Option, &RootWindowTarget, &mut ControlFlow)>>>, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] +pub(crate) struct PlatformSpecificEventLoopAttributes {} + +impl EventLoop { + pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { + let panic_info: Rc = Default::default(); + setup_control_flow_observers(Rc::downgrade(&panic_info)); + + let delegate = unsafe { + if !util::is_main_thread() { + panic!("On macOS, `EventLoop` must be created on the main thread!"); + } + + // This must be done before `NSApp()` (equivalent to sending + // `sharedApplication`) is called anywhere else, or we'll end up + // with the wrong `NSApplication` class and the wrong thread could + // be marked as main. + let app: id = msg_send![APP_CLASS.0, sharedApplication]; + + let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); + let _pool = NSAutoreleasePool::new(); + let _: () = msg_send![app, setDelegate:*delegate]; + delegate + }; + + EventLoop { + delegate, + window_target: Rc::new(RootWindowTarget { + p: Default::default(), + _marker: PhantomData, + }), + panic_info, + _callback: None, + } + } + + pub fn window_target(&self) -> &RootWindowTarget { + &self.window_target + } + + pub fn run(mut self, callback: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + let exit_code = self.run_return(callback); + process::exit(exit_code); + } + + pub fn run_return(&mut self, callback: F) -> i32 + where + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), + { + // This transmute is always safe, in case it was reached through `run`, since our + // lifetime will be already 'static. In other cases caller should ensure that all data + // they passed to callback will actually outlive it, some apps just can't move + // everything to event loop, so this is something that they should care about. + let callback = unsafe { + mem::transmute::< + Rc, &RootWindowTarget, &mut ControlFlow)>>, + Rc, &RootWindowTarget, &mut ControlFlow)>>, + >(Rc::new(RefCell::new(callback))) + }; + + self._callback = Some(Rc::clone(&callback)); + + let mtm = MainThreadMarker::new().unwrap(); + + let exit_code = unsafe { + let _pool = NSAutoreleasePool::new(); + let app = NSApp(mtm); + + // A bit of juggling with the callback references to make sure + // that `self.callback` is the only owner of the callback. + let weak_cb: Weak<_> = Rc::downgrade(&callback); + mem::drop(callback); + + AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); + let () = msg_send![&app, run]; + + if let Some(panic) = self.panic_info.take() { + drop(self._callback.take()); + resume_unwind(panic); + } + AppState::exit() + }; + drop(self._callback.take()); + + exit_code + } + + pub fn create_proxy(&self) -> Proxy { + Proxy::new(self.window_target.p.sender.clone()) + } +} + +#[inline] +pub unsafe fn post_dummy_event(target: &NSApplication) { + let event_class = class!(NSEvent); + let dummy_event: id = msg_send![ + event_class, + otherEventWithType: NSEventType::ApplicationDefined, + location: NSPoint::new(0.0, 0.0), + modifierFlags: NSEventModifierFlags::empty(), + timestamp: 0 as NSTimeInterval, + windowNumber: 0 as NSInteger, + context: nil, + subtype: NSEventSubtype::WindowExposed, + data1: 0 as NSInteger, + data2: 0 as NSInteger, + ]; + let () = msg_send![target, postEvent: dummy_event, atStart: YES]; +} + +/// Catches panics that happen inside `f` and when a panic +/// happens, stops the `sharedApplication` +#[inline] +pub fn stop_app_on_panic R + UnwindSafe, R>( + panic_info: Weak, + f: F, +) -> Option { + match catch_unwind(f) { + Ok(r) => Some(r), + Err(e) => { + // It's important that we set the panic before requesting a `stop` + // because some callback are still called during the `stop` message + // and we need to know in those callbacks if the application is currently + // panicking + { + let panic_info = panic_info.upgrade().unwrap(); + panic_info.set_panic(e); + } + unsafe { + let app_class = class!(NSApplication); + let app: Retained = msg_send![app_class, sharedApplication]; + let () = msg_send![&app, stop: nil]; + + // Posting a dummy event to get `stop` to take effect immediately. + // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 + post_dummy_event(&app); + } + None + } + } +} + +pub struct Proxy { + sender: Sender, + source: CFRunLoopSourceRef, +} + +unsafe impl Send for Proxy {} +unsafe impl Sync for Proxy {} + +impl Drop for Proxy { + fn drop(&mut self) { + unsafe { + CFRelease(self.source as _); + } + } +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Proxy::new(self.sender.clone()) + } +} + +impl Proxy { + fn new(sender: Sender) -> Self { + unsafe { + // just wake up the eventloop + extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} + + // adding a Source to the main CFRunLoop lets us wake it up and + // process user events through the normal OS EventLoop mechanisms. + let rl = CFRunLoopGetMain(); + let mut context: CFRunLoopSourceContext = mem::zeroed(); + context.perform = Some(event_loop_proxy_handler); + let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); + CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); + CFRunLoopWakeUp(rl); + + Proxy { sender, source } + } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self + .sender + .send(event) + .map_err(|channel::SendError(x)| EventLoopClosed(x))?; + unsafe { + // let the main thread know there's a new event + CFRunLoopSourceSignal(self.source); + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); + } + Ok(()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs new file mode 100644 index 000000000..3b4a36421 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs @@ -0,0 +1,273 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +// TODO: Upstream these + +#![allow( + dead_code, + non_snake_case, + non_upper_case_globals, + clippy::enum_variant_names +)] + +use std::{ffi::c_void, ptr}; + +use core_foundation::{ + array::CFArrayRef, data::CFDataRef, dictionary::CFDictionaryRef, string::CFStringRef, + uuid::CFUUIDRef, +}; +use core_graphics::{ + base::CGError, + display::{boolean_t, CGDirectDisplayID, CGDisplayConfigRef}, + geometry::{CGPoint, CGRect}, +}; +use objc2::{ + encode::{Encode, Encoding}, + runtime::{AnyObject, Bool}, +}; +use objc2_foundation::NSInteger; + +#[allow(non_camel_case_types)] +pub type id = *mut AnyObject; +pub const nil: id = ptr::null_mut(); + +#[allow(non_camel_case_types)] +pub type BOOL = Bool; +#[allow(deprecated)] +pub const YES: Bool = Bool::YES; +#[allow(deprecated)] +pub const NO: Bool = Bool::NO; + +pub const NSNotFound: NSInteger = NSInteger::MAX; + +pub const kCGBaseWindowLevelKey: NSInteger = 0; +pub const kCGMinimumWindowLevelKey: NSInteger = 1; +pub const kCGDesktopWindowLevelKey: NSInteger = 2; +pub const kCGBackstopMenuLevelKey: NSInteger = 3; +pub const kCGNormalWindowLevelKey: NSInteger = 4; +pub const kCGFloatingWindowLevelKey: NSInteger = 5; +pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6; +pub const kCGDockWindowLevelKey: NSInteger = 7; +pub const kCGMainMenuWindowLevelKey: NSInteger = 8; +pub const kCGStatusWindowLevelKey: NSInteger = 9; +pub const kCGModalPanelWindowLevelKey: NSInteger = 10; +pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11; +pub const kCGDraggingWindowLevelKey: NSInteger = 12; +pub const kCGScreenSaverWindowLevelKey: NSInteger = 13; +pub const kCGMaximumWindowLevelKey: NSInteger = 14; +pub const kCGOverlayWindowLevelKey: NSInteger = 15; +pub const kCGHelpWindowLevelKey: NSInteger = 16; +pub const kCGUtilityWindowLevelKey: NSInteger = 17; +pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; +pub const kCGCursorWindowLevelKey: NSInteger = 19; +pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; + +#[derive(Debug, Clone, Copy)] +#[repr(isize)] +pub enum NSWindowLevel { + BelowNormalWindowLevel = (kCGBaseWindowLevelKey - 1) as _, + NSNormalWindowLevel = kCGBaseWindowLevelKey as _, + NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, + NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _, + NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _, + NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _, + NSStatusWindowLevel = kCGStatusWindowLevelKey as _, + NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, + NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, +} + +unsafe impl Encode for NSWindowLevel { + const ENCODING: Encoding = isize::ENCODING; +} + +pub type CGDisplayFadeInterval = f32; +pub type CGDisplayReservationInterval = f32; +pub type CGDisplayBlendFraction = f32; + +pub const kCGDisplayBlendNormal: f32 = 0.0; +pub const kCGDisplayBlendSolidColor: f32 = 1.0; + +pub type CGDisplayFadeReservationToken = u32; +pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; + +pub type Boolean = u8; +pub const FALSE: Boolean = 0; +pub const TRUE: Boolean = 1; + +pub const kCGErrorSuccess: i32 = 0; +pub const kCGErrorFailure: i32 = 1000; +pub const kCGErrorIllegalArgument: i32 = 1001; +pub const kCGErrorInvalidConnection: i32 = 1002; +pub const kCGErrorInvalidContext: i32 = 1003; +pub const kCGErrorCannotComplete: i32 = 1004; +pub const kCGErrorNotImplemented: i32 = 1006; +pub const kCGErrorRangeCheck: i32 = 1007; +pub const kCGErrorTypeCheck: i32 = 1008; +pub const kCGErrorInvalidOperation: i32 = 1010; +pub const kCGErrorNoneAvailable: i32 = 1011; + +pub const IO1BitIndexedPixels: &str = "P"; +pub const IO2BitIndexedPixels: &str = "PP"; +pub const IO4BitIndexedPixels: &str = "PPPP"; +pub const IO8BitIndexedPixels: &str = "PPPPPPPP"; +pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; +pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; + +pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; +pub const kIO64BitDirectPixels: &str = "-16R16G16B16"; + +pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16"; +pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32"; + +pub const IOYUV422Pixels: &str = "Y4U2V2"; +pub const IO8BitOverlayPixels: &str = "O8"; + +pub type CGWindowLevel = i32; +pub type CGDisplayModeRef = *mut libc::c_void; + +// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. +// However, that framework was only introduced "publicly" in macOS 10.13. +// +// Since we want to support older versions, we can't link to `ColorSync` +// directly. Fortunately, it has always been available as a subframework of +// `ApplicationServices`, see: +// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} + +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + pub fn CGRestorePermanentDisplayConfiguration(); + pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError; + pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError; + pub fn CGConfigureDisplayFadeEffect( + config: CGDisplayConfigRef, + fadeOutSeconds: CGDisplayFadeInterval, + fadeInSeconds: CGDisplayFadeInterval, + fadeRed: f32, + fadeGreen: f32, + fadeBlue: f32, + ) -> CGError; + pub fn CGAcquireDisplayFadeReservation( + seconds: CGDisplayReservationInterval, + token: *mut CGDisplayFadeReservationToken, + ) -> CGError; + pub fn CGDisplayFade( + token: CGDisplayFadeReservationToken, + duration: CGDisplayFadeInterval, + startBlend: CGDisplayBlendFraction, + endBlend: CGDisplayBlendFraction, + redBlend: f32, + greenBlend: f32, + blueBlend: f32, + synchronous: Boolean, + ) -> CGError; + pub fn CGRectContainsPoint(rect: CGRect, point: CGPoint) -> boolean_t; + pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; + pub fn CGShieldingWindowLevel() -> CGWindowLevel; + pub fn CGDisplaySetDisplayMode( + display: CGDirectDisplayID, + mode: CGDisplayModeRef, + options: CFDictionaryRef, + ) -> CGError; + pub fn CGDisplayCopyAllDisplayModes( + display: CGDirectDisplayID, + options: CFDictionaryRef, + ) -> CFArrayRef; + pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize; + pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize; + pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64; + pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef; + pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); + pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); +} + +#[repr(transparent)] +pub struct TISInputSource(std::ffi::c_void); +pub type TISInputSourceRef = *mut TISInputSource; + +#[repr(transparent)] +pub struct UCKeyboardLayout(std::ffi::c_void); + +pub type OptionBits = u32; +pub type UniCharCount = std::os::raw::c_ulong; +pub type UniChar = u16; +pub type OSStatus = i32; + +#[allow(non_upper_case_globals)] +pub const kUCKeyActionDisplay: u16 = 3; +#[allow(non_upper_case_globals)] +pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; + +#[link(name = "Carbon", kind = "framework")] +extern "C" { + pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; + + #[allow(non_snake_case)] + pub fn TISGetInputSourceProperty( + inputSource: TISInputSourceRef, + propertyKey: CFStringRef, + ) -> CFDataRef; + + pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; + + pub fn LMGetKbdType() -> u8; + + #[allow(non_snake_case)] + pub fn UCKeyTranslate( + keyLayoutPtr: *const UCKeyboardLayout, + virtualKeyCode: u16, + keyAction: u16, + modifierKeyState: u32, + keyboardType: u32, + keyTranslateOptions: OptionBits, + deadKeyState: *mut u32, + maxStringLength: UniCharCount, + actualStringLength: *mut UniCharCount, + unicodeString: *mut UniChar, + ) -> OSStatus; +} + +mod core_video { + use super::*; + + #[link(name = "CoreVideo", kind = "framework")] + extern "C" {} + + // CVBase.h + + pub type CVTimeFlags = i32; // int32_t + pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0; + + #[repr(C)] + #[derive(Debug, Clone)] + pub struct CVTime { + pub time_value: i64, // int64_t + pub time_scale: i32, // int32_t + pub flags: i32, // int32_t + } + + // CVReturn.h + + pub type CVReturn = i32; // int32_t + pub const kCVReturnSuccess: CVReturn = 0; + + // CVDisplayLink.h + + pub type CVDisplayLinkRef = *mut c_void; + + extern "C" { + pub fn CVDisplayLinkCreateWithCGDisplay( + displayID: CGDirectDisplayID, + displayLinkOut: *mut CVDisplayLinkRef, + ) -> CVReturn; + pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink: CVDisplayLinkRef) + -> CVTime; + pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef); + } +} + +pub use core_video::*; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs new file mode 100644 index 000000000..c5c7439d0 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs @@ -0,0 +1,14 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::icon::{BadIcon, RgbaIcon}; + +#[derive(Debug, Clone)] +pub struct PlatformIcon(RgbaIcon); + +impl PlatformIcon { + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + Ok(PlatformIcon(RgbaIcon::from_rgba(rgba, width, height)?)) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs new file mode 100644 index 000000000..4418cfcce --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs @@ -0,0 +1,264 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::keyboard::{KeyCode, NativeKeyCode}; + +pub fn keycode_to_scancode(code: KeyCode) -> Option { + match code { + KeyCode::KeyA => Some(0x00), + KeyCode::KeyS => Some(0x01), + KeyCode::KeyD => Some(0x02), + KeyCode::KeyF => Some(0x03), + KeyCode::KeyH => Some(0x04), + KeyCode::KeyG => Some(0x05), + KeyCode::KeyZ => Some(0x06), + KeyCode::KeyX => Some(0x07), + KeyCode::KeyC => Some(0x08), + KeyCode::KeyV => Some(0x09), + KeyCode::KeyB => Some(0x0b), + KeyCode::KeyQ => Some(0x0c), + KeyCode::KeyW => Some(0x0d), + KeyCode::KeyE => Some(0x0e), + KeyCode::KeyR => Some(0x0f), + KeyCode::KeyY => Some(0x10), + KeyCode::KeyT => Some(0x11), + KeyCode::Digit1 => Some(0x12), + KeyCode::Digit2 => Some(0x13), + KeyCode::Digit3 => Some(0x14), + KeyCode::Digit4 => Some(0x15), + KeyCode::Digit6 => Some(0x16), + KeyCode::Digit5 => Some(0x17), + KeyCode::Equal => Some(0x18), + KeyCode::Digit9 => Some(0x19), + KeyCode::Digit7 => Some(0x1a), + KeyCode::Minus => Some(0x1b), + KeyCode::Digit8 => Some(0x1c), + KeyCode::Digit0 => Some(0x1d), + KeyCode::BracketRight => Some(0x1e), + KeyCode::KeyO => Some(0x1f), + KeyCode::KeyU => Some(0x20), + KeyCode::BracketLeft => Some(0x21), + KeyCode::KeyI => Some(0x22), + KeyCode::KeyP => Some(0x23), + KeyCode::Enter => Some(0x24), + KeyCode::KeyL => Some(0x25), + KeyCode::KeyJ => Some(0x26), + KeyCode::Quote => Some(0x27), + KeyCode::KeyK => Some(0x28), + KeyCode::Semicolon => Some(0x29), + KeyCode::Backslash => Some(0x2a), + KeyCode::Comma => Some(0x2b), + KeyCode::Slash => Some(0x2c), + KeyCode::KeyN => Some(0x2d), + KeyCode::KeyM => Some(0x2e), + KeyCode::Period => Some(0x2f), + KeyCode::Tab => Some(0x30), + KeyCode::Space => Some(0x31), + KeyCode::Backquote => Some(0x32), + KeyCode::Backspace => Some(0x33), + KeyCode::Escape => Some(0x35), + KeyCode::SuperRight => Some(0x36), + KeyCode::SuperLeft => Some(0x37), + KeyCode::ShiftLeft => Some(0x38), + KeyCode::AltLeft => Some(0x3a), + KeyCode::ControlLeft => Some(0x3b), + KeyCode::ShiftRight => Some(0x3c), + KeyCode::AltRight => Some(0x3d), + KeyCode::ControlRight => Some(0x3e), + KeyCode::F17 => Some(0x40), + KeyCode::NumpadDecimal => Some(0x41), + KeyCode::NumpadMultiply => Some(0x43), + KeyCode::NumpadAdd => Some(0x45), + KeyCode::NumLock => Some(0x47), + KeyCode::AudioVolumeUp => Some(0x49), + KeyCode::AudioVolumeDown => Some(0x4a), + KeyCode::NumpadDivide => Some(0x4b), + KeyCode::NumpadEnter => Some(0x4c), + KeyCode::NumpadSubtract => Some(0x4e), + KeyCode::F18 => Some(0x4f), + KeyCode::F19 => Some(0x50), + KeyCode::NumpadEqual => Some(0x51), + KeyCode::Numpad0 => Some(0x52), + KeyCode::Numpad1 => Some(0x53), + KeyCode::Numpad2 => Some(0x54), + KeyCode::Numpad3 => Some(0x55), + KeyCode::Numpad4 => Some(0x56), + KeyCode::Numpad5 => Some(0x57), + KeyCode::Numpad6 => Some(0x58), + KeyCode::Numpad7 => Some(0x59), + KeyCode::F20 => Some(0x5a), + KeyCode::Numpad8 => Some(0x5b), + KeyCode::Numpad9 => Some(0x5c), + KeyCode::IntlYen => Some(0x5d), + KeyCode::F5 => Some(0x60), + KeyCode::F6 => Some(0x61), + KeyCode::F7 => Some(0x62), + KeyCode::F3 => Some(0x63), + KeyCode::F8 => Some(0x64), + KeyCode::F9 => Some(0x65), + KeyCode::F11 => Some(0x67), + KeyCode::F13 => Some(0x69), + KeyCode::F16 => Some(0x6a), + KeyCode::F14 => Some(0x6b), + KeyCode::F10 => Some(0x6d), + KeyCode::F12 => Some(0x6f), + KeyCode::F15 => Some(0x71), + KeyCode::Insert => Some(0x72), + KeyCode::Home => Some(0x73), + KeyCode::PageUp => Some(0x74), + KeyCode::Delete => Some(0x75), + KeyCode::F4 => Some(0x76), + KeyCode::End => Some(0x77), + KeyCode::F2 => Some(0x78), + KeyCode::PageDown => Some(0x79), + KeyCode::F1 => Some(0x7a), + KeyCode::ArrowLeft => Some(0x7b), + KeyCode::ArrowRight => Some(0x7c), + KeyCode::ArrowDown => Some(0x7d), + KeyCode::ArrowUp => Some(0x7e), + _ => None, + } +} + +pub fn keycode_from_scancode(scancode: u32) -> KeyCode { + match scancode { + 0x00 => KeyCode::KeyA, + 0x01 => KeyCode::KeyS, + 0x02 => KeyCode::KeyD, + 0x03 => KeyCode::KeyF, + 0x04 => KeyCode::KeyH, + 0x05 => KeyCode::KeyG, + 0x06 => KeyCode::KeyZ, + 0x07 => KeyCode::KeyX, + 0x08 => KeyCode::KeyC, + 0x09 => KeyCode::KeyV, + //0x0a => World 1, + 0x0b => KeyCode::KeyB, + 0x0c => KeyCode::KeyQ, + 0x0d => KeyCode::KeyW, + 0x0e => KeyCode::KeyE, + 0x0f => KeyCode::KeyR, + 0x10 => KeyCode::KeyY, + 0x11 => KeyCode::KeyT, + 0x12 => KeyCode::Digit1, + 0x13 => KeyCode::Digit2, + 0x14 => KeyCode::Digit3, + 0x15 => KeyCode::Digit4, + 0x16 => KeyCode::Digit6, + 0x17 => KeyCode::Digit5, + 0x18 => KeyCode::Equal, + 0x19 => KeyCode::Digit9, + 0x1a => KeyCode::Digit7, + 0x1b => KeyCode::Minus, + 0x1c => KeyCode::Digit8, + 0x1d => KeyCode::Digit0, + 0x1e => KeyCode::BracketRight, + 0x1f => KeyCode::KeyO, + 0x20 => KeyCode::KeyU, + 0x21 => KeyCode::BracketLeft, + 0x22 => KeyCode::KeyI, + 0x23 => KeyCode::KeyP, + 0x24 => KeyCode::Enter, + 0x25 => KeyCode::KeyL, + 0x26 => KeyCode::KeyJ, + 0x27 => KeyCode::Quote, + 0x28 => KeyCode::KeyK, + 0x29 => KeyCode::Semicolon, + 0x2a => KeyCode::Backslash, + 0x2b => KeyCode::Comma, + 0x2c => KeyCode::Slash, + 0x2d => KeyCode::KeyN, + 0x2e => KeyCode::KeyM, + 0x2f => KeyCode::Period, + 0x30 => KeyCode::Tab, + 0x31 => KeyCode::Space, + 0x32 => KeyCode::Backquote, + 0x33 => KeyCode::Backspace, + //0x34 => unknown, + 0x35 => KeyCode::Escape, + 0x36 => KeyCode::SuperRight, + 0x37 => KeyCode::SuperLeft, + 0x38 => KeyCode::ShiftLeft, + 0x39 => KeyCode::CapsLock, + 0x3a => KeyCode::AltLeft, + 0x3b => KeyCode::ControlLeft, + 0x3c => KeyCode::ShiftRight, + 0x3d => KeyCode::AltRight, + 0x3e => KeyCode::ControlRight, + 0x3f => KeyCode::Fn, + 0x40 => KeyCode::F17, + 0x41 => KeyCode::NumpadDecimal, + //0x42 -> unknown, + 0x43 => KeyCode::NumpadMultiply, + //0x44 => unknown, + 0x45 => KeyCode::NumpadAdd, + //0x46 => unknown, + 0x47 => KeyCode::NumLock, + //0x48 => KeyCode::NumpadClear, + + // TODO: (Artur) for me, kVK_VolumeUp is 0x48 + // macOS 10.11 + // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + //0x49 => KeyCode::AudioVolumeUp, + 0x49 => KeyCode::AudioVolumeDown, + 0x4b => KeyCode::NumpadDivide, + 0x4c => KeyCode::NumpadEnter, + //0x4d => unknown, + 0x4e => KeyCode::NumpadSubtract, + 0x4f => KeyCode::F18, + 0x50 => KeyCode::F19, + 0x51 => KeyCode::NumpadEqual, + 0x52 => KeyCode::Numpad0, + 0x53 => KeyCode::Numpad1, + 0x54 => KeyCode::Numpad2, + 0x55 => KeyCode::Numpad3, + 0x56 => KeyCode::Numpad4, + 0x57 => KeyCode::Numpad5, + 0x58 => KeyCode::Numpad6, + 0x59 => KeyCode::Numpad7, + 0x5a => KeyCode::F20, + 0x5b => KeyCode::Numpad8, + 0x5c => KeyCode::Numpad9, + 0x5d => KeyCode::IntlYen, + //0x5e => JIS Ro, + //0x5f => unknown, + 0x60 => KeyCode::F5, + 0x61 => KeyCode::F6, + 0x62 => KeyCode::F7, + 0x63 => KeyCode::F3, + 0x64 => KeyCode::F8, + 0x65 => KeyCode::F9, + //0x66 => JIS Eisuu (macOS), + 0x67 => KeyCode::F11, + //0x68 => JIS Kanna (macOS), + 0x69 => KeyCode::F13, + 0x6a => KeyCode::F16, + 0x6b => KeyCode::F14, + //0x6c => unknown, + 0x6d => KeyCode::F10, + //0x6e => unknown, + 0x6f => KeyCode::F12, + //0x70 => unknown, + 0x71 => KeyCode::F15, + 0x72 => KeyCode::Insert, + 0x73 => KeyCode::Home, + 0x74 => KeyCode::PageUp, + 0x75 => KeyCode::Delete, + 0x76 => KeyCode::F4, + 0x77 => KeyCode::End, + 0x78 => KeyCode::F2, + 0x79 => KeyCode::PageDown, + 0x7a => KeyCode::F1, + 0x7b => KeyCode::ArrowLeft, + 0x7c => KeyCode::ArrowRight, + 0x7d => KeyCode::ArrowDown, + 0x7e => KeyCode::ArrowUp, + //0x7f => unknown, + + // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as + // backquote (`) on Windows' US layout. + 0xa => KeyCode::Backquote, + _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode as u16)), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs new file mode 100644 index 000000000..0c24a5257 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs @@ -0,0 +1,97 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +mod app; +mod app_delegate; +mod app_state; +mod badge; +mod dock; +mod event; +mod event_loop; +mod ffi; +mod icon; +mod keycode; +mod monitor; +mod observer; +mod progress_bar; +mod util; +mod view; +mod window; +mod window_delegate; + +use std::{fmt, ops::Deref, sync::Arc}; + +pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; +pub use self::{ + app_delegate::get_aux_state_mut, + event::KeyEventExtra, + event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, + keycode::{keycode_from_scancode, keycode_to_scancode}, + monitor::{MonitorHandle, VideoMode}, + progress_bar::set_progress_indicator, + window::{Id as WindowId, Parent, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, +}; +use crate::{ + error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, +}; +pub(crate) use badge::set_badge_label; +pub(crate) use dock::set_dock_visibility; +pub(crate) use icon::PlatformIcon; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; + +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId + } +} + +// Constant device ID; to be removed when if backend is updated to report real device IDs. +pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); + +pub struct Window { + window: Arc, + // We keep this around so that it doesn't get dropped until the window does. + #[allow(dead_code)] + delegate: util::IdRef, +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum OsError { + CGError(core_graphics::base::CGError), + CreationError(&'static str), +} + +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Deref for Window { + type Target = UnownedWindow; + #[inline] + fn deref(&self) -> &Self::Target { + &self.window + } +} + +impl Window { + pub fn new( + _window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let (window, delegate) = UnownedWindow::new(attributes, pl_attribs)?; + Ok(Window { window, delegate }) + } +} + +impl fmt::Display for OsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OsError::CGError(e) => f.pad(&format!("CGError {}", e)), + OsError::CreationError(e) => f.pad(e), + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs new file mode 100644 index 000000000..7cdbd3986 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs @@ -0,0 +1,338 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::VecDeque, fmt}; + +use super::ffi::{self, id, nil, CGRectContainsPoint}; +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, +}; +use core_foundation::{ + array::{CFArrayGetCount, CFArrayGetValueAtIndex}, + base::{CFRelease, TCFType}, + string::CFString, +}; +use core_graphics::{ + display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}, + geometry::CGPoint, +}; +use objc2::{msg_send, rc::Retained}; +use objc2_app_kit::NSScreen; +use objc2_foundation::{ns_string, MainThreadMarker, NSString, NSUInteger}; + +#[derive(Clone)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, + pub(crate) native_mode: NativeDisplayMode, +} + +impl PartialEq for VideoMode { + fn eq(&self, other: &Self) -> bool { + self.size == other.size + && self.bit_depth == other.bit_depth + && self.refresh_rate == other.refresh_rate + && self.monitor == other.monitor + } +} + +impl Eq for VideoMode {} + +impl std::hash::Hash for VideoMode { + fn hash(&self, state: &mut H) { + self.size.hash(state); + self.bit_depth.hash(state); + self.refresh_rate.hash(state); + self.monitor.hash(state); + } +} + +impl std::fmt::Debug for VideoMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VideoMode") + .field("size", &self.size) + .field("bit_depth", &self.bit_depth) + .field("refresh_rate", &self.refresh_rate) + .field("monitor", &self.monitor) + .finish() + } +} + +pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); + +unsafe impl Send for NativeDisplayMode {} + +impl Drop for NativeDisplayMode { + fn drop(&mut self) { + unsafe { + ffi::CGDisplayModeRelease(self.0); + } + } +} + +impl Clone for NativeDisplayMode { + fn clone(&self) -> Self { + unsafe { + ffi::CGDisplayModeRetain(self.0); + } + NativeDisplayMode(self.0) + } +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(Clone)] +pub struct MonitorHandle(CGDirectDisplayID); + +// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that +// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an +// unique identifier that persists even across system reboots +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0) + == ffi::CGDisplayCreateUUIDFromDisplayID(other.0) + } + } +} + +impl Eq for MonitorHandle {} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0) + .cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0)) + } + } +} + +impl std::hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state); + } + } +} + +pub fn available_monitors() -> VecDeque { + if let Ok(displays) = CGDisplay::active_displays() { + let mut monitors = VecDeque::with_capacity(displays.len()); + for display in displays { + monitors.push_back(MonitorHandle(display)); + } + monitors + } else { + VecDeque::with_capacity(0) + } +} + +pub fn primary_monitor() -> MonitorHandle { + MonitorHandle(CGDisplay::main().id) +} + +// `from_point` get a monitor handle which contains the given point. +pub fn from_point(x: f64, y: f64) -> Option { + unsafe { + for monitor in available_monitors() { + let bound = CGDisplayBounds(monitor.0); + if CGRectContainsPoint(bound, CGPoint::new(x, y)) > 0 { + return Some(monitor); + } + } + } + None +} + +impl fmt::Debug for MonitorHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Do this using the proper fmt API + #[derive(Debug)] + #[allow(dead_code)] // none of the member reads in Debug::fmt are detected + struct MonitorHandle { + name: Option, + native_identifier: u32, + size: PhysicalSize, + position: PhysicalPosition, + scale_factor: f64, + } + + let monitor_id_proxy = MonitorHandle { + name: self.name(), + native_identifier: self.native_identifier(), + size: self.size(), + position: self.position(), + scale_factor: self.scale_factor(), + }; + + monitor_id_proxy.fmt(f) + } +} + +impl MonitorHandle { + pub fn new(id: CGDirectDisplayID) -> Self { + MonitorHandle(id) + } + + pub fn name(&self) -> Option { + let MonitorHandle(display_id) = *self; + let screen_num = CGDisplay::new(display_id).model_number(); + Some(format!("Monitor #{}", screen_num)) + } + + #[inline] + pub fn native_identifier(&self) -> u32 { + self.0 + } + + pub fn size(&self) -> PhysicalSize { + let MonitorHandle(display_id) = *self; + let display = CGDisplay::new(display_id); + let height = display.pixels_high(); + let width = display.pixels_wide(); + PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor()) + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; + PhysicalPosition::from_logical::<_, f64>( + (bounds.origin.x as f64, bounds.origin.y as f64), + self.scale_factor(), + ) + } + + pub fn scale_factor(&self) -> f64 { + let screen = match self.ns_screen() { + Some(screen) => screen, + None => return 1.0, // default to 1.0 when we can't find the screen + }; + NSScreen::backingScaleFactor(&screen) as f64 + } + + pub fn video_modes(&self) -> impl Iterator { + let cv_refresh_rate = unsafe { + let mut display_link = std::ptr::null_mut(); + assert_eq!( + ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link), + ffi::kCVReturnSuccess + ); + let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); + ffi::CVDisplayLinkRelease(display_link); + + // This value is indefinite if an invalid display link was specified + assert!(time.flags & ffi::kCVTimeIsIndefinite == 0); + + time.time_scale as i64 / time.time_value + }; + + let monitor = self.clone(); + + unsafe { + let modes = { + let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); + assert!(!array.is_null(), "failed to get list of display modes"); + let array_count = CFArrayGetCount(array); + let modes: Vec<_> = (0..array_count) + .map(move |i| { + let mode = CFArrayGetValueAtIndex(array, i) as *mut _; + ffi::CGDisplayModeRetain(mode); + mode + }) + .collect(); + CFRelease(array as *const _); + modes + }; + + modes.into_iter().map(move |mode| { + let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; + + // CGDisplayModeGetRefreshRate returns 0.0 for any display that + // isn't a CRT + let refresh_rate = if cg_refresh_rate > 0 { + cg_refresh_rate + } else { + cv_refresh_rate + }; + + let pixel_encoding = + CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode)).to_string(); + let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { + 32 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { + 16 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { + 30 + } else { + unimplemented!() + }; + + let video_mode = VideoMode { + size: ( + ffi::CGDisplayModeGetPixelWidth(mode) as u32, + ffi::CGDisplayModeGetPixelHeight(mode) as u32, + ), + refresh_rate: refresh_rate as u16, + bit_depth, + monitor: monitor.clone(), + native_mode: NativeDisplayMode(mode), + }; + + RootVideoMode { video_mode } + }) + } + } + + pub(crate) fn ns_screen(&self) -> Option> { + // SAFETY: TODO. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + unsafe { + let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); + let screens = NSScreen::screens(mtm); + let count: NSUInteger = msg_send![&screens, count]; + let key = ns_string!("NSScreenNumber"); + for i in 0..count { + let screen: Retained = msg_send![&screens, objectAtIndex: i as NSUInteger]; + let device_description = NSScreen::deviceDescription(&screen); + let value: id = msg_send![&device_description, objectForKey: &*key]; + if value != nil { + let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; + let other_uuid = + ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); + if uuid == other_uuid { + return Some(screen); + } + } + } + None + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs new file mode 100644 index 000000000..02524ea7d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs @@ -0,0 +1,305 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + self, + os::raw::*, + panic::{AssertUnwindSafe, UnwindSafe}, + ptr, + rc::Weak, + time::Instant, +}; + +use crate::platform_impl::platform::{ + app_state::AppState, + event_loop::{stop_app_on_panic, PanicInfo}, + ffi, +}; + +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + pub static kCFRunLoopCommonModes: CFRunLoopMode; + + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: ffi::Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode); + pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); + #[allow(dead_code)] + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); +} + +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +#[allow(non_upper_case_globals)] +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +#[allow(non_upper_case_globals)] +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +#[allow(non_upper_case_globals)] +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +#[allow(non_upper_case_globals)] +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = + extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void); +pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void); + +pub enum CFRunLoopTimerContext {} + +/// This mirrors the struct with the same name from Core Foundation. +/// https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc +#[allow(non_snake_case)] +#[repr(C)] +pub struct CFRunLoopObserverContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, + pub equal: Option ffi::Boolean>, + pub hash: Option CFHashCode>, + pub schedule: Option, + pub cancel: Option, + pub perform: Option, +} + +unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) +where + F: FnOnce(Weak) + UnwindSafe, +{ + let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo); + // Asserting unwind safety on this type should be fine because `PanicInfo` is + // `RefUnwindSafe` and `Rc` is `UnwindSafe` if `T` is `RefUnwindSafe`. + let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw)); + // `from_raw` takes ownership of the data behind the pointer. + // But if this scope takes ownership of the weak pointer, then + // the weak pointer will get free'd at the end of the scope. + // However we want to keep that weak reference around after the function. + std::mem::forget(info_from_raw); + + stop_app_on_panic(Weak::clone(&panic_info), move || { + let _ = &panic_info; + f(panic_info.0) + }); +} + +// begin is queued with the highest priority to ensure it is processed before other observers +extern "C" fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + panic_info: *mut c_void, +) { + unsafe { + control_flow_handler(panic_info, |panic_info| { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => { + //trace!("Triggered `CFRunLoopAfterWaiting`"); + AppState::wakeup(panic_info); + //trace!("Completed `CFRunLoopAfterWaiting`"); + } + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + }); + } +} + +// end is queued with the lowest priority to ensure it is processed after other observers +// without that, LoopDestroyed would get sent after MainEventsCleared +extern "C" fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + panic_info: *mut c_void, +) { + unsafe { + control_flow_handler(panic_info, |panic_info| { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => { + //trace!("Triggered `CFRunLoopBeforeWaiting`"); + AppState::cleared(panic_info); + //trace!("Completed `CFRunLoopBeforeWaiting`"); + } + kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + }); + } +} + +struct RunLoop(CFRunLoopRef); + +impl RunLoop { + unsafe fn get() -> Self { + RunLoop(CFRunLoopGetMain()) + } + + unsafe fn add_observer( + &self, + flags: CFOptionFlags, + priority: CFIndex, + handler: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) { + let observer = CFRunLoopObserverCreate( + ptr::null_mut(), + flags, + ffi::TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run + handler, + context, + ); + CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes); + } +} + +pub fn setup_control_flow_observers(panic_info: Weak) { + unsafe { + let mut context = CFRunLoopObserverContext { + info: Weak::into_raw(panic_info) as *mut _, + version: 0, + retain: None, + release: None, + copyDescription: None, + }; + let run_loop = RunLoop::get(); + run_loop.add_observer( + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + CFIndex::MIN, + control_flow_begin_handler, + &mut context as *mut _, + ); + run_loop.add_observer( + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + CFIndex::MAX, + control_flow_end_handler, + &mut context as *mut _, + ); + } +} + +pub struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl Default for EventLoopWaker { + fn default() -> EventLoopWaker { + extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. + // It is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediately in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); + EventLoopWaker { timer } + } + } +} + +impl EventLoopWaker { + pub fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } + } + + pub fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } + } + + pub fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs new file mode 100644 index 000000000..d50015361 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs @@ -0,0 +1,161 @@ +use std::{ffi::CStr, sync::Once}; + +use objc2::{ + msg_send, + rc::Retained, + runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, +}; +use objc2_foundation::{NSArray, NSInsetRect, NSPoint, NSRect, NSSize}; + +use super::ffi::{id, nil, NO}; +use crate::window::{ProgressBarState, ProgressState}; + +/// Set progress indicator in the Dock. +pub fn set_progress_indicator(progress_state: ProgressBarState) { + unsafe { + let ns_app: id = msg_send![class!(NSApplication), sharedApplication]; + let dock_tile: id = msg_send![ns_app, dockTile]; + if dock_tile == nil { + return; + } + + // check progress indicator is already set or create new one + let progress_indicator: id = get_exist_progress_indicator(dock_tile) + .unwrap_or_else(|| create_progress_indicator(ns_app, dock_tile)); + + // set progress indicator state + if let Some(progress) = progress_state.progress { + let progress = progress.clamp(0, 100) as f64; + let _: () = msg_send![progress_indicator, setDoubleValue: progress]; + let _: () = msg_send![progress_indicator, setHidden: NO]; + } + #[allow(deprecated)] // TODO: Use define_class! + if let Some(state) = progress_state.state { + *(*progress_indicator).get_mut_ivar("state") = state as u8; + let _: () = msg_send![ + progress_indicator, + setHidden: matches!(state, ProgressState::None) + ]; + } + + let _: () = msg_send![dock_tile, display]; + } +} + +fn create_progress_indicator(ns_app: id, dock_tile: id) -> id { + unsafe { + let mut image_view: id = msg_send![dock_tile, contentView]; + if image_view == nil { + // create new dock tile view with current app icon + let app_icon_image: id = msg_send![ns_app, applicationIconImage]; + image_view = msg_send![class!(NSImageView), imageViewWithImage: app_icon_image]; + let _: () = msg_send![dock_tile, setContentView: image_view]; + } + + // create custom progress indicator + let dock_tile_size: NSSize = msg_send![dock_tile, size]; + let frame = NSRect::new( + NSPoint::new(0.0, 0.0), + NSSize::new(dock_tile_size.width, 15.0), + ); + let progress_class = create_progress_indicator_class(); + let progress_indicator: id = msg_send![progress_class, alloc]; + let progress_indicator: id = msg_send![progress_indicator, initWithFrame: frame]; + let _: id = msg_send![progress_indicator, autorelease]; + + // set progress indicator to the dock tile + let _: () = msg_send![image_view, addSubview: progress_indicator]; + + progress_indicator + } +} + +fn get_exist_progress_indicator(dock_tile: id) -> Option { + unsafe { + let content_view: id = msg_send![dock_tile, contentView]; + if content_view == nil { + return None; + } + let subviews: Option> = msg_send![content_view, subviews]; + let subviews = subviews?; + + for idx in 0..subviews.count() { + let subview: id = msg_send![&subviews, objectAtIndex: idx]; + + let is_progress_indicator: bool = + msg_send![subview, isKindOfClass: class!(NSProgressIndicator)]; + if is_progress_indicator { + return Some(subview); + } + } + } + None +} + +fn create_progress_indicator_class() -> *const Class { + static mut APP_CLASS: *const Class = 0 as *const Class; + static INIT: Once = Once::new(); + + INIT.call_once(|| unsafe { + let superclass = class!(NSProgressIndicator); + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoProgressIndicator\0").unwrap(), + superclass, + ) + .unwrap(); + + decl.add_method(sel!(drawRect:), draw_progress_bar as extern "C" fn(_, _, _)); + + // progress bar states, follows ProgressState + decl.add_ivar::(CStr::from_bytes_with_nul(b"state\0").unwrap()); + + APP_CLASS = decl.register(); + }); + + unsafe { APP_CLASS } +} + +extern "C" fn draw_progress_bar(this: &Object, _: Sel, rect: NSRect) { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let bar = NSRect::new( + NSPoint { x: 0.0, y: 4.0 }, + NSSize { + width: rect.size.width, + height: 8.0, + }, + ); + let bar_inner = NSInsetRect(bar, 0.5, 0.5); + let mut bar_progress = NSInsetRect(bar, 1.0, 1.0); + + // set progress width + let current_progress: f64 = msg_send![this, doubleValue]; + let normalized_progress: f64 = (current_progress / 100.0).clamp(0.0, 1.0); + bar_progress.size.width *= normalized_progress; + + // draw outer bar + let bg_color: id = msg_send![class!(NSColor), colorWithWhite:1.0 alpha:0.05]; + let _: () = msg_send![bg_color, set]; + draw_rounded_rect(bar); + // draw inner bar + draw_rounded_rect(bar_inner); + + // draw progress + let state: u8 = *(this.get_ivar("state")); + let progress_color: id = match state { + x if x == ProgressState::Paused as u8 => msg_send![class!(NSColor), systemYellowColor], + x if x == ProgressState::Error as u8 => msg_send![class!(NSColor), systemRedColor], + _ => msg_send![class!(NSColor), systemBlueColor], + }; + let _: () = msg_send![progress_color, set]; + draw_rounded_rect(bar_progress); + } +} + +fn draw_rounded_rect(rect: NSRect) { + unsafe { + let raduis = rect.size.height / 2.0; + let bezier_path: id = msg_send![class!(NSBezierPath), bezierPathWithRoundedRect:rect, xRadius:raduis, yRadius:raduis]; + let _: () = msg_send![bezier_path, fill]; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs new file mode 100644 index 000000000..23dd8ec48 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs @@ -0,0 +1,263 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + ops::Deref, + sync::{Mutex, Weak}, +}; + +use core_graphics::base::CGFloat; +use dispatch2::{DispatchQueue, DispatchQueueAttr}; +use objc2::{rc::autoreleasepool, Message}; +use objc2_app_kit::{NSScreen, NSView, NSWindow, NSWindowStyleMask}; +use objc2_foundation::{MainThreadMarker, NSPoint, NSSize, NSString}; + +use crate::{ + dpi::LogicalSize, + platform_impl::platform::{ + ffi::{self, id, NO, YES}, + view::reapply_traffic_light_inset, + window::SharedState, + }, +}; + +pub fn is_main_thread() -> bool { + unsafe { msg_send!(class!(NSThread), isMainThread) } +} + +// Unsafe wrapper type that allows us to dispatch things that aren't Send. +// This should *only* be used to dispatch to the main queue. +// While it is indeed not guaranteed that these types can safely be sent to +// other threads, we know that they're safe to use on the main thread. +struct MainThreadSafe(T); + +unsafe impl Send for MainThreadSafe {} + +impl Deref for MainThreadSafe { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +fn run_on_main(f: F) { + if is_main_thread() { + f(); + } else { + DispatchQueue::main().exec_sync(f); + } +} + +unsafe fn set_style_mask(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) { + ns_window.setStyleMask(mask); + // If we don't do this, key handling will break + // (at least until the window is clicked again/etc.) + ns_window.makeFirstResponder(Some(ns_view)); +} + +// Always use this function instead of trying to modify `styleMask` directly! +// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. +// Otherwise, this would vomit out errors about not being on the main thread +// and fail to do anything. +pub unsafe fn set_style_mask_async( + ns_window: &NSWindow, + ns_view: &NSView, + mask: NSWindowStyleMask, +) { + let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); + DispatchQueue::main().exec_async(move || { + set_style_mask(&ns_window, &ns_view, mask); + }); +} +pub unsafe fn set_style_mask_sync(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) { + if is_main_thread() { + set_style_mask(ns_window, ns_view, mask); + } else { + let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); + DispatchQueue::main().exec_sync(move || { + set_style_mask(&ns_window, &ns_view, mask); + }) + } +} + +// `setContentSize:` isn't thread-safe either, though it doesn't log any errors +// and just fails silently. Anyway, GCD to the rescue! +pub unsafe fn set_content_size_async(ns_window: &NSWindow, size: LogicalSize) { + let ns_window = MainThreadSafe(ns_window.retain()); + DispatchQueue::main().exec_async(move || { + ns_window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); + }); +} + +// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy +// to log errors. +pub unsafe fn set_frame_top_left_point_async(ns_window: &NSWindow, point: NSPoint) { + let ns_window = MainThreadSafe(ns_window.retain()); + DispatchQueue::main().exec_async(move || { + ns_window.setFrameTopLeftPoint(point); + }); +} + +// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. +pub unsafe fn set_level_async(ns_window: &NSWindow, level: ffi::NSWindowLevel) { + let ns_window = MainThreadSafe(ns_window.retain()); + DispatchQueue::main().exec_async(move || { + ns_window.setLevel(level as _); + }); +} + +// `toggleFullScreen` is thread-safe, but our additional logic to account for +// window styles isn't. +pub unsafe fn toggle_full_screen_async( + ns_window: &NSWindow, + ns_view: &NSView, + not_fullscreen: bool, + shared_state: Weak>, +) { + let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); + let shared_state = MainThreadSafe(shared_state); + DispatchQueue::main().exec_async(move || { + // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we + // set a normal style temporarily. The previous state will be + // restored in `WindowDelegate::window_did_exit_fullscreen`. + if not_fullscreen { + let curr_mask = ns_window.styleMask(); + let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; + if !curr_mask.contains(required) { + set_style_mask(&ns_window, &ns_view, required); + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + shared_state_lock.saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); + } + } + } + // Window level must be restored from `CGShieldingWindowLevel() + // + 1` back to normal in order for `toggleFullScreen` to do + // anything + ns_window.setLevel(0); + ns_window.toggleFullScreen(None); + }); +} + +pub unsafe fn restore_display_mode_async(ns_screen: u32) { + DispatchQueue::main().exec_async(move || { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); + }); +} + +// `setMaximized` is not thread-safe +pub unsafe fn set_maximized_async( + ns_window: &NSWindow, + is_zoomed: bool, + maximized: bool, + shared_state: Weak>, +) { + let ns_window = MainThreadSafe(ns_window.retain()); + let shared_state = MainThreadSafe(shared_state); + DispatchQueue::main().exec_async(move || { + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `set_maximized`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + shared_state_lock.standard_frame = Some(NSWindow::frame(&ns_window)); + } + + shared_state_lock.maximized = maximized; + + let curr_mask = ns_window.styleMask(); + if shared_state_lock.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } else if curr_mask.contains(NSWindowStyleMask::Resizable) + && curr_mask.contains(NSWindowStyleMask::Titled) + { + // Just use the native zoom if resizable + ns_window.zoom(None); + } else { + // if it's not resizable, we set the frame directly + let new_rect = if maximized { + let mtm = MainThreadMarker::new_unchecked(); + let screen = NSScreen::mainScreen(mtm).unwrap(); + NSScreen::visibleFrame(&screen) + } else { + shared_state_lock.saved_standard_frame() + }; + let _: () = msg_send![&*ns_window, setFrame:new_rect, display:NO, animate: YES]; + } + + trace!("Unlocked shared state in `set_maximized`"); + } + }); +} + +// `orderOut:` isn't thread-safe. Calling it from another thread actually works, +// but with an odd delay. +pub unsafe fn order_out_sync(ns_window: &NSWindow) { + let ns_window = MainThreadSafe(ns_window.retain()); + run_on_main(move || { + ns_window.orderOut(None); + }); +} + +// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread +// actually works, but with an odd delay. +pub unsafe fn make_key_and_order_front_sync(ns_window: &NSWindow) { + let ns_window = MainThreadSafe(ns_window.retain()); + run_on_main(move || { + ns_window.makeKeyAndOrderFront(None); + }); +} + +// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the +// window drag regions, which throws an exception when not done in the main +// thread +pub unsafe fn set_title_async(ns_window: &NSWindow, ns_view: &NSView, title: String) { + let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); + DispatchQueue::main().exec_async(move || { + let title = NSString::from_str(&title); + ns_window.setTitle(&title); + // `setTitle:` moves the traffic light buttons back to their default spot. + // Re-apply the configured inset kept on the view so the custom position + // survives a title change (#13044). + reapply_traffic_light_inset(&ns_window, &ns_view); + }); +} + +// `setFocus:` isn't thread-safe. +pub unsafe fn set_focus(ns_window: &NSWindow) { + let ns_window = MainThreadSafe(ns_window.retain()); + run_on_main(move || { + ns_window.makeKeyAndOrderFront(None); + let app: id = msg_send![class!(NSApplication), sharedApplication]; + let () = msg_send![app, activateIgnoringOtherApps: YES]; + }); +} + +// `close:` is thread-safe, but we want the event to be triggered from the main +// thread. Though, it's a good idea to look into that more... +pub unsafe fn close_async(ns_window: &NSWindow) { + let ns_window = MainThreadSafe(ns_window.retain()); + run_on_main(move || { + autoreleasepool(move |_| { + ns_window.close(); + }); + }); +} + +// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently. +pub unsafe fn set_ignore_mouse_events(ns_window: &NSWindow, ignore: bool) { + let ns_window = MainThreadSafe(ns_window.retain()); + DispatchQueue::main().exec_async(move || { + ns_window.setIgnoresMouseEvents(ignore); + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs new file mode 100644 index 000000000..4ecdce5ae --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs @@ -0,0 +1,177 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::platform_impl::platform::ffi::{id, nil, NO}; +use objc2::{ + msg_send, + rc::Retained, + runtime::{AnyObject, Sel}, + AllocAnyThread, +}; +use objc2_app_kit::NSImage; +use objc2_foundation::{ns_string, NSDictionary, NSPoint, NSString}; +use std::{ + cell::RefCell, + ffi::{c_void, CString}, + ptr::null_mut, +}; + +use crate::window::CursorIcon; + +#[derive(Default)] +pub enum Cursor { + #[default] + Default, + Native(&'static str), + Undocumented(&'static str), + WebKit(&'static str), +} + +impl From for Cursor { + fn from(cursor: CursorIcon) -> Self { + // See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc. + match cursor { + CursorIcon::Default => Cursor::Default, + CursorIcon::Arrow => Cursor::Native("arrowCursor"), + CursorIcon::Hand => Cursor::Native("pointingHandCursor"), + CursorIcon::Grab => Cursor::Native("openHandCursor"), + CursorIcon::Grabbing => Cursor::Native("closedHandCursor"), + CursorIcon::Text => Cursor::Native("IBeamCursor"), + CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), + CursorIcon::Copy => Cursor::Native("dragCopyCursor"), + CursorIcon::Alias => Cursor::Native("dragLinkCursor"), + CursorIcon::NotAllowed | CursorIcon::NoDrop => Cursor::Native("operationNotAllowedCursor"), + CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"), + CursorIcon::Crosshair => Cursor::Native("crosshairCursor"), + CursorIcon::EResize => Cursor::Native("resizeRightCursor"), + CursorIcon::NResize => Cursor::Native("resizeUpCursor"), + CursorIcon::WResize => Cursor::Native("resizeLeftCursor"), + CursorIcon::SResize => Cursor::Native("resizeDownCursor"), + CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"), + CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"), + + // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 + CursorIcon::Help => Cursor::Undocumented("_helpCursor"), + CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"), + CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"), + CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"), + CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"), + CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"), + CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"), + CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"), + CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"), + + // While these are available, the former just loads a white arrow, + // and the latter loads an ugly deflated beachball! + // CursorIcon::Move => Cursor::Undocumented("_moveCursor"), + // CursorIcon::Wait => Cursor::Undocumented("_waitCursor"), + + // An even more undocumented cursor... + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 + // This is the wrong semantics for `Wait`, but it's the same as + // what's used in Safari and Chrome. + CursorIcon::Wait | CursorIcon::Progress => Cursor::Undocumented("busyButClickableCursor"), + + // For the rest, we can just snatch the cursors from WebKit... + // They fit the style of the native cursors, and will seem + // completely standard to macOS users. + // https://stackoverflow.com/a/21786835/5435443 + CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"), + CursorIcon::Cell => Cursor::WebKit("cell"), + } + } +} + +impl Cursor { + pub unsafe fn load(&self) -> id { + match self { + Cursor::Default => null_mut(), + Cursor::Native(cursor_name) => { + let sel = Sel::register(&CString::new(*cursor_name).unwrap()); + msg_send![class!(NSCursor), performSelector: sel] + } + Cursor::Undocumented(cursor_name) => { + let class = class!(NSCursor); + let sel = Sel::register(&CString::new(*cursor_name).unwrap()); + let sel = if msg_send![class, respondsToSelector: sel] { + sel + } else { + warn!("Cursor `{}` appears to be invalid", cursor_name); + sel!(arrowCursor) + }; + msg_send![class, performSelector: sel] + } + Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name), + } + } +} + +// Note that loading `busybutclickable` with this code won't animate the frames; +// instead you'll just get them all in a column. +pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { + const CURSOR_ROOT: &str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"; + let cursor_root = ns_string!(CURSOR_ROOT); + let cursor_name = NSString::from_str(cursor_name); + let cursor_pdf = ns_string!("cursor.pdf"); + let cursor_plist = ns_string!("info.plist"); + let key_x = ns_string!("hotx"); + let key_y = ns_string!("hoty"); + + let cursor_path: Retained = + msg_send![cursor_root, stringByAppendingPathComponent: &*cursor_name]; + let pdf_path: Retained = + msg_send![&cursor_path, stringByAppendingPathComponent: &*cursor_pdf]; + let info_path: Retained = + msg_send![&cursor_path, stringByAppendingPathComponent: &*cursor_plist]; + + let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap(); + #[allow(deprecated)] + let info = + NSDictionary::::dictionaryWithContentsOfFile(&info_path).unwrap(); + let x = info.objectForKey(&key_x).unwrap(); + let y = info.objectForKey(&key_y).unwrap(); + let point = NSPoint::new(msg_send![&x, doubleValue], msg_send![&y, doubleValue]); + let cursor: id = msg_send![class!(NSCursor), alloc]; + msg_send![ + cursor, + initWithImage:&*image, + hotSpot:point, + ] +} + +pub unsafe fn invisible_cursor() -> id { + // 16x16 GIF data for invisible cursor + // You can reproduce this via ImageMagick. + // $ convert -size 16x16 xc:none cursor.gif + static CURSOR_BYTES: &[u8] = &[ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, + 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, + ]; + + thread_local! { + // We can't initialize this at startup. + static CURSOR_OBJECT: RefCell = const { RefCell::new(nil) }; + } + + CURSOR_OBJECT.with(|cursor_obj| { + if *cursor_obj.borrow() == nil { + // Create a cursor from `CURSOR_BYTES` + let cursor_data: id = msg_send![ + class!(NSData), + dataWithBytesNoCopy:CURSOR_BYTES.as_ptr().cast::(), + length:CURSOR_BYTES.len(), + freeWhenDone:NO, + ]; + + let ns_image: id = msg_send![class!(NSImage), alloc]; + let _: id = msg_send![ns_image, initWithData: cursor_data]; + let cursor: id = msg_send![class!(NSCursor), alloc]; + *cursor_obj.borrow_mut() = + msg_send![cursor, initWithImage:ns_image, hotSpot: NSPoint::new(0.0, 0.0)]; + } + *cursor_obj.borrow() + }) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs new file mode 100644 index 000000000..b9a39147e --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs @@ -0,0 +1,145 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +mod r#async; +mod cursor; + +pub use self::{cursor::*, r#async::*}; + +use std::ops::{BitAnd, Deref}; + +use core_graphics::display::CGDisplay; +use objc2::{ + class, + runtime::{AnyClass as Class, AnyObject as Object, Sel}, +}; +use objc2_app_kit::{NSApp, NSView, NSWindow, NSWindowStyleMask}; +use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSPoint, NSRange, NSRect, NSUInteger}; + +use crate::{ + dpi::{LogicalPosition, PhysicalPosition}, + error::ExternalError, + platform_impl::platform::ffi::{self, id, nil, BOOL, YES}, +}; + +// Replace with `!` once stable +#[derive(Debug)] +pub enum Never {} + +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + +pub const EMPTY_RANGE: NSRange = NSRange { + location: ffi::NSNotFound as NSUInteger, + length: 0, +}; + +#[derive(Debug, PartialEq)] +pub struct IdRef(id); + +impl IdRef { + pub fn new(inner: id) -> IdRef { + IdRef(inner) + } + + #[allow(dead_code)] + pub fn retain(inner: id) -> IdRef { + if inner != nil { + let _: id = unsafe { msg_send![inner, retain] }; + } + IdRef(inner) + } +} + +impl Drop for IdRef { + fn drop(&mut self) { + if self.0 != nil { + unsafe { + let _pool = NSAutoreleasePool::new(); + let () = msg_send![self.0, release]; + }; + } + } +} + +impl Deref for IdRef { + type Target = id; + #[allow(clippy::needless_lifetimes)] + fn deref<'a>(&'a self) -> &'a id { + &self.0 + } +} + +impl Clone for IdRef { + fn clone(&self) -> IdRef { + IdRef::retain(self.0) + } +} + +// For consistency with other platforms, this will... +// 1. translate the bottom-left window corner into the top-left window corner +// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one +pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { + CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) +} + +/// Converts from tao screen-coordinates to macOS screen-coordinates. +/// Tao: top-left is (0, 0) and y increasing downwards +/// macOS: bottom-left is (0, 0) and y increasing upwards +pub fn window_position(position: LogicalPosition) -> NSPoint { + NSPoint::new( + position.x, + CGDisplay::main().pixels_high() as f64 - position.y, + ) +} + +pub fn cursor_position() -> Result, ExternalError> { + let point: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] }; + let y = CGDisplay::main().pixels_high() as f64 - point.y; + let point = LogicalPosition::new(point.x, y); + Ok(point.to_physical(super::monitor::primary_monitor().scale_factor())) +} + +pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { + let superclass: *const Class = msg_send![this, superclass]; + &*superclass +} + +pub unsafe fn create_input_context(view: &NSView) -> IdRef { + let input_context: id = msg_send![class!(NSTextInputContext), alloc]; + let input_context: id = msg_send![input_context, initWithClient: view]; + IdRef::new(input_context) +} + +#[allow(dead_code)] +pub unsafe fn open_emoji_picker() { + // SAFETY: TODO + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + let () = msg_send![&NSApp(mtm), orderFrontCharacterPalette: nil]; +} + +pub extern "C" fn yes(_: &Object, _: Sel) -> BOOL { + YES +} + +pub unsafe fn toggle_style_mask( + window: &NSWindow, + view: &NSView, + mask: NSWindowStyleMask, + on: bool, +) { + let current_style_mask = window.styleMask(); + if on { + window.setStyleMask(current_style_mask | mask); + } else { + window.setStyleMask(current_style_mask & (!mask)); + } + + // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! + window.makeFirstResponder(Some(view)); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs new file mode 100644 index 000000000..4e4b6a231 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs @@ -0,0 +1,1197 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +#![allow(unused_unsafe)] +#![allow(deprecated)] // TODO: Use define_class! + +use std::{ + boxed::Box, + collections::{HashSet, VecDeque}, + ffi::CStr, + os::raw::*, + ptr, + sync::{Arc, Mutex, Weak}, +}; + +use objc2::{ + msg_send, + rc::Retained, + runtime::{ + AnyClass as Class, AnyObject as Object, AnyProtocol as Protocol, ClassBuilder as ClassDecl, Sel, + }, + AllocAnyThread, +}; +use objc2_app_kit::{ + NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow, NSWindowButton, +}; +use objc2_foundation::{ + ns_string, MainThreadMarker, NSAttributedString, NSInteger, NSMutableAttributedString, NSPoint, + NSRange, NSRect, NSSize, NSString, NSUInteger, +}; +use std::sync::LazyLock; + +use crate::{ + dpi::LogicalPosition, + event::{ + DeviceEvent, ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, + }, + keyboard::{KeyCode, ModifiersState}, + platform_impl::platform::{ + app_state::AppState, + event::{code_to_key, create_key_event, event_mods, get_scancode, EventWrapper}, + ffi::*, + util::{self}, + window::get_window_id, + DEVICE_ID, + }, + window::WindowId, +}; + +pub struct CursorState { + pub visible: bool, + pub cursor: util::Cursor, +} + +impl Default for CursorState { + fn default() -> Self { + Self { + visible: true, + cursor: Default::default(), + } + } +} + +pub(super) struct ViewState { + ns_window: objc2::rc::Weak, + pub cursor_state: Arc>, + ime_spot: Option<(f64, f64)>, + + /// This is true when we are currently modifying a marked text + /// using ime. When the text gets commited, this is set to false. + in_ime_preedit: bool, + + /// This is used to detect if a key-press causes an ime event. + /// If a key-press does not cause an ime event, that means + /// that the key-press cancelled the ime session. (Except arrow keys) + key_triggered_ime: bool, + // Not Needed Anymore + //raw_characters: Option, + is_key_down: bool, + pub(super) modifiers: ModifiersState, + phys_modifiers: HashSet, + tracking_rect: Option, + pub(super) traffic_light_inset: Option>, +} + +impl ViewState { + fn get_scale_factor(&self) -> f64 { + (unsafe { NSWindow::backingScaleFactor(&self.ns_window.load().unwrap()) }) as f64 + } +} + +pub fn new_view(ns_window: &NSWindow) -> (Option>, Weak>) { + let cursor_state = Default::default(); + let cursor_access = Arc::downgrade(&cursor_state); + let state = ViewState { + ns_window: objc2::rc::Weak::from(ns_window), + cursor_state, + ime_spot: None, + in_ime_preedit: false, + key_triggered_ime: false, + is_key_down: false, + modifiers: Default::default(), + phys_modifiers: Default::default(), + tracking_rect: None, + traffic_light_inset: None, + }; + unsafe { + // This is free'd in `dealloc` + let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; + let ns_view = msg_send![VIEW_CLASS.0, alloc]; + (msg_send![ns_view, initWithTao: state_ptr], cursor_access) + } +} + +pub unsafe fn set_ime_position(ns_view: &NSView, input_context: id, x: f64, y: f64) { + let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + let content_rect = NSWindow::contentRectForFrameRect( + &state.ns_window.load().unwrap(), + NSWindow::frame(&state.ns_window.load().unwrap()), + ); + let base_x = content_rect.origin.x as f64; + let base_y = (content_rect.origin.y + content_rect.size.height) as f64; + state.ime_spot = Some((base_x + x, base_y - y)); + let _: () = msg_send![input_context, invalidateCharacterCoordinates]; +} + +fn is_arrow_key(keycode: KeyCode) -> bool { + matches!( + keycode, + KeyCode::ArrowUp | KeyCode::ArrowDown | KeyCode::ArrowLeft | KeyCode::ArrowRight + ) +} + +struct ViewClass(&'static Class); +unsafe impl Send for ViewClass {} +unsafe impl Sync for ViewClass {} + +static VIEW_CLASS: LazyLock = LazyLock::new(|| unsafe { + let superclass = class!(NSView); + let mut decl = + ClassDecl::new(CStr::from_bytes_with_nul(b"TaoView\0").unwrap(), superclass).unwrap(); + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); + decl.add_method( + sel!(initWithTao:), + init_with_tao as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(viewDidMoveToWindow), + view_did_move_to_window as extern "C" fn(_, _), + ); + decl.add_method(sel!(drawRect:), draw_rect as extern "C" fn(_, _, _)); + decl.add_method( + sel!(acceptsFirstResponder), + accepts_first_responder as extern "C" fn(_, _) -> _, + ); + decl.add_method(sel!(touchBar), touch_bar as extern "C" fn(_, _) -> _); + decl.add_method( + sel!(resetCursorRects), + reset_cursor_rects as extern "C" fn(_, _), + ); + decl.add_method( + sel!(hasMarkedText), + has_marked_text as extern "C" fn(_, _) -> _, + ); + decl.add_method(sel!(markedRange), marked_range as extern "C" fn(_, _) -> _); + decl.add_method( + sel!(selectedRange), + selected_range as extern "C" fn(_, _) -> _, + ); + decl.add_method( + sel!(setMarkedText:selectedRange:replacementRange:), + set_marked_text as extern "C" fn(_, _, _, _, _), + ); + decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(_, _)); + decl.add_method( + sel!(validAttributesForMarkedText), + valid_attributes_for_marked_text as extern "C" fn(_, _) -> _, + ); + decl.add_method( + sel!(attributedSubstringForProposedRange:actualRange:), + attributed_substring_for_proposed_range as extern "C" fn(_, _, _, _) -> _, + ); + decl.add_method( + sel!(insertText:replacementRange:), + insert_text as extern "C" fn(_, _, _, _), + ); + decl.add_method( + sel!(characterIndexForPoint:), + character_index_for_point as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(firstRectForCharacterRange:actualRange:), + first_rect_for_character_range as extern "C" fn(_, _, _, _) -> _, + ); + decl.add_method( + sel!(doCommandBySelector:), + do_command_by_selector as extern "C" fn(_, _, _), + ); + decl.add_method(sel!(keyDown:), key_down as extern "C" fn(_, _, _)); + decl.add_method(sel!(keyUp:), key_up as extern "C" fn(_, _, _)); + decl.add_method(sel!(flagsChanged:), flags_changed as extern "C" fn(_, _, _)); + decl.add_method(sel!(insertTab:), insert_tab as extern "C" fn(_, _, _)); + decl.add_method( + sel!(insertBackTab:), + insert_back_tab as extern "C" fn(_, _, _), + ); + decl.add_method(sel!(mouseDown:), mouse_down as extern "C" fn(_, _, _)); + decl.add_method(sel!(mouseUp:), mouse_up as extern "C" fn(_, _, _)); + decl.add_method( + sel!(rightMouseDown:), + right_mouse_down as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(rightMouseUp:), + right_mouse_up as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(otherMouseDown:), + other_mouse_down as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(otherMouseUp:), + other_mouse_up as extern "C" fn(_, _, _), + ); + decl.add_method(sel!(mouseMoved:), mouse_moved as extern "C" fn(_, _, _)); + decl.add_method(sel!(mouseDragged:), mouse_dragged as extern "C" fn(_, _, _)); + decl.add_method( + sel!(rightMouseDragged:), + right_mouse_dragged as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(otherMouseDragged:), + other_mouse_dragged as extern "C" fn(_, _, _), + ); + decl.add_method(sel!(mouseEntered:), mouse_entered as extern "C" fn(_, _, _)); + decl.add_method(sel!(mouseExited:), mouse_exited as extern "C" fn(_, _, _)); + decl.add_method(sel!(scrollWheel:), scroll_wheel as extern "C" fn(_, _, _)); + decl.add_method( + sel!(pressureChangeWithEvent:), + pressure_change_with_event as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(_wantsKeyDownForEvent:), + wants_key_down_for_event as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(cancelOperation:), + cancel_operation as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(frameDidChange:), + frame_did_change as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(acceptsFirstMouse:), + accepts_first_mouse as extern "C" fn(_, _, _) -> _, + ); + decl.add_ivar::<*mut c_void>(CStr::from_bytes_with_nul(b"taoState\0").unwrap()); + decl.add_ivar::(CStr::from_bytes_with_nul(b"markedText\0").unwrap()); + let protocol = Protocol::get(CStr::from_bytes_with_nul(b"NSTextInputClient\0").unwrap()).unwrap(); + decl.add_protocol(protocol); + ViewClass(decl.register()) +}); + +extern "C" fn dealloc(this: &Object, _sel: Sel) { + unsafe { + let state: *mut c_void = *this.get_ivar("taoState"); + let marked_text: *mut NSMutableAttributedString = *this.get_ivar("markedText"); + let _: () = msg_send![marked_text, release]; + drop(Box::from_raw(state as *mut ViewState)); + } +} + +extern "C" fn init_with_tao(this: &Object, _sel: Sel, state: *mut c_void) -> id { + unsafe { + let this: id = msg_send![this, init]; + if this != nil { + *(*this).get_mut_ivar("taoState") = state; + let marked_text = Retained::into_raw(NSMutableAttributedString::new()); + *(*this).get_mut_ivar("markedText") = marked_text; + let _: () = msg_send![this, setPostsFrameChangedNotifications: YES]; + + let notification_center: &Object = msg_send![class!(NSNotificationCenter), defaultCenter]; + let notification_name = ns_string!("NSViewFrameDidChangeNotification"); + let _: () = msg_send![ + notification_center, + addObserver: this + selector: sel!(frameDidChange:) + name: &*notification_name + object: this + ]; + } + this + } +} + +extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) { + trace!("Triggered `viewDidMoveToWindow`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![this, removeTrackingRect: tracking_rect]; + } + + let rect: NSRect = msg_send![this, visibleRect]; + let tracking_rect: NSInteger = msg_send![this, + addTrackingRect:rect + owner:this + userData:ptr::null_mut::() + assumeInside:NO + ]; + state.tracking_rect = Some(tracking_rect); + } + trace!("Completed `viewDidMoveToWindow`"); +} + +extern "C" fn frame_did_change(this: &Object, _sel: Sel, _event: id) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![this, removeTrackingRect: tracking_rect]; + } + + let rect: NSRect = msg_send![this, visibleRect]; + let tracking_rect: NSInteger = msg_send![this, + addTrackingRect:rect + owner:this + userData:ptr::null_mut::() + assumeInside:NO + ]; + + state.tracking_rect = Some(tracking_rect); + } +} + +extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(position) = state.traffic_light_inset { + let window = state.ns_window.load().unwrap(); + inset_traffic_lights(&window, position); + } + + AppState::handle_redraw(WindowId(get_window_id(&state.ns_window.load().unwrap()))); + + let superclass = util::superclass(this); + let () = msg_send![super(this, superclass), drawRect: rect]; + } +} + +extern "C" fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { + YES +} + +// This is necessary to prevent a beefy terminal error on MacBook Pros: +// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem +// TODO: Add an API extension for using `NSTouchBar` +extern "C" fn touch_bar(_this: &Object, _sel: Sel) -> id { + nil +} + +extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let bounds: NSRect = msg_send![this, bounds]; + let cursor_state = state.cursor_state.lock().unwrap(); + let cursor = if cursor_state.visible { + cursor_state.cursor.load() + } else { + util::invisible_cursor() + }; + + if !cursor.is_null() { + let _: () = msg_send![this, + addCursorRect:bounds + cursor:cursor + ]; + } + } +} + +extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { + unsafe { + trace!("Triggered `hasMarkedText`"); + let marked_text: &NSMutableAttributedString = *this.get_ivar("markedText"); + trace!("Completed `hasMarkedText`"); + (marked_text.length() > 0).into() + } +} + +extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange { + unsafe { + trace!("Triggered `markedRange`"); + let marked_text: &NSMutableAttributedString = *this.get_ivar("markedText"); + let length = marked_text.length(); + trace!("Completed `markedRange`"); + if length > 0 { + NSRange::new(0, length - 1) + } else { + util::EMPTY_RANGE + } + } +} + +extern "C" fn selected_range(_this: &Object, _sel: Sel) -> NSRange { + trace!("Triggered `selectedRange`"); + trace!("Completed `selectedRange`"); + util::EMPTY_RANGE +} + +/// An IME pre-edit operation happened, changing the text that's +/// currently being pre-edited. This more or less corresponds to +/// the `compositionupdate` event on the web. +extern "C" fn set_marked_text( + this: &mut Object, + _sel: Sel, + string: id, + _selected_range: NSRange, + _replacement_range: NSRange, +) { + trace!("Triggered `setMarkedText`"); + unsafe { + let has_attr: bool = msg_send![string, isKindOfClass: class!(NSAttributedString)]; + let marked_text = if has_attr { + NSMutableAttributedString::initWithAttributedString( + NSMutableAttributedString::alloc(), + &*(string as *const NSAttributedString), + ) + } else { + NSMutableAttributedString::initWithString( + NSMutableAttributedString::alloc(), + &*(string as *const NSString), + ) + }; + let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); + let () = msg_send![(*marked_text_ref), release]; + *marked_text_ref = Retained::into_raw(marked_text); + + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + state.in_ime_preedit = true; + state.key_triggered_ime = true; + } + trace!("Completed `setMarkedText`"); +} + +extern "C" fn unmark_text(this: &mut Object, _sel: Sel) { + trace!("Triggered `unmarkText`"); + unsafe { + let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); + let () = msg_send![(*marked_text_ref), release]; + *marked_text_ref = Retained::into_raw(NSMutableAttributedString::new()); + let input_context: id = msg_send![this, inputContext]; + let _: () = msg_send![input_context, discardMarkedText]; + } + trace!("Completed `unmarkText`"); +} + +extern "C" fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id { + trace!("Triggered `validAttributesForMarkedText`"); + trace!("Completed `validAttributesForMarkedText`"); + unsafe { msg_send![class!(NSArray), array] } +} + +extern "C" fn attributed_substring_for_proposed_range( + _this: &Object, + _sel: Sel, + _range: NSRange, + _actual_range: *mut c_void, // *mut NSRange +) -> id { + trace!("Triggered `attributedSubstringForProposedRange`"); + trace!("Completed `attributedSubstringForProposedRange`"); + nil +} + +extern "C" fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger { + trace!("Triggered `characterIndexForPoint`"); + trace!("Completed `characterIndexForPoint`"); + 0 +} + +extern "C" fn first_rect_for_character_range( + this: &Object, + _sel: Sel, + _range: NSRange, + _actual_range: *mut c_void, // *mut NSRange +) -> NSRect { + unsafe { + trace!("Triggered `firstRectForCharacterRange`"); + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + let (x, y) = state.ime_spot.unwrap_or_else(|| { + let content_rect = NSWindow::contentRectForFrameRect( + &state.ns_window.load().unwrap(), + NSWindow::frame(&state.ns_window.load().unwrap()), + ); + let x = content_rect.origin.x; + let y = util::bottom_left_to_top_left(content_rect); + (x, y) + }); + trace!("Completed `firstRectForCharacterRange`"); + NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) + } +} + +extern "C" fn insert_text( + this: &Object, + _sel: Sel, + string: &NSString, + _replacement_range: NSRange, +) { + trace!("Triggered `insertText`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let has_attr: bool = msg_send![string, isKindOfClass: class!(NSAttributedString)]; + let characters = if has_attr { + // This is a *mut NSAttributedString + msg_send![string, string] + } else { + // This is already a *mut NSString + string + }; + + let string: String = characters + .to_string() + .chars() + .filter(|c| !is_corporate_character(*c)) + .collect(); + state.is_key_down = true; + + // We don't need this now, but it's here if that changes. + //let event: id = msg_send![NSApp(), currentEvent]; + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::ReceivedImeText(string), + })); + if state.in_ime_preedit { + state.in_ime_preedit = false; + state.key_triggered_ime = true; + } + } + trace!("Completed `insertText`"); +} + +extern "C" fn do_command_by_selector(_this: &Object, _sel: Sel, _command: Sel) { + trace!("Triggered `doCommandBySelector`"); + // TODO: (Artur) all these inputs seem to trigger a key event with the correct text + // content so this is not needed anymore, it seems. + + // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character + // happens, i.e. newlines, tabs, and Ctrl+C. + + // unsafe { + // let state_ptr: *mut c_void = *this.get_ivar("taoState"); + // let state = &mut *(state_ptr as *mut ViewState); + + // let mut events = VecDeque::with_capacity(1); + // if command == sel!(insertNewline:) { + // // The `else` condition would emit the same character, but I'm keeping this here both... + // // 1) as a reminder for how `doCommandBySelector` works + // // 2) to make our use of carriage return explicit + // events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { + // window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + // event: WindowEvent::ReceivedCharacter('\r'), + // })); + // } else { + // let raw_characters = state.raw_characters.take(); + // if let Some(raw_characters) = raw_characters { + // for character in raw_characters + // .chars() + // .filter(|c| !is_corporate_character(*c)) + // { + // events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { + // window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + // event: WindowEvent::ReceivedCharacter(character), + // })); + // } + // } + // }; + // AppState::queue_events(events); + // } + + trace!("Completed `doCommandBySelector`"); +} + +// Get characters +// fn get_characters(event: id, ignore_modifiers: bool) -> String { +// unsafe { +// let characters: id = if ignore_modifiers { +// msg_send![event, charactersIgnoringModifiers] +// } else { +// msg_send![event, characters] +// }; + +// assert_ne!(characters, nil); +// let slice = slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len()); + +// let string = str::from_utf8_unchecked(slice); +// string.to_owned() +// } +// } + +// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT +fn is_corporate_character(c: char) -> bool { + #[allow(clippy::match_like_matches_macro)] + match c { + '\u{F700}'..='\u{F747}' + | '\u{F802}'..='\u{F84F}' + | '\u{F850}' + | '\u{F85C}' + | '\u{F85D}' + | '\u{F85F}' + | '\u{F860}'..='\u{F86B}' + | '\u{F870}'..='\u{F8FF}' => true, + _ => false, + } +} + +// // Retrieves a layout-independent keycode given an event. +// fn retrieve_keycode(event: id) -> Option { +// #[inline] +// fn get_code(ev: id, raw: bool) -> Option { +// let characters = get_characters(ev, raw); +// characters.chars().next().and_then(|c| char_to_keycode(c)) +// } + +// // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. +// // If we don't get a match, then we fall back to unmodified characters. +// let code = get_code(event, false).or_else(|| get_code(event, true)); + +// // We've checked all layout related keys, so fall through to scancode. +// // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). +// // +// // We're additionally checking here for F21-F24 keys, since their keycode +// // can vary, but we know that they are encoded +// // in characters property. +// code.or_else(|| { +// let scancode = get_scancode(event); +// scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true))) +// }) +// } + +// Update `state.modifiers` if `event` has something different +fn update_potentially_stale_modifiers(state: &mut ViewState, event: &NSEvent) { + let event_modifiers = event_mods(event); + if state.modifiers != event_modifiers { + state.modifiers = event_modifiers; + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::ModifiersChanged(state.modifiers), + })); + } +} + +extern "C" fn key_down(this: &mut Object, _sel: Sel, event: &NSEvent) { + trace!("Triggered `keyDown`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + let window_id = WindowId(get_window_id(&state.ns_window.load().unwrap())); + + // keyboard refactor: don't seems to be needed anymore + // let characters = get_characters(event, false); + //state.raw_characters = Some(characters); + + let is_repeat: bool = msg_send![event, isARepeat]; + + update_potentially_stale_modifiers(state, event); + + let pass_along = !is_repeat || !state.is_key_down; + if pass_along { + // See below for why we do this. + let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); + let () = msg_send![(*marked_text_ref), release]; + *marked_text_ref = Retained::into_raw(NSMutableAttributedString::new()); + state.key_triggered_ime = false; + + // Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do... + // So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some + // keys to generate twice as many characters. + let array: id = msg_send![class!(NSArray), arrayWithObject: event]; + let () = msg_send![&*this, interpretKeyEvents: array]; + } + // The `interpretKeyEvents` above, may invoke `set_marked_text` or `insert_text`, + // if the event corresponds to an IME event. + let in_ime = state.key_triggered_ime; + let key_event = create_key_event(event, true, is_repeat, in_ime, None); + let is_arrow_key = is_arrow_key(key_event.physical_key); + if pass_along { + // The `interpretKeyEvents` above, may invoke `set_marked_text` or `insert_text`, + // if the event corresponds to an IME event. + // If `set_marked_text` or `insert_text` were not invoked, then the IME was deactivated, + // and we should cancel the IME session. + // When using arrow keys in an IME window, the input context won't invoke the + // IME related methods, so in that case we shouldn't cancel the IME session. + let is_preediting: bool = state.in_ime_preedit; + if is_preediting && !state.key_triggered_ime && !is_arrow_key { + // In this case we should cancel the IME session. + let () = msg_send![this, unmarkText]; + state.in_ime_preedit = false; + } + } + let window_event = Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: key_event, + is_synthetic: false, + }, + }; + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `keyDown`"); +} + +extern "C" fn key_up(this: &Object, _sel: Sel, event: &NSEvent) { + trace!("Triggered `keyUp`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + state.is_key_down = false; + + update_potentially_stale_modifiers(state, event); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: create_key_event(event, false, false, false, None), + is_synthetic: false, + }, + }; + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `keyUp`"); +} + +extern "C" fn flags_changed(this: &Object, _sel: Sel, ns_event: &NSEvent) { + use KeyCode::{ + AltLeft, AltRight, ControlLeft, ControlRight, ShiftLeft, ShiftRight, SuperLeft, SuperRight, + }; + + trace!("Triggered `flagsChanged`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let scancode = get_scancode(ns_event); + + // We'll correct the `is_press` and the `key_override` below. + let event = create_key_event(ns_event, false, false, false, Some(KeyCode::SuperLeft)); + let mut events = VecDeque::with_capacity(4); + + macro_rules! process_event { + ($tao_flag:expr, $ns_flag:expr, $target_key:expr) => { + let ns_event_contains_keymask = NSEvent::modifierFlags(ns_event).contains($ns_flag); + let mut actual_key = KeyCode::from_scancode(scancode as u32); + let was_pressed = state.phys_modifiers.contains(&actual_key); + let mut is_pressed = ns_event_contains_keymask; + let matching_keys; + if actual_key == KeyCode::KeyA { + // When switching keyboard layout using Ctrl+Space, the Ctrl release event + // has `KeyA` as its keycode which would produce an incorrect key event. + // To avoid this, we detect this scenario and override the key with one + // that should be reasonable + actual_key = $target_key; + matching_keys = true; + } else { + matching_keys = actual_key == $target_key; + if matching_keys && is_pressed && was_pressed { + // If we received a modifier flags changed event AND the key that triggered + // it was already pressed then this must mean that this event indicates the + // release of that key. + is_pressed = false; + } + } + if matching_keys { + if is_pressed != was_pressed { + let mut event = event.clone(); + event.state = if is_pressed { + ElementState::Pressed + } else { + ElementState::Released + }; + event.physical_key = actual_key; + event.logical_key = code_to_key(event.physical_key, scancode); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + if is_pressed { + state.phys_modifiers.insert($target_key); + } else { + state.phys_modifiers.remove(&$target_key); + } + if ns_event_contains_keymask { + state.modifiers.insert($tao_flag); + } else { + state.modifiers.remove($tao_flag); + } + } + } + }; + } + process_event!( + ModifiersState::SHIFT, + NSEventModifierFlags::Shift, + ShiftLeft + ); + process_event!( + ModifiersState::SHIFT, + NSEventModifierFlags::Shift, + ShiftRight + ); + process_event!( + ModifiersState::CONTROL, + NSEventModifierFlags::Control, + ControlLeft + ); + process_event!( + ModifiersState::CONTROL, + NSEventModifierFlags::Control, + ControlRight + ); + process_event!(ModifiersState::ALT, NSEventModifierFlags::Option, AltLeft); + process_event!(ModifiersState::ALT, NSEventModifierFlags::Option, AltRight); + process_event!( + ModifiersState::SUPER, + NSEventModifierFlags::Command, + SuperLeft + ); + process_event!( + ModifiersState::SUPER, + NSEventModifierFlags::Command, + SuperRight + ); + + let window_id = WindowId(get_window_id(&state.ns_window.load().unwrap())); + + for event in events { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event, + })); + } + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(state.modifiers), + })); + } + trace!("Completed `flagsChanged`"); +} + +extern "C" fn insert_tab(this: &Object, _sel: Sel, _sender: id) { + unsafe { + let window: id = msg_send![this, window]; + let first_responder: id = msg_send![window, firstResponder]; + let this_ptr = this as *const _ as *mut _; + if first_responder == this_ptr { + let (): _ = msg_send![window, selectNextKeyView: this]; + } + } +} + +extern "C" fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { + unsafe { + let window: id = msg_send![this, window]; + let first_responder: id = msg_send![window, firstResponder]; + let this_ptr = this as *const _ as *mut _; + if first_responder == this_ptr { + let (): _ = msg_send![window, selectPreviousKeyView: this]; + } + } +} + +// Allows us to receive Cmd-. (the shortcut for closing a dialog) +// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 +extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { + trace!("Triggered `cancelOperation`"); + unsafe { + let mtm = MainThreadMarker::new_unchecked(); + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let event: Retained = msg_send![&NSApp(mtm), currentEvent]; + update_potentially_stale_modifiers(state, &event); + + let scancode = 0x2f; + let key = KeyCode::from_scancode(scancode); + debug_assert_eq!(key, KeyCode::Period); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: create_key_event(&event, true, false, false, Some(key)), + is_synthetic: false, + }, + }; + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `cancelOperation`"); +} + +fn mouse_click(this: &Object, event: &NSEvent, button: MouseButton, button_state: ElementState) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + update_potentially_stale_modifiers(state, event); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::MouseInput { + device_id: DEVICE_ID, + state: button_state, + button, + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } +} + +extern "C" fn mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Left, ElementState::Pressed); +} + +extern "C" fn mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Left, ElementState::Released); +} + +extern "C" fn right_mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Right, ElementState::Pressed); +} + +extern "C" fn right_mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Right, ElementState::Released); +} + +extern "C" fn other_mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Middle, ElementState::Pressed); +} + +extern "C" fn other_mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); + mouse_click(this, event, MouseButton::Middle, ElementState::Released); +} + +fn mouse_motion(this: &NSView, event: &NSEvent) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_point = event.locationInWindow(); + let view_point = this.convertPoint_fromView(window_point, None); + let view_rect = NSView::frame(this); + + if view_point.x.is_sign_negative() + || view_point.y.is_sign_negative() + || view_point.x > view_rect.size.width + || view_point.y > view_rect.size.height + { + let mouse_buttons_down: NSUInteger = msg_send![class!(NSEvent), pressedMouseButtons]; + if mouse_buttons_down == 0 { + // Point is outside of the client area (view) and no buttons are pressed + return; + } + } + + let x = view_point.x as f64; + let y = view_rect.size.height as f64 - view_point.y as f64; + let logical_position = LogicalPosition::new(x, y); + + update_potentially_stale_modifiers(state, event); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: logical_position.to_physical(state.get_scale_factor()), + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } +} + +extern "C" fn mouse_moved(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); +} + +extern "C" fn mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); +} + +extern "C" fn right_mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); +} + +extern "C" fn other_mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { + mouse_motion(this, event); +} + +extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) { + trace!("Triggered `mouseEntered`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let enter_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::CursorEntered { + device_id: DEVICE_ID, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(enter_event)); + } + trace!("Completed `mouseEntered`"); +} + +extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) { + trace!("Triggered `mouseExited`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::CursorLeft { + device_id: DEVICE_ID, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `mouseExited`"); +} + +extern "C" fn scroll_wheel(this: &NSView, _sel: Sel, event: &NSEvent) { + trace!("Triggered `scrollWheel`"); + + mouse_motion(this, event); + + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let delta = { + // macOS horizontal sign convention is the inverse of tao. + let (x, y) = (event.scrollingDeltaX() * -1.0, event.scrollingDeltaY()); + if event.hasPreciseScrollingDeltas() { + let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); + MouseScrollDelta::PixelDelta(delta) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; + let phase = match event.phase() { + NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, + NSEventPhase::Ended => TouchPhase::Ended, + _ => TouchPhase::Moved, + }; + + let device_event = Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseWheel { delta }, + }; + + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + update_potentially_stale_modifiers(state, event); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(device_event)); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `scrollWheel`"); +} + +extern "C" fn pressure_change_with_event(this: &NSView, _sel: Sel, event: &NSEvent) { + trace!("Triggered `pressureChangeWithEvent`"); + + mouse_motion(this, event); + + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + + let pressure = event.pressure(); + let stage = event.stage(); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage: stage as i64, + }, + }; + + AppState::queue_event(EventWrapper::StaticEvent(window_event)); + } + trace!("Completed `pressureChangeWithEvent`"); +} + +// Allows us to receive Ctrl-Tab and Ctrl-Esc. +// Note that this *doesn't* help with any missing Cmd inputs. +// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 +extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL { + YES +} + +extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL { + YES +} + +pub unsafe fn inset_traffic_lights(window: &NSWindow, position: LogicalPosition) { + let (x, y) = (position.x, position.y); + + let close = window + .standardWindowButton(NSWindowButton::CloseButton) + .unwrap(); + let miniaturize = window + .standardWindowButton(NSWindowButton::MiniaturizeButton) + .unwrap(); + let zoom = window + .standardWindowButton(NSWindowButton::ZoomButton) + .unwrap(); + + let title_bar_container_view = close.superview().unwrap().superview().unwrap(); + + let close_rect = NSView::frame(&close); + let title_bar_frame_height = close_rect.size.height + y; + let mut title_bar_rect = NSView::frame(&title_bar_container_view); + title_bar_rect.size.height = title_bar_frame_height; + title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height; + let _: () = msg_send![&title_bar_container_view, setFrame: title_bar_rect]; + + let window_buttons = vec![close, miniaturize.clone(), zoom]; + let space_between = NSView::frame(&miniaturize).origin.x - close_rect.origin.x; + + for (i, button) in window_buttons.into_iter().enumerate() { + let mut rect = NSView::frame(&button); + rect.origin.x = x + (i as f64 * space_between); + button.setFrameOrigin(rect.origin); + } +} + +// Re-apply the custom traffic light inset stored on the view, if one was set. +// AppKit resets the buttons to their default position on some events (title +// change, leaving fullscreen) without triggering a `drawRect:`, so callers use +// this to restore the configured position afterwards (#13044, #15451). +pub unsafe fn reapply_traffic_light_inset(ns_window: &NSWindow, ns_view: &NSView) { + let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); + if state_ptr.is_null() { + return; + } + let state = &*(state_ptr as *mut ViewState); + if let Some(position) = state.traffic_light_inset { + inset_traffic_lights(ns_window, position); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs new file mode 100644 index 000000000..f910c957d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs @@ -0,0 +1,1791 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +#![allow(unused_unsafe)] + +use std::{ + collections::VecDeque, + convert::TryInto, + f64, + ffi::CStr, + os::raw::c_void, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, Weak, + }, +}; + +use crate::{ + dpi::{ + LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, + }, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + icon::Icon, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform::macos::WindowExtMacOS, + platform_impl::{ + platform::{ + app_state::AppState, + ffi, + monitor::{self, MonitorHandle, VideoMode}, + util::{self, IdRef}, + view::{self, new_view, CursorState}, + window_delegate::new_delegate, + OsError, + }, + set_badge_label, set_progress_indicator, + }, + window::{ + CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, + WindowAttributes, WindowId as RootWindowId, WindowSizeConstraints, + }, +}; +use core_graphics::{ + base::CGFloat, + display::{CGDisplay, CGDisplayMode}, + geometry::CGPoint, +}; +use objc2::{ + msg_send, + rc::Retained, + runtime::{AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel}, +}; +use objc2_app_kit::{ + self as appkit, NSApp, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSEvent, + NSEventModifierFlags, NSEventSubtype, NSEventType, NSRequestUserAttentionType, NSScreen, NSView, + NSWindow, NSWindowButton, NSWindowCollectionBehavior, NSWindowFullScreenButton, + NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, +}; +use objc2_foundation::{ + ns_string, MainThreadMarker, NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, + NSRect, NSSize, NSString, NSTimeInterval, NSUInteger, +}; +use std::sync::LazyLock; + +use super::{ + ffi::{id, nil, NO}, + view::ViewState, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(pub usize); + +impl Id { + pub unsafe fn dummy() -> Self { + Id(0) + } +} + +// Convert the `NSWindow` associated with a window to a usize to use as a unique identifier +// for the window. +pub fn get_window_id(window_cocoa_id: &NSWindow) -> Id { + Id(window_cocoa_id as *const NSWindow as _) +} + +#[non_exhaustive] +#[derive(Clone)] +pub enum Parent { + None, + ChildOf(*mut c_void), +} + +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub parent: Parent, + pub movable_by_window_background: bool, + pub titlebar_transparent: bool, + pub title_hidden: bool, + pub titlebar_hidden: bool, + pub titlebar_buttons_hidden: bool, + pub fullsize_content_view: bool, + pub resize_increments: Option>, + pub disallow_hidpi: bool, + pub has_shadow: bool, + pub traffic_light_inset: Option, + pub automatic_tabbing: bool, + pub tabbing_identifier: Option, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + #[inline] + fn default() -> Self { + Self { + parent: Parent::None, + movable_by_window_background: false, + titlebar_transparent: false, + title_hidden: false, + titlebar_hidden: false, + titlebar_buttons_hidden: false, + fullsize_content_view: false, + resize_increments: None, + disallow_hidpi: false, + has_shadow: true, + traffic_light_inset: None, + automatic_tabbing: true, + tabbing_identifier: None, + } + } +} + +unsafe fn create_view( + ns_window: &NSWindow, + pl_attribs: &PlatformSpecificWindowBuilderAttributes, +) -> Option<(Retained, Weak>)> { + let (ns_view, cursor_state) = new_view(ns_window); + ns_view.map(|ns_view| { + if !pl_attribs.disallow_hidpi { + #[allow(deprecated)] + ns_view.setWantsBestResolutionOpenGLSurface(true); + } + + #[allow(deprecated)] // TODO: Use define_class! + if let Some(position) = pl_attribs.traffic_light_inset { + let state_ptr: *mut c_void = *(**ns_view).get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + let scale_factor = NSWindow::backingScaleFactor(ns_window); + let position = position.to_logical(scale_factor); + state.traffic_light_inset = Some(position); + } + + // On Mojave, views automatically become layer-backed shortly after being added to + // a window. Changing the layer-backedness of a view breaks the association between + // the view and its associated OpenGL context. To work around this, on Mojave we + // explicitly make the view layer-backed up front so that AppKit doesn't do it + // itself and break the association with its context. + if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { + ns_view.setWantsLayer(true); + } + + (ns_view, cursor_state) + }) +} + +fn create_window( + attrs: &WindowAttributes, + pl_attrs: &PlatformSpecificWindowBuilderAttributes, +) -> Option> { + unsafe { + let mtm = MainThreadMarker::new_unchecked(); + let _pool = NSAutoreleasePool::new(); + let screen = match attrs.fullscreen { + Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor }))) + | Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + })) => { + let monitor_screen = monitor.ns_screen(); + Some(monitor_screen.unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap())) + } + Some(Fullscreen::Borderless(None)) => Some( + attrs + .position + .and_then(screen_from_position) + .unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap()), + ), + None => None, + }; + let frame = match &screen { + Some(screen) => NSScreen::frame(screen), + None => { + let screen = attrs + .position + .and_then(screen_from_position) + .unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap()); + let scale_factor = NSScreen::backingScaleFactor(&screen) as f64; + let desired_size = attrs + .inner_size + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); + let (width, height): (f64, f64) = attrs + .inner_size_constraints + .clamp(desired_size, scale_factor) + .to_logical::(scale_factor) + .into(); + let (left, bottom) = match attrs.position { + Some(position) => { + let logical = util::window_position(position.to_logical(scale_factor)); + // macOS wants the position of the bottom left corner, + // but caller is setting the position of top left corner + (logical.x, logical.y - height) + } + // This value is ignored by calling win.center() below + None => (0.0, 0.0), + }; + NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) + } + }; + + let mut masks = if !attrs.decorations && screen.is_none() || pl_attrs.titlebar_hidden { + // Resizable UnownedWindow without a titlebar or borders + // if decorations is set to false, ignore pl_attrs + NSWindowStyleMask::Borderless + | NSWindowStyleMask::Resizable + | NSWindowStyleMask::Miniaturizable + } else { + // default case, resizable window with titlebar and titlebar buttons + NSWindowStyleMask::Closable + | NSWindowStyleMask::Miniaturizable + | NSWindowStyleMask::Resizable + | NSWindowStyleMask::Titled + }; + + if !attrs.resizable { + masks &= !NSWindowStyleMask::Resizable; + } + + if !attrs.minimizable { + masks &= !NSWindowStyleMask::Miniaturizable; + } + + if !attrs.closable { + masks &= !NSWindowStyleMask::Closable; + } + + if pl_attrs.fullsize_content_view { + masks |= NSWindowStyleMask::FullSizeContentView; + } + + let ns_window: id = msg_send![WINDOW_CLASS.0, alloc]; + let ns_window_ptr: id = msg_send![ + ns_window, + initWithContentRect: frame, + styleMask: masks, + backing: NSBackingStoreType::Buffered, + defer: NO, + ]; + + Retained::retain(ns_window_ptr).and_then(|r| r.downcast::().ok()).map(|ns_window| { + #[allow(deprecated)] + { + *((*ns_window_ptr).get_mut_ivar::("focusable")) = attrs.focusable.into(); + } + + let title = NSString::from_str(&attrs.title); + ns_window.setReleasedWhenClosed(false); + ns_window.setTitle(&title); + ns_window.setAcceptsMouseMovedEvents(true); + + if pl_attrs.titlebar_transparent { + ns_window.setTitlebarAppearsTransparent(true); + } + if pl_attrs.title_hidden { + ns_window.setTitleVisibility(appkit::NSWindowTitleVisibility::Hidden); + } + if pl_attrs.titlebar_buttons_hidden { + for titlebar_button in &[ + NSWindowFullScreenButton, + NSWindowButton::MiniaturizeButton, + NSWindowButton::CloseButton, + NSWindowButton::ZoomButton, + ] { + if let Some(button) = ns_window.standardWindowButton(*titlebar_button) { + button.setHidden(true); + } + } + } + if pl_attrs.movable_by_window_background { + ns_window.setMovableByWindowBackground(true); + } + + if attrs.always_on_top { + ns_window.setLevel(ffi::NSWindowLevel::NSFloatingWindowLevel as isize); + } + + if attrs.always_on_bottom { + ns_window.setLevel(ffi::NSWindowLevel::BelowNormalWindowLevel as isize); + } + + if attrs.content_protection { + ns_window.setSharingType(NSWindowSharingType::None); + } + + if !attrs.maximizable { + if let Some(button) = ns_window + .standardWindowButton(NSWindowButton::ZoomButton) { + button.setEnabled(false); + } + } + + if let Some(increments) = pl_attrs.resize_increments { + let (x, y) = (increments.width, increments.height); + if x >= 1.0 && y >= 1.0 { + let size = NSSize::new(x as CGFloat, y as CGFloat); + ns_window.setResizeIncrements(size); + } + } + + if let Parent::ChildOf(parent) = pl_attrs.parent { + let _: () = + msg_send![parent as id, addChildWindow: &*ns_window, ordered: NSWindowOrderingMode::Above]; + } + + if !pl_attrs.automatic_tabbing { + NSWindow::setAllowsAutomaticWindowTabbing(false, mtm); + } + + if let Some(tabbing_identifier) = &pl_attrs.tabbing_identifier { + ns_window.setTabbingIdentifier( &NSString::from_str(tabbing_identifier)); + } + + if !pl_attrs.has_shadow { + ns_window.setHasShadow(false); + } + if attrs.position.is_none() { + ns_window.center(); + } + + ns_window + }) + } +} + +fn screen_from_position(position: Position) -> Option> { + for m in monitor::available_monitors() { + let monitor_pos = m.position(); + let monitor_size = m.size(); + + // type annotations required for 32bit targets. + let window_position = position.to_physical::(m.scale_factor()); + + let is_in_monitor = monitor_pos.x <= window_position.x + && window_position.x < monitor_pos.x + monitor_size.width as i32 + && monitor_pos.y <= window_position.y + && window_position.y < monitor_pos.y + monitor_size.height as i32; + if is_in_monitor { + return m.ns_screen(); + } + } + None +} + +pub(super) fn get_ns_theme() -> Theme { + unsafe { + let appearances: Vec> = vec![ + NSString::from_str("NSAppearanceNameAqua"), + NSString::from_str("NSAppearanceNameDarkAqua"), + ]; + let app_class = class!(NSApplication); + let app: id = msg_send![app_class, sharedApplication]; + let has_theme: bool = msg_send![app, respondsToSelector: sel!(effectiveAppearance)]; + if !has_theme { + return Theme::Light; + } + let appearance: id = msg_send![app, effectiveAppearance]; + let name: Retained = msg_send![ + appearance, + bestMatchFromAppearancesWithNames: &*NSArray::from_retained_slice(&appearances) + ]; + let name = name.to_string(); + match &*name { + "NSAppearanceNameDarkAqua" => Theme::Dark, + _ => Theme::Light, + } + } +} + +pub(super) fn set_ns_theme(theme: Option) { + unsafe { + let app_class = class!(NSApplication); + let app: id = msg_send![app_class, sharedApplication]; + let has_theme: bool = msg_send![app, respondsToSelector: sel!(effectiveAppearance)]; + if has_theme { + let appearance = if let Some(theme) = theme { + let name = NSString::from_str(match theme { + Theme::Dark => "NSAppearanceNameDarkAqua", + Theme::Light => "NSAppearanceNameAqua", + }); + msg_send![class!(NSAppearance), appearanceNamed: &*name] + } else { + nil + }; + let _: () = msg_send![app, setAppearance: appearance]; + } + } +} + +struct WindowClass(&'static Class); + +static WINDOW_CLASS: LazyLock = LazyLock::new(|| unsafe { + let window_superclass = class!(NSWindow); + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoWindow\0").unwrap(), + window_superclass, + ) + .unwrap(); + decl.add_method( + sel!(canBecomeMainWindow), + is_focusable as extern "C" fn(_, _) -> _, + ); + decl.add_method( + sel!(canBecomeKeyWindow), + is_focusable as extern "C" fn(_, _) -> _, + ); + decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _)); + // progress bar states, follows ProgressState + decl.add_ivar::(CStr::from_bytes_with_nul(b"focusable\0").unwrap()); + WindowClass(decl.register()) +}); + +extern "C" fn is_focusable(this: &Object, _: Sel) -> Bool { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + *(this.get_ivar("focusable")) + } +} + +extern "C" fn send_event(this: &Object, _sel: Sel, event: &NSEvent) { + unsafe { + let event_type = event.r#type(); + if event_type == NSEventType::LeftMouseDown { + // When wkwebview is set on NSWindow, `WindowBuilder::with_movable_by_window_background` is not working. + // Because of this, we need to invoke `[NSWindow performWindowDragWithEvent]` in NSLeftMouseDown event. + let is_movable_window: bool = msg_send![this, isMovableByWindowBackground]; + if is_movable_window { + let _: () = msg_send![this, performWindowDragWithEvent: event]; + } + } + let superclass = util::superclass(this); + let _: () = msg_send![super(this, superclass), sendEvent: event]; + } +} + +#[derive(Default)] +pub struct SharedState { + pub resizable: bool, + pub fullscreen: Option, + // This is true between windowWillEnterFullScreen and windowDidEnterFullScreen + // or windowWillExitFullScreen and windowDidExitFullScreen. + // We must not toggle fullscreen when this is true. + pub in_fullscreen_transition: bool, + // If it is attempted to toggle fullscreen when in_fullscreen_transition is true, + // Set target_fullscreen and do after fullscreen transition is end. + pub target_fullscreen: Option>, + pub maximized: bool, + pub standard_frame: Option, + is_simple_fullscreen: bool, + pub saved_style: Option, + /// Presentation options saved before entering `set_simple_fullscreen`, and + /// restored upon exiting it. Also used when transitioning from Borderless to + /// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu + /// bar in exclusive fullscreen but want to restore the original options when + /// transitioning back to borderless fullscreen. + save_presentation_opts: Option, + pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, + pub current_theme: Theme, +} + +impl SharedState { + pub fn saved_standard_frame(&self) -> NSRect { + self + .standard_frame + .unwrap_or_else(|| NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0))) + } +} + +impl From for SharedState { + fn from(attribs: WindowAttributes) -> Self { + SharedState { + resizable: attribs.resizable, + // This fullscreen field tracks the current state of the window + // (as seen by `WindowDelegate`), and since the window hasn't + // actually been fullscreened yet, we can't set it yet. This is + // necessary for state transitions to work right, since otherwise + // the initial value and the first `set_fullscreen` call would be + // identical, resulting in a no-op. + fullscreen: None, + maximized: attribs.maximized, + ..Default::default() + } + } +} + +pub struct UnownedWindow { + pub ns_window: Retained, // never changes + pub ns_view: Retained, // never changes + input_context: IdRef, // never changes + pub shared_state: Arc>, + decorations: AtomicBool, + cursor_state: Weak>, + transparent: bool, + pub inner_rect: Option>, +} + +unsafe impl Send for UnownedWindow {} +unsafe impl Sync for UnownedWindow {} + +impl UnownedWindow { + pub fn new( + mut win_attribs: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result<(Arc, IdRef), RootOsError> { + if !util::is_main_thread() { + panic!("Windows can only be created on the main thread on macOS"); + } + trace!("Creating new window"); + + let _pool = unsafe { NSAutoreleasePool::new() }; + let ns_window = create_window(&win_attribs, &pl_attribs) + .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; + + let (ns_view, cursor_state) = unsafe { create_view(&ns_window, &pl_attribs) } + .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?; + + // Configure the new view as the "key view" for the window + unsafe { + ns_window.setContentView(Some(&ns_view)); + ns_window.setInitialFirstResponder(Some(&ns_view)); + } + + let input_context = unsafe { util::create_input_context(&ns_view) }; + + let scale_factor = unsafe { NSWindow::backingScaleFactor(&ns_window) as f64 }; + + unsafe { + if win_attribs.transparent { + ns_window.setOpaque(false); + } + + if win_attribs.transparent || win_attribs.background_color.is_some() { + let color = win_attribs + .background_color + .map(|(r, g, b, a)| { + NSColor::colorWithRed_green_blue_alpha( + r as f64 / 255.0, + g as f64 / 255.0, + b as f64 / 255.0, + a as f64 / 255.0, + ) + }) + .unwrap_or_else(|| NSColor::clearColor()); + ns_window.setBackgroundColor(Some(&color)); + } + + if win_attribs.inner_size_constraints.has_min() { + let min_size = win_attribs + .inner_size_constraints + .min_size_logical(scale_factor); + set_min_inner_size(&ns_window, min_size); + } + if win_attribs.inner_size_constraints.has_max() { + let max_size = win_attribs + .inner_size_constraints + .max_size_logical(scale_factor); + set_max_inner_size(&ns_window, max_size); + } + + // register for drag and drop operations. + ns_window.registerForDraggedTypes(&NSArray::arrayWithObject(appkit::NSFilenamesPboardType)); + } + + // Since `win_attribs` is put into a mutex below, we'll just copy these + // attributes now instead of bothering to lock it later. + // Also, `SharedState` doesn't carry `fullscreen` over; it's set + // indirectly by us calling `set_fullscreen` below, causing handlers in + // `WindowDelegate` to update the state. + let fullscreen = win_attribs.fullscreen.take(); + let maximized = win_attribs.maximized; + let transparent = win_attribs.transparent; + let visible = win_attribs.visible; + let focused = win_attribs.focused; + let decorations = win_attribs.decorations; + let visible_on_all_workspaces = win_attribs.visible_on_all_workspaces; + let inner_rect = win_attribs + .inner_size + .map(|size| size.to_physical(scale_factor)); + + let cloned_preferred_theme = win_attribs.preferred_theme; + + let window = Arc::new(UnownedWindow { + ns_view, + ns_window, + input_context, + shared_state: Arc::new(Mutex::new(win_attribs.into())), + decorations: AtomicBool::new(decorations), + cursor_state, + inner_rect, + transparent, + }); + + match cloned_preferred_theme { + Some(theme) => { + set_ns_theme(Some(theme)); + let mut state = window.shared_state.lock().unwrap(); + state.current_theme = theme.clone(); + } + None => { + let mut state = window.shared_state.lock().unwrap(); + state.current_theme = get_ns_theme(); + } + } + + let delegate = new_delegate(&window, fullscreen.is_some()); + + // Set fullscreen mode after we setup everything + window.set_fullscreen(fullscreen); + window.set_visible_on_all_workspaces(visible_on_all_workspaces); + + // Setting the window as key has to happen *after* we set the fullscreen + // state, since otherwise we'll briefly see the window at normal size + // before it transitions. + if visible { + if focused { + // Tightly linked with `app_state::window_activation_hack` + unsafe { window.ns_window.makeKeyAndOrderFront(None) }; + } else { + unsafe { window.ns_window.orderFront(None) }; + } + } + + if maximized { + window.set_maximized(maximized); + } + + Ok((window, delegate)) + } + + fn set_style_mask_async(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_async(&self.ns_window, &self.ns_view, mask) }; + } + + fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_sync(&self.ns_window, &self.ns_view, mask) }; + } + + pub fn id(&self) -> Id { + get_window_id(&self.ns_window) + } + + pub fn set_title(&self, title: &str) { + unsafe { + util::set_title_async(&self.ns_window, &self.ns_view, title.to_string()); + } + } + + pub fn title(&self) -> String { + self.ns_window.title().to_string() + } + + pub fn set_visible(&self, visible: bool) { + match visible { + true => unsafe { util::make_key_and_order_front_sync(&self.ns_window) }, + false => unsafe { util::order_out_sync(&self.ns_window) }, + } + } + + #[inline] + // Shortener for set_visible(true) + pub fn set_focus(&self) { + unsafe { + let is_minimized = self.ns_window.isMiniaturized(); + let is_visible = self.ns_window.isVisible(); + if !is_minimized && is_visible { + util::set_focus(&self.ns_window); + } + } + } + + #[inline] + pub fn set_focusable(&self, focusable: bool) { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let ns_window = + Retained::into_raw(Retained::cast_unchecked::(self.ns_window.clone())); + *((*ns_window).get_mut_ivar::("focusable")) = focusable.into(); + } + } + + #[inline] + pub fn is_focused(&self) -> bool { + unsafe { self.ns_window.isKeyWindow() } + } + + pub fn request_redraw(&self) { + AppState::queue_redraw(RootWindowId(self.id())); + } + + pub fn outer_position(&self) -> Result, NotSupportedError> { + let frame_rect = unsafe { NSWindow::frame(&self.ns_window) }; + let position = LogicalPosition::new( + frame_rect.origin.x as f64, + util::bottom_left_to_top_left(frame_rect), + ); + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) + } + + pub fn inner_position(&self) -> Result, NotSupportedError> { + let content_rect = unsafe { + NSWindow::contentRectForFrameRect(&self.ns_window, NSWindow::frame(&self.ns_window)) + }; + let position = LogicalPosition::new( + content_rect.origin.x as f64, + util::bottom_left_to_top_left(content_rect), + ); + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) + } + + pub fn set_outer_position(&self, position: Position) { + let scale_factor = self.scale_factor(); + let position = position.to_logical(scale_factor); + unsafe { + util::set_frame_top_left_point_async(&self.ns_window, util::window_position(position)); + } + } + + #[inline] + pub fn inner_size(&self) -> PhysicalSize { + let view_frame = unsafe { NSView::frame(&self.ns_view) }; + let logical: LogicalSize = + (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let scale_factor = self.scale_factor(); + logical.to_physical(scale_factor) + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + let view_frame = unsafe { NSWindow::frame(&self.ns_window) }; + let logical: LogicalSize = + (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let scale_factor = self.scale_factor(); + logical.to_physical(scale_factor) + } + + #[inline] + pub fn set_inner_size(&self, size: Size) { + unsafe { + let scale_factor = self.scale_factor(); + util::set_content_size_async(&self.ns_window, size.to_logical(scale_factor)); + } + } + + pub fn set_min_inner_size(&self, dimensions: Option) { + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: 0.0, + height: 0.0, + })); + let scale_factor = self.scale_factor(); + unsafe { + set_min_inner_size(&self.ns_window, dimensions.to_logical(scale_factor)); + } + } + + pub fn set_max_inner_size(&self, dimensions: Option) { + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: f32::MAX as f64, + height: f32::MAX as f64, + })); + let scale_factor = self.scale_factor(); + unsafe { + set_max_inner_size(&self.ns_window, dimensions.to_logical(scale_factor)); + } + } + + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + let scale_factor = self.scale_factor(); + unsafe { + let min_size = constraints.min_size_logical(scale_factor); + set_min_inner_size(&self.ns_window, min_size); + let max_size = constraints.max_size_logical(scale_factor); + set_max_inner_size(&self.ns_window, max_size); + } + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + let fullscreen = { + trace!("Locked shared state in `set_resizable`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.resizable = resizable; + trace!("Unlocked shared state in `set_resizable`"); + shared_state_lock.fullscreen.is_some() + }; + if !fullscreen { + let mut mask = unsafe { self.ns_window.styleMask() }; + if resizable { + mask |= NSWindowStyleMask::Resizable; + } else { + mask &= !NSWindowStyleMask::Resizable; + } + self.set_style_mask_sync(mask); + } // Otherwise, we don't change the mask until we exit fullscreen. + } + + #[inline] + pub fn set_minimizable(&self, minimizable: bool) { + let mut mask = unsafe { self.ns_window.styleMask() }; + if minimizable { + mask |= NSWindowStyleMask::Miniaturizable; + } else { + mask &= !NSWindowStyleMask::Miniaturizable; + } + self.set_style_mask_sync(mask); + } + + #[inline] + pub fn set_maximizable(&self, maximizable: bool) { + unsafe { + if let Some(button) = self + .ns_window + .standardWindowButton(NSWindowButton::ZoomButton) + { + button.setEnabled(maximizable); + } + } + } + + #[inline] + pub fn set_closable(&self, closable: bool) { + let mut mask = unsafe { self.ns_window.styleMask() }; + if closable { + mask |= NSWindowStyleMask::Closable; + } else { + mask &= !NSWindowStyleMask::Closable; + } + self.set_style_mask_sync(mask); + } + + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let cursor = util::Cursor::from(cursor); + if let Some(cursor_access) = self.cursor_state.upgrade() { + cursor_access.lock().unwrap().cursor = cursor; + } + unsafe { + self.ns_window.invalidateCursorRectsForView(&self.ns_view); + } + } + + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 + CGDisplay::associate_mouse_and_mouse_cursor_position(!grab) + .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) + } + + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + if let Some(cursor_access) = self.cursor_state.upgrade() { + let mut cursor_state = cursor_access.lock().unwrap(); + if visible != cursor_state.visible { + cursor_state.visible = visible; + drop(cursor_state); + unsafe { + self.ns_window.invalidateCursorRectsForView(&self.ns_view); + } + } + } + } + + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position() + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + NSWindow::backingScaleFactor(&self.ns_window) as _ + } + + #[inline] + pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { + let physical_window_position = self.inner_position().unwrap(); + let scale_factor = self.scale_factor(); + let window_position = physical_window_position.to_logical::(scale_factor); + let logical_cursor_position = cursor_position.to_logical::(scale_factor); + let point = CGPoint { + x: logical_cursor_position.x + window_position.x, + y: logical_cursor_position.y + window_position.y, + }; + CGDisplay::warp_mouse_cursor_position(point) + .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; + CGDisplay::associate_mouse_and_mouse_cursor_position(true) + .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; + + Ok(()) + } + + #[inline] + pub fn set_background_color(&self, color: Option) { + unsafe { + let color = color + .map(|(r, g, b, a)| { + Some(NSColor::colorWithRed_green_blue_alpha( + r as f64 / 255.0, + g as f64 / 255.0, + b as f64 / 255.0, + a as f64 / 255.0, + )) + }) + .unwrap_or_else(|| { + if self.transparent { + Some(NSColor::clearColor()) + } else { + None + } + }); + self.ns_window.setBackgroundColor(color.as_deref()); + } + } + + #[inline] + pub fn drag_window(&self) -> Result<(), ExternalError> { + unsafe { + let mtm = MainThreadMarker::new_unchecked(); + let mut event: id = msg_send![&NSApp(mtm), currentEvent]; + + let event_type: NSUInteger = msg_send![event, type]; + if event_type == 0x15 { + let mouse_location: NSPoint = msg_send![class!(NSEvent), mouseLocation]; + let event_modifier_flags: NSEventModifierFlags = msg_send![event, modifierFlags]; + let event_timestamp: NSTimeInterval = msg_send![event, timestamp]; + let event_window_number: NSInteger = msg_send![event, windowNumber]; + + event = msg_send![ + class!(NSEvent), + mouseEventWithType: NSEventType::LeftMouseDown, + location: mouse_location, + modifierFlags: event_modifier_flags, + timestamp: event_timestamp, + windowNumber: event_window_number, + context: nil, + eventNumber: NSEventSubtype::WindowExposed, + clickCount: 1, + pressure: 1.0, + ]; + } + + let _: () = msg_send![&self.ns_window, performWindowDragWithEvent: event]; + } + + Ok(()) + } + + pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + unsafe { + util::set_ignore_mouse_events(&self.ns_window, ignore); + } + + Ok(()) + } + + pub(crate) fn is_zoomed(&self) -> bool { + // Previously, is_zoomed temporarily mutated the window's styleMask(or resizable) + // to force macOS to return a valid result for borderless windows. + // This synchronous mutation could trigger unnecessary layout passes or state inconsistencies. + // Related issue: https://github.com/rust-windowing/winit/issues/4071 and https://github.com/tauri-apps/plugins-workspace/issues/3240 + + unsafe { + let curr_mask = self.ns_window.styleMask(); + + let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; + if curr_mask.contains(required) { + return self.ns_window.isZoomed(); + } + + if let Some(screen) = self.ns_window.screen() { + let frame = self.ns_window.frame(); + let visible_frame = screen.visibleFrame(); + + return (frame.size.width - visible_frame.size.width).abs() < 1.0 + && (frame.size.height - visible_frame.size.height).abs() < 1.0; + } + + // Fallback to original `isZoomed` check + self.ns_window.isZoomed() + } + } + + fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { + let base_mask = shared_state + .saved_style + .take() + .unwrap_or_else(|| unsafe { self.ns_window.styleMask() }); + if shared_state.resizable { + base_mask | NSWindowStyleMask::Resizable + } else { + base_mask & !NSWindowStyleMask::Resizable + } + } + + /// This is called when the window is exiting fullscreen, whether by the + /// user clicking on the green fullscreen button or programmatically by + /// `toggleFullScreen:` + pub(crate) fn restore_state_from_fullscreen(&self) { + trace!("Locked shared state in `restore_state_from_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + shared_state_lock.fullscreen = None; + + let maximized = shared_state_lock.maximized; + let mask = self.saved_style(&mut *shared_state_lock); + + drop(shared_state_lock); + trace!("Unlocked shared state in `restore_state_from_fullscreen`"); + + self.set_style_mask_async(mask); + self.set_maximized(maximized); + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let is_minimized: bool = unsafe { self.ns_window.isMiniaturized() }; + if is_minimized == minimized { + return; + } + + if minimized { + unsafe { + NSWindow::miniaturize(&self.ns_window, Some(&self.ns_window)); + } + } else { + unsafe { + NSWindow::deminiaturize(&self.ns_window, Some(&self.ns_window)); + } + } + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + let is_zoomed = self.is_zoomed(); + if is_zoomed == maximized { + return; + }; + unsafe { + util::set_maximized_async( + &self.ns_window, + is_zoomed, + maximized, + Arc::downgrade(&self.shared_state), + ); + } + } + + #[inline] + pub fn fullscreen(&self) -> Option { + let shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen.clone() + } + + #[inline] + pub fn is_visible(&self) -> bool { + unsafe { self.ns_window.isVisible() } + } + + #[inline] + pub fn is_always_on_top(&self) -> bool { + unsafe { self.ns_window.level() == ffi::kCGFloatingWindowLevelKey } + } + + #[inline] + pub fn is_maximized(&self) -> bool { + self.is_zoomed() + } + + #[inline] + pub fn is_minimized(&self) -> bool { + unsafe { self.ns_window.isMiniaturized() } + } + + #[inline] + pub fn is_resizable(&self) -> bool { + unsafe { msg_send![&self.ns_window, isResizable] } + } + + #[inline] + pub fn is_minimizable(&self) -> bool { + unsafe { msg_send![&self.ns_window, isMiniaturizable] } + } + + #[inline] + pub fn is_maximizable(&self) -> bool { + unsafe { + if let Some(button) = self + .ns_window + .standardWindowButton(NSWindowButton::ZoomButton) + { + button.isEnabled() + } else { + false + } + } + } + + #[inline] + pub fn is_closable(&self) -> bool { + let is_closable: bool = unsafe { msg_send![&self.ns_window, hasCloseBox] }; + is_closable + } + + #[inline] + pub fn is_decorated(&self) -> bool { + self.decorations.load(Ordering::Acquire) + } + + #[inline] + pub fn set_fullscreen(&self, fullscreen: Option) { + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + if shared_state_lock.is_simple_fullscreen { + trace!("Unlocked shared state in `set_fullscreen`"); + return; + } + if shared_state_lock.in_fullscreen_transition { + // We can't set fullscreen here. + // Set fullscreen after transition. + shared_state_lock.target_fullscreen = Some(fullscreen); + trace!("Unlocked shared state in `set_fullscreen`"); + return; + } + let old_fullscreen = shared_state_lock.fullscreen.clone(); + if fullscreen == old_fullscreen { + trace!("Unlocked shared state in `set_fullscreen`"); + return; + } + trace!("Unlocked shared state in `set_fullscreen`"); + drop(shared_state_lock); + + // If the fullscreen is on a different monitor, we must move the window + // to that monitor before we toggle fullscreen (as `toggleFullScreen` + // does not take a screen parameter, but uses the current screen) + if let Some(ref fullscreen) = fullscreen { + let new_screen = match fullscreen { + Fullscreen::Borderless(Some(borderless)) => { + let RootMonitorHandle { inner: monitor } = borderless.clone(); + monitor + } + Fullscreen::Borderless(None) => { + if let Some(current) = self.current_monitor_inner() { + let RootMonitorHandle { inner: monitor } = current; + monitor + } else { + return; + } + } + Fullscreen::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + }) => monitor.clone(), + } + .ns_screen() + .unwrap(); + + unsafe { + let old_screen = NSWindow::screen(&self.ns_window); + if old_screen.as_ref() != Some(&new_screen) { + let mut screen_frame: NSRect = msg_send![&new_screen, frame]; + // The coordinate system here has its origin at bottom-left + // and Y goes up + screen_frame.origin.y += screen_frame.size.height; + util::set_frame_top_left_point_async(&self.ns_window, screen_frame.origin); + } + } + } + + if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { + // Note: `enterFullScreenMode:withOptions:` seems to do the exact + // same thing as we're doing here (captures the display, sets the + // video mode, and hides the menu bar and dock), with the exception + // of that I couldn't figure out how to set the display mode with + // it. I think `enterFullScreenMode:withOptions:` is still using the + // older display mode API where display modes were of the type + // `CFDictionary`, but this has changed, so we can't obtain the + // correct parameter for this any longer. Apple's code samples for + // this function seem to just pass in "YES" for the display mode + // parameter, which is not consistent with the docs saying that it + // takes a `NSDictionary`.. + + let display_id = video_mode.monitor().inner.native_identifier(); + + let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; + + if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { + unsafe { + let app = NSApp(mtm); + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); + } + } + + unsafe { + // Fade to black (and wait for the fade to complete) to hide the + // flicker from capturing the display and switching display mode + if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) == ffi::kCGErrorSuccess { + ffi::CGDisplayFade( + fade_token, + 0.3, + ffi::kCGDisplayBlendNormal, + ffi::kCGDisplayBlendSolidColor, + 0.0, + 0.0, + 0.0, + ffi::TRUE, + ); + } + + assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); + } + + unsafe { + let result = ffi::CGDisplaySetDisplayMode( + display_id, + video_mode.video_mode.native_mode.0, + std::ptr::null(), + ); + assert!(result == ffi::kCGErrorSuccess, "failed to set video mode"); + + // After the display has been configured, fade back in + // asynchronously + if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { + ffi::CGDisplayFade( + fade_token, + 0.6, + ffi::kCGDisplayBlendSolidColor, + ffi::kCGDisplayBlendNormal, + 0.0, + 0.0, + 0.0, + ffi::FALSE, + ); + ffi::CGReleaseDisplayFadeReservation(fade_token); + } + } + } + + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen = fullscreen.clone(); + trace!("Unlocked shared state in `set_fullscreen`"); + + match (&old_fullscreen, &fullscreen) { + (&None, &Some(_)) => unsafe { + util::toggle_full_screen_async( + &self.ns_window, + &self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Borderless(_)), &None) => unsafe { + // State is restored by `window_did_exit_fullscreen` + util::toggle_full_screen_async( + &self.ns_window, + &self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe { + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + // Rest of the state is restored by `window_did_exit_fullscreen` + util::toggle_full_screen_async( + &self.ns_window, + &self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { + // If we're already in fullscreen mode, calling + // `CGDisplayCapture` will place the shielding window on top of + // our window, which results in a black display and is not what + // we want. So, we must place our window on top of the shielding + // window. Unfortunately, this also makes our window be on top + // of the menu bar, and this looks broken, so we must make sure + // that the menu bar is disabled. This is done in the window + // delegate in `window:willUseFullScreenPresentationOptions:`. + let app = NSApp(mtm); + trace!("Locked shared state in `set_fullscreen`"); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); + + let presentation_options = NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::HideDock + | NSApplicationPresentationOptions::HideMenuBar; + app.setPresentationOptions(presentation_options); + + self + .ns_window + .setLevel(ffi::CGShieldingWindowLevel() as isize + 1); + }, + ( + &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), + &Some(Fullscreen::Borderless(_)), + ) => unsafe { + let presentation_options = shared_state_lock.save_presentation_opts.unwrap_or_else(|| { + NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::AutoHideDock + | NSApplicationPresentationOptions::AutoHideMenuBar + }); + NSApp(mtm).setPresentationOptions(presentation_options); + + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + + // Restore the normal window level following the Borderless fullscreen + // `CGShieldingWindowLevel() + 1` hack. + let () = msg_send![ + &self.ns_window, + setLevel: ffi::NSWindowLevel::NSNormalWindowLevel + ]; + }, + _ => {} + } + trace!("Unlocked shared state in `set_fullscreen`"); + } + + #[inline] + pub fn set_decorations(&self, decorations: bool) { + if decorations != self.decorations.load(Ordering::Acquire) { + self.decorations.store(decorations, Ordering::Release); + + let (fullscreen, resizable) = { + trace!("Locked shared state in `set_decorations`"); + let shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Unlocked shared state in `set_decorations`"); + ( + shared_state_lock.fullscreen.is_some(), + shared_state_lock.resizable, + ) + }; + + // If we're in fullscreen mode, we wait to apply decoration changes + // until we're in `window_did_exit_fullscreen`. + if fullscreen { + return; + } + + let new_mask = { + let mut new_mask = if decorations { + NSWindowStyleMask::Closable + | NSWindowStyleMask::Miniaturizable + | NSWindowStyleMask::Resizable + | NSWindowStyleMask::Titled + } else { + NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable + }; + if !resizable { + new_mask &= !NSWindowStyleMask::Resizable; + } + new_mask + }; + self.set_style_mask_async(new_mask); + } + } + + #[inline] + pub fn set_always_on_bottom(&self, always_on_bottom: bool) { + let level = if always_on_bottom { + ffi::NSWindowLevel::BelowNormalWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(&self.ns_window, level) }; + } + + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + let level = if always_on_top { + ffi::NSWindowLevel::NSFloatingWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(&self.ns_window, level) }; + } + + #[inline] + pub fn set_window_icon(&self, _icon: Option) { + // macOS doesn't have window icons. Though, there is + // `setRepresentedFilename`, but that's semantically distinct and should + // only be used when the window is in some way representing a specific + // file/directory. For instance, Terminal.app uses this for the CWD. + // Anyway, that should eventually be implemented as + // `WindowBuilderExt::with_represented_file` or something, and doesn't + // have anything to do with `set_window_icon`. + // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + } + + #[inline] + pub fn set_ime_position(&self, spot: Position) { + let scale_factor = self.scale_factor(); + let logical_spot = spot.to_logical(scale_factor); + unsafe { + view::set_ime_position( + &self.ns_view, + *self.input_context, + logical_spot.x, + logical_spot.y, + ); + } + } + + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let ns_request_type = request_type.map(|ty| match ty { + UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest, + UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest, + }); + unsafe { + let mtm = MainThreadMarker::new_unchecked(); + if let Some(ty) = ns_request_type { + NSApp(mtm).requestUserAttention(ty); + } + } + } + + #[inline] + // Allow directly accessing the current monitor internally without unwrapping. + pub(crate) fn current_monitor_inner(&self) -> Option { + unsafe { + let screen: Retained = self.ns_window.screen()?; + let desc = NSScreen::deviceDescription(&screen); + let key = ns_string!("NSScreenNumber"); + let value = NSDictionary::objectForKey(&desc, &key).unwrap(); + let display_id: NSUInteger = msg_send![&value, unsignedIntegerValue]; + Some(RootMonitorHandle { + inner: MonitorHandle::new(display_id.try_into().unwrap()), + }) + } + } + + #[inline] + pub fn current_monitor(&self) -> Option { + self.current_monitor_inner() + } + #[inline] + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + monitor::from_point(x, y).map(|inner| RootMonitorHandle { inner }) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) + } + + #[cfg(feature = "rwh_04")] + #[inline] + pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { + let mut window_handle = rwh_04::AppKitHandle::empty(); + window_handle.ns_window = self.ns_window(); + window_handle.ns_view = self.ns_view(); + rwh_04::RawWindowHandle::AppKit(window_handle) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { + let mut window_handle = rwh_05::AppKitWindowHandle::empty(); + window_handle.ns_window = self.ns_window(); + window_handle.ns_view = self.ns_view(); + rwh_05::RawWindowHandle::AppKit(window_handle) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_window_handle_rwh_06(&self) -> Result { + let window_handle = rwh_06::AppKitWindowHandle::new({ + let ptr = self.ns_view(); + std::ptr::NonNull::new(ptr).expect("Id should never be null") + }); + Ok(rwh_06::RawWindowHandle::AppKit(window_handle)) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::AppKit( + rwh_06::AppKitDisplayHandle::new(), + )) + } + + #[inline] + pub fn theme(&self) -> Theme { + let state = self.shared_state.lock().unwrap(); + state.current_theme + } + + pub fn set_theme(&self, theme: Option) { + set_ns_theme(theme); + let mut state = self.shared_state.lock().unwrap(); + state.current_theme = theme.unwrap_or_else(get_ns_theme); + } + + pub fn set_content_protection(&self, enabled: bool) { + unsafe { + self.ns_window.setSharingType(if enabled { + NSWindowSharingType::None + } else { + NSWindowSharingType::ReadOnly + }); + } + } + + pub fn set_visible_on_all_workspaces(&self, visible: bool) { + unsafe { + let mut collection_behavior = self.ns_window.collectionBehavior(); + if visible { + collection_behavior |= NSWindowCollectionBehavior::CanJoinAllSpaces; + } else { + collection_behavior &= !NSWindowCollectionBehavior::CanJoinAllSpaces; + }; + self.ns_window.setCollectionBehavior(collection_behavior) + } + } + + pub fn set_progress_bar(&self, progress: ProgressBarState) { + set_progress_indicator(progress); + } + + pub fn set_badge_label(&self, label: Option) { + set_badge_label(label); + } +} + +impl WindowExtMacOS for UnownedWindow { + #[inline] + fn ns_window(&self) -> *mut c_void { + &*self.ns_window as *const NSWindow as *mut _ + } + + #[inline] + fn ns_view(&self) -> *mut c_void { + &*self.ns_window.contentView().unwrap() as *const NSView as *mut _ + } + + #[inline] + fn simple_fullscreen(&self) -> bool { + let shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.is_simple_fullscreen + } + + #[inline] + fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + unsafe { + let mtm = MainThreadMarker::new_unchecked(); + let app = NSApp(mtm); + let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); + let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; + + // Do nothing if native fullscreen is active. + if is_native_fullscreen + || (fullscreen && is_simple_fullscreen) + || (!fullscreen && !is_simple_fullscreen) + { + return false; + } + + if fullscreen { + // Remember the original window's settings + // Exclude title bar + shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect( + &self.ns_window, + NSWindow::frame(&self.ns_window), + )); + shared_state_lock.saved_style = Some(self.ns_window.styleMask()); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); + + // Tell our window's state that we're in fullscreen + shared_state_lock.is_simple_fullscreen = true; + + // Simulate pre-Lion fullscreen by hiding the dock and menu bar + let presentation_options = NSApplicationPresentationOptions::AutoHideDock + | NSApplicationPresentationOptions::AutoHideMenuBar; + app.setPresentationOptions(presentation_options); + + // Hide the titlebar + util::toggle_style_mask( + &self.ns_window, + &self.ns_view, + NSWindowStyleMask::Titled, + false, + ); + + // Set the window frame to the screen frame size + let screen = self.ns_window.screen().unwrap(); + let screen_frame = NSScreen::frame(&screen); + NSWindow::setFrame_display(&self.ns_window, screen_frame, true); + + // Fullscreen windows can't be resized, minimized, or moved + util::toggle_style_mask( + &self.ns_window, + &self.ns_view, + NSWindowStyleMask::Miniaturizable, + false, + ); + util::toggle_style_mask( + &self.ns_window, + &self.ns_view, + NSWindowStyleMask::Resizable, + false, + ); + NSWindow::setMovable(&self.ns_window, false); + + true + } else { + let new_mask = self.saved_style(&mut *shared_state_lock); + self.set_style_mask_async(new_mask); + shared_state_lock.is_simple_fullscreen = false; + + if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { + app.setPresentationOptions(presentation_opts); + } + + let frame = shared_state_lock.saved_standard_frame(); + NSWindow::setFrame_display(&self.ns_window, frame, true); + NSWindow::setMovable(&self.ns_window, true); + + true + } + } + } + + #[inline] + fn has_shadow(&self) -> bool { + self.ns_window.hasShadow() + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + self.ns_window.setHasShadow(has_shadow) + } + + #[inline] + fn set_traffic_light_inset>(&self, position: P) { + let position: Position = position.into(); + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let state_ptr: *mut c_void = *(self.ns_view).get_ivar("taoState"); + let state = &mut *(state_ptr as *mut ViewState); + state.traffic_light_inset = Some(position.to_logical(self.scale_factor())); + } + } + + #[inline] + fn set_is_document_edited(&self, edited: bool) { + self.ns_window.setDocumentEdited(edited) + } + + #[inline] + fn is_document_edited(&self) -> bool { + unsafe { self.ns_window.isDocumentEdited() } + } + + #[inline] + fn set_allows_automatic_window_tabbing(&self, enabled: bool) { + let mtm = MainThreadMarker::new().unwrap(); + NSWindow::setAllowsAutomaticWindowTabbing(enabled, mtm) + } + + #[inline] + fn allows_automatic_window_tabbing(&self) -> bool { + let mtm = MainThreadMarker::new().unwrap(); + NSWindow::allowsAutomaticWindowTabbing(mtm) + } + + #[inline] + fn set_tabbing_identifier(&self, identifier: &str) { + unsafe { + self + .ns_window + .setTabbingIdentifier(&NSString::from_str(identifier)); + } + } + + #[inline] + fn tabbing_identifier(&self) -> String { + let tabbing_identifier = NSWindow::tabbingIdentifier(&self.ns_window); + tabbing_identifier.to_string() + } + + #[inline] + fn set_fullsize_content_view(&self, fullsize: bool) { + let mut mask = self.ns_window.styleMask(); + if fullsize { + mask |= NSWindowStyleMask::FullSizeContentView; + } else { + mask &= !NSWindowStyleMask::FullSizeContentView; + } + self.set_style_mask_sync(mask); + } + + #[inline] + fn set_titlebar_transparent(&self, transparent: bool) { + self.ns_window.setTitlebarAppearsTransparent(transparent); + } + + fn set_badge_label(&self, label: Option) { + set_badge_label(label); + } +} + +impl Drop for UnownedWindow { + fn drop(&mut self) { + trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); + // Close the window if it has not yet been closed. + unsafe { util::close_async(&self.ns_window) }; + } +} + +unsafe fn set_min_inner_size(window: &NSWindow, mut min_size: LogicalSize) { + let mut current_rect = NSWindow::frame(window); + let content_rect = NSWindow::contentRectForFrameRect(window, NSWindow::frame(window)); + // Convert from client area size to window size + min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 + min_size.height += (current_rect.size.height - content_rect.size.height) as f64; + window.setMinSize(NSSize { + width: min_size.width as CGFloat, + height: min_size.height as CGFloat, + }); + // If necessary, resize the window to match constraint + if current_rect.size.width < min_size.width { + current_rect.size.width = min_size.width; + window.setFrame_display(current_rect, false) + } + if current_rect.size.height < min_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - min_size.height; + current_rect.size.height = min_size.height; + window.setFrame_display(current_rect, false) + } +} + +unsafe fn set_max_inner_size(window: &NSWindow, mut max_size: LogicalSize) { + let mut current_rect = NSWindow::frame(window); + let content_rect = NSWindow::contentRectForFrameRect(window, NSWindow::frame(window)); + // Convert from client area size to window size + max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 + max_size.height += (current_rect.size.height - content_rect.size.height) as f64; + window.setMaxSize(NSSize { + width: max_size.width as CGFloat, + height: max_size.height as CGFloat, + }); + // If necessary, resize the window to match constraint + if current_rect.size.width > max_size.width { + current_rect.size.width = max_size.width; + window.setFrame_display(current_rect, false) + } + if current_rect.size.height > max_size.height { + // The origin point of a rectangle is at its bottom left in Cocoa. + // To ensure the window's top-left point remains the same: + current_rect.origin.y += current_rect.size.height - max_size.height; + current_rect.size.height = max_size.height; + window.setFrame_display(current_rect, false) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs new file mode 100644 index 000000000..4bd2f5ec1 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs @@ -0,0 +1,681 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + f64, + ffi::CStr, + os::raw::c_void, + sync::{Arc, Weak}, +}; + +use objc2::{ + msg_send, + rc::Retained, + runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, +}; +use objc2_app_kit::{ + self as appkit, NSApplicationPresentationOptions, NSPasteboard, NSView, NSWindow, +}; +use objc2_foundation::{ns_string, NSArray, NSAutoreleasePool, NSString, NSUInteger}; +use std::sync::LazyLock; + +use crate::{ + dpi::{LogicalPosition, LogicalSize}, + event::{Event, WindowEvent}, + keyboard::ModifiersState, + platform_impl::platform::{ + app_state::AppState, + event::{EventProxy, EventWrapper}, + ffi::{id, nil, BOOL, NO, YES}, + util::{self, IdRef}, + view::{reapply_traffic_light_inset, ViewState}, + window::{get_ns_theme, get_window_id, UnownedWindow}, + }, + window::{Fullscreen, WindowId}, +}; + +pub struct WindowDelegateState { + ns_window: Retained, // never changes + // We keep this ns_view because we still need its view state for some extern function + // like didResignKey + ns_view: Retained, // never changes + + window: Weak, + + // TODO: It's possible for delegate methods to be called asynchronously, + // causing data races / `RefCell` panics. + + // This is set when WindowBuilder::with_fullscreen was set, + // see comments of `window_did_fail_to_enter_fullscreen` + initial_fullscreen: bool, + + // During `windowDidResize`, we use this to only send Moved if the position changed. + previous_position: Option<(f64, f64)>, + + // Used to prevent redundant events. + previous_scale_factor: f64, + + // Used to prevent resized events from being fired + // when we are using our workaround in the `is_zoomed` function. + is_checking_zoomed_in: bool, +} + +impl WindowDelegateState { + pub fn new(window: &Arc, initial_fullscreen: bool) -> Self { + let scale_factor = window.scale_factor(); + let mut delegate_state = WindowDelegateState { + ns_window: window.ns_window.clone(), + ns_view: window.ns_view.clone(), + window: Arc::downgrade(window), + initial_fullscreen, + previous_position: None, + previous_scale_factor: scale_factor, + is_checking_zoomed_in: false, + }; + if (scale_factor - 1.0).abs() > f64::EPSILON { + delegate_state.emit_static_scale_factor_changed_event(); + } + + delegate_state + } + + fn ns_view(&self) -> Retained { + self.ns_window.contentView().unwrap() + } + + fn with_window(&mut self, callback: F) -> Option + where + F: FnOnce(&UnownedWindow) -> T, + { + self.window.upgrade().map(|ref window| callback(window)) + } + + pub fn emit_event(&mut self, event: WindowEvent<'static>) { + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(&self.ns_window)), + event, + }; + AppState::queue_event(EventWrapper::StaticEvent(event)); + } + + pub fn emit_static_scale_factor_changed_event(&mut self) { + let scale_factor = self.get_scale_factor(); + if (scale_factor - self.previous_scale_factor).abs() < f64::EPSILON { + return; + }; + + self.previous_scale_factor = scale_factor; + let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + ns_window: self.ns_window.clone(), + suggested_size: self.view_size(), + scale_factor, + }); + AppState::queue_event(wrapper); + } + + pub fn emit_resize_event(&mut self) { + let rect = NSView::frame(&self.ns_view()); + let scale_factor = self.get_scale_factor(); + let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let size = logical_size.to_physical(scale_factor); + self.emit_event(WindowEvent::Resized(size)); + } + + fn emit_move_event(&mut self) { + let rect = NSWindow::frame(&self.ns_window); + let x = rect.origin.x as f64; + let y = util::bottom_left_to_top_left(rect); + let moved = self.previous_position != Some((x, y)); + if moved { + self.previous_position = Some((x, y)); + let scale_factor = self.get_scale_factor(); + let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); + self.emit_event(WindowEvent::Moved(physical_pos)); + } + } + + fn get_scale_factor(&self) -> f64 { + NSWindow::backingScaleFactor(&self.ns_window) as f64 + } + + fn view_size(&self) -> LogicalSize { + let ns_size = NSView::frame(&self.ns_view()).size; + LogicalSize::new(ns_size.width as f64, ns_size.height as f64) + } +} + +pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> IdRef { + let state = WindowDelegateState::new(window, initial_fullscreen); + unsafe { + // This is free'd in `dealloc` + let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; + let delegate: id = msg_send![WINDOW_DELEGATE_CLASS.0, alloc]; + IdRef::new(msg_send![delegate, initWithTao: state_ptr]) + } +} + +struct WindowDelegateClass(*const Class); +unsafe impl Send for WindowDelegateClass {} +unsafe impl Sync for WindowDelegateClass {} + +static WINDOW_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new( + CStr::from_bytes_with_nul(b"TaoWindowDelegate\0").unwrap(), + superclass, + ) + .unwrap(); + + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); + decl.add_method( + sel!(initWithTao:), + init_with_tao as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(markIsCheckingZoomedIn), + mark_is_checking_zoomed_in as extern "C" fn(_, _), + ); + decl.add_method( + sel!(clearIsCheckingZoomedIn), + clear_is_checking_zoomed_in as extern "C" fn(_, _), + ); + + decl.add_method( + sel!(windowShouldClose:), + window_should_close as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(windowWillClose:), + window_will_close as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidResize:), + window_did_resize as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidChangeBackingProperties:), + window_did_change_backing_properties as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidBecomeKey:), + window_did_become_key as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidResignKey:), + window_did_resign_key as extern "C" fn(_, _, _), + ); + + decl.add_method( + sel!(draggingEntered:), + dragging_entered as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(prepareForDragOperation:), + prepare_for_drag_operation as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(performDragOperation:), + perform_drag_operation as extern "C" fn(_, _, _) -> _, + ); + decl.add_method( + sel!(concludeDragOperation:), + conclude_drag_operation as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(draggingExited:), + dragging_exited as extern "C" fn(_, _, _), + ); + + decl.add_method( + sel!(window:willUseFullScreenPresentationOptions:), + window_will_use_fullscreen_presentation_options as extern "C" fn(_, _, _, _) -> _, + ); + decl.add_method( + sel!(windowDidEnterFullScreen:), + window_did_enter_fullscreen as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidExitFullScreen:), + window_did_exit_fullscreen as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowWillExitFullScreen:), + window_will_exit_fullscreen as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(windowDidFailToEnterFullScreen:), + window_did_fail_to_enter_fullscreen as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(effectiveAppearanceDidChange:), + effective_appearance_did_change as extern "C" fn(_, _, _), + ); + decl.add_method( + sel!(effectiveAppearanceDidChangedOnMainThread:), + effective_appearance_did_changed_on_main_thread as extern "C" fn(_, _, _), + ); + + decl.add_ivar::<*mut c_void>(CStr::from_bytes_with_nul(b"taoState\0").unwrap()); + WindowDelegateClass(decl.register()) +}); + +// This function is definitely unsafe, but labeling that would increase +// boilerplate and wouldn't really clarify anything... +fn with_state T, T>(this: &Object, callback: F) { + #[allow(deprecated)] // TODO: Use define_class! + let state_ptr = unsafe { + let state_ptr: *mut c_void = *this.get_ivar("taoState"); + &mut *(state_ptr as *mut WindowDelegateState) + }; + callback(state_ptr); +} + +extern "C" fn dealloc(this: &Object, _sel: Sel) { + with_state(this, |state| unsafe { + drop(Box::from_raw(state as *mut WindowDelegateState)); + }); +} + +extern "C" fn init_with_tao(this: &Object, _sel: Sel, state: *mut c_void) -> id { + #[allow(deprecated)] // TODO: Use define_class! + unsafe { + let this: id = msg_send![this, init]; + if this != nil { + *(*this).get_mut_ivar("taoState") = state; + with_state(&*this, |state| { + let () = msg_send![&state.ns_window, setDelegate: this]; + }); + } + + let notification_center: &Object = + msg_send![class!(NSDistributedNotificationCenter), defaultCenter]; + let notification_name = ns_string!("AppleInterfaceThemeChangedNotification"); + let _: () = msg_send![ + notification_center, + addObserver: this + selector: sel!(effectiveAppearanceDidChange:) + name: &*notification_name + object: nil + ]; + + this + } +} + +extern "C" fn mark_is_checking_zoomed_in(this: &Object, _sel: Sel) { + with_state(&*this, |state| { + state.is_checking_zoomed_in = true; + }); +} + +extern "C" fn clear_is_checking_zoomed_in(this: &Object, _sel: Sel) { + with_state(&*this, |state| { + state.is_checking_zoomed_in = false; + }); +} + +extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `windowShouldClose:`"); + with_state(this, |state| state.emit_event(WindowEvent::CloseRequested)); + trace!("Completed `windowShouldClose:`"); + NO +} + +extern "C" fn window_will_close(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillClose:`"); + with_state(this, |state| unsafe { + // `setDelegate:` retains the previous value and then autoreleases it + let _pool = NSAutoreleasePool::new(); + // Since El Capitan, we need to be careful that delegate methods can't + // be called after the window closes. + let () = msg_send![&state.ns_window, setDelegate: nil]; + state.emit_event(WindowEvent::Destroyed); + }); + trace!("Completed `windowWillClose:`"); +} + +extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResize:`"); + with_state(this, |state| { + if !state.is_checking_zoomed_in { + state.emit_resize_event(); + state.emit_move_event(); + } + }); + trace!("Completed `windowDidResize:`"); +} + +// This won't be triggered if the move was part of a resize. +extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidMove:`"); + with_state(this, |state| { + state.emit_move_event(); + }); + trace!("Completed `windowDidMove:`"); +} + +extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidChangeBackingProperties:`"); + with_state(this, |state| { + state.emit_static_scale_factor_changed_event(); + }); + trace!("Completed `windowDidChangeBackingProperties:`"); +} + +extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidBecomeKey:`"); + with_state(this, |state| { + // TODO: center the cursor if the window had mouse grab when it + // lost focus + state.emit_event(WindowEvent::Focused(true)); + }); + trace!("Completed `windowDidBecomeKey:`"); +} + +extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResignKey:`"); + with_state(this, |state| { + // It happens rather often, e.g. when the user is Cmd+Tabbing, that the + // NSWindowDelegate will receive a didResignKey event despite no event + // being received when the modifiers are released. This is because + // flagsChanged events are received by the NSView instead of the + // NSWindowDelegate, and as a result a tracked modifiers state can quite + // easily fall out of synchrony with reality. This requires us to emit + // a synthetic ModifiersChanged event when we lose focus. + // + // Here we (very unsafely) acquire the taoState (a ViewState) from the + // Object referenced by state.ns_view (an IdRef, which is dereferenced + // to an id) + #[allow(deprecated)] // TODO: Use define_class! + let view_state: &mut ViewState = unsafe { + let ns_view: &Object = &state.ns_view; + let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); + &mut *(state_ptr as *mut ViewState) + }; + + // Both update the state and emit a ModifiersChanged event. + if !view_state.modifiers.is_empty() { + view_state.modifiers = ModifiersState::empty(); + state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + } + + state.emit_event(WindowEvent::Focused(false)); + }); + trace!("Completed `windowDidResignKey:`"); +} + +/// Invoked when the dragged image enters destination bounds or frame +extern "C" fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `draggingEntered:`"); + + use std::path::PathBuf; + + let pb: Retained = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = + unsafe { NSPasteboard::propertyListForType(&pb, appkit::NSFilenamesPboardType) }.unwrap(); + + for file in unsafe { Retained::cast_unchecked::(filenames) } { + let file = unsafe { Retained::cast_unchecked::(file) }; + + unsafe { + let f = NSString::UTF8String(&file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + with_state(this, |state| { + state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); + }); + } + } + + trace!("Completed `draggingEntered:`"); + YES +} + +/// Invoked when the image is released +extern "C" fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `prepareForDragOperation:`"); + trace!("Completed `prepareForDragOperation:`"); + YES +} + +/// Invoked after the released image has been removed from the screen +extern "C" fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `performDragOperation:`"); + + use std::path::PathBuf; + + let pb: Retained = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = + unsafe { NSPasteboard::propertyListForType(&pb, appkit::NSFilenamesPboardType) }.unwrap(); + + for file in unsafe { Retained::cast_unchecked::(filenames) } { + let file = unsafe { Retained::cast_unchecked::(file) }; + + unsafe { + let f = NSString::UTF8String(&file); + let path = CStr::from_ptr(f).to_string_lossy().into_owned(); + + with_state(this, |state| { + state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); + }); + } + } + + trace!("Completed `performDragOperation:`"); + YES +} + +/// Invoked when the dragging operation is complete +extern "C" fn conclude_drag_operation(_: &Object, _: Sel, _: id) { + trace!("Triggered `concludeDragOperation:`"); + trace!("Completed `concludeDragOperation:`"); +} + +/// Invoked when the dragging operation is cancelled +extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { + trace!("Triggered `draggingExited:`"); + with_state(this, |state| { + state.emit_event(WindowEvent::HoveredFileCancelled) + }); + trace!("Completed `draggingExited:`"); +} + +/// Invoked when before enter fullscreen +extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillEnterFullscreen:`"); + + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_will_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.maximized = window.is_zoomed(); + match shared_state.fullscreen { + // Exclusive mode sets the state in `set_fullscreen` as the user + // can't enter exclusive mode by other means (like the + // fullscreen button on the window decorations) + Some(Fullscreen::Exclusive(_)) => (), + // `window_will_enter_fullscreen` was triggered and we're already + // in fullscreen, so we must've reached here by `set_fullscreen` + // as it updates the state + Some(Fullscreen::Borderless(_)) => (), + // Otherwise, we must've reached fullscreen by the user clicking + // on the green fullscreen button. Update state! + None => { + let current_monitor = window.current_monitor_inner(); + shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) + } + } + shared_state.in_fullscreen_transition = true; + trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + }) + }); + trace!("Completed `windowWillEnterFullscreen:`"); +} + +/// Invoked when before exit fullscreen +extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillExitFullScreen:`"); + + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_will_exit_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = true; + trace!("Unlocked shared state in `window_will_exit_fullscreen`"); + }); + }); + trace!("Completed `windowWillExitFullScreen:`"); +} + +extern "C" fn window_will_use_fullscreen_presentation_options( + this: &Object, + _: Sel, + _: id, + proposed_options: NSUInteger, +) -> NSUInteger { + // Generally, games will want to disable the menu bar and the dock. Ideally, + // this would be configurable by the user. Unfortunately because of our + // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is + // placed on top of the menu bar in exclusive fullscreen mode. This looks + // broken so we always disable the menu bar in exclusive fullscreen. We may + // still want to make this configurable for borderless fullscreen. Right now + // we don't, for consistency. If we do, it should be documented that the + // user-provided options are ignored in exclusive fullscreen. + let mut options: NSUInteger = proposed_options; + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`"); + let shared_state = window.shared_state.lock().unwrap(); + if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { + options = (NSApplicationPresentationOptions::FullScreen + | NSApplicationPresentationOptions::HideDock + | NSApplicationPresentationOptions::HideMenuBar) + .bits(); + } + trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`"); + }) + }); + + options +} + +/// Invoked when entered fullscreen +extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidEnterFullscreen:`"); + with_state(this, |state| { + state.initial_fullscreen = false; + state.with_window(|window| { + trace!("Locked shared state in `window_did_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + trace!("Unlocked shared state in `window_did_enter_fullscreen`"); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } + }); + state.emit_resize_event(); + state.emit_move_event(); + }); + trace!("Completed `windowDidEnterFullscreen:`"); +} + +/// Invoked when exited fullscreen +extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidExitFullscreen:`"); + with_state(this, |state| { + state.with_window(|window| { + window.restore_state_from_fullscreen(); + trace!("Locked shared state in `window_did_exit_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + trace!("Unlocked shared state in `window_did_exit_fullscreen`"); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } + // Leaving fullscreen resets the traffic light buttons to their default + // position without a `drawRect:`, so re-apply the custom inset (#15451). + unsafe { reapply_traffic_light_inset(&window.ns_window, &window.ns_view) }; + }); + state.emit_resize_event(); + state.emit_move_event(); + }); + trace!("Completed `windowDidExitFullscreen:`"); +} + +/// Invoked when fail to enter fullscreen +/// +/// When this window launch from a fullscreen app (e.g. launch from VS Code +/// terminal), it creates a new virtual destkop and a transition animation. +/// This animation takes one second and cannot be disable without +/// elevated privileges. In this animation time, all toggleFullscreen events +/// will be failed. In this implementation, we will try again by using +/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. +/// It should be fine as we only do this at initialzation (i.e with_fullscreen +/// was set). +/// +/// From Apple doc: +/// In some cases, the transition to enter full-screen mode can fail, +/// due to being in the midst of handling some other animation or user gesture. +/// This method indicates that there was an error, and you should clean up any +/// work you may have done to prepare to enter full-screen mode. +extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidFailToEnterFullscreen:`"); + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + shared_state.target_fullscreen = None; + trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`"); + }); + if state.initial_fullscreen { + let _: () = unsafe { + msg_send![ + &state.ns_window, + performSelector:sel!(toggleFullScreen:), + withObject:nil, + afterDelay: 0.5, + ] + }; + } else { + state.with_window(|window| window.restore_state_from_fullscreen()); + } + }); + trace!("Completed `windowDidFailToEnterFullscreen:`"); +} + +// Observe theme change +extern "C" fn effective_appearance_did_change(this: &Object, _: Sel, _: id) { + trace!("Triggered `effectiveAppearDidChange:`"); + unsafe { + let _: () = msg_send![this, performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:), withObject:nil, waitUntilDone:false]; + } +} +extern "C" fn effective_appearance_did_changed_on_main_thread(this: &Object, _: Sel, _: id) { + with_state(this, |state| { + let theme = get_ns_theme(); + let current_theme = state.window.upgrade().map(|w| { + let mut state = w.shared_state.lock().unwrap(); + let current_theme = state.current_theme; + state.current_theme = theme; + current_theme + }); + if current_theme != Some(theme) { + state.emit_event(WindowEvent::ThemeChanged(theme)); + } + }); + trace!("Completed `effectiveAppearDidChange:`"); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs new file mode 100644 index 000000000..e8b62af96 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs @@ -0,0 +1,40 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(target_os = "windows")] +#[path = "windows/mod.rs"] +mod platform; +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +#[path = "linux/mod.rs"] +mod platform; +#[cfg(target_os = "macos")] +#[path = "macos/mod.rs"] +mod platform; +#[cfg(target_os = "android")] +#[path = "android/mod.rs"] +mod platform; +#[cfg(target_os = "ios")] +#[path = "ios/mod.rs"] +mod platform; + +pub use platform::*; + +#[cfg(all( + not(target_os = "ios"), + not(target_os = "windows"), + not(target_os = "linux"), + not(target_os = "macos"), + not(target_os = "android"), + not(target_os = "dragonfly"), + not(target_os = "freebsd"), + not(target_os = "netbsd"), + not(target_os = "openbsd"), +))] +compile_error!("The platform you're compiling for is not supported by tao"); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs new file mode 100644 index 000000000..26033112a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs @@ -0,0 +1,274 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +#![allow(non_snake_case)] + +use std::sync::LazyLock; +/// This is a simple implementation of support for Windows Dark Mode, +/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode +use windows::{ + core::{s, w, BOOL, PCSTR, PSTR}, + Win32::{ + Foundation::{ERROR_SUCCESS, HANDLE, HMODULE, HWND, LPARAM, WPARAM}, + Graphics::Dwm::{DwmSetWindowAttribute, DWMWINDOWATTRIBUTE}, + System::{ + LibraryLoader::*, + Registry::{RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD}, + }, + UI::{Accessibility::*, Input::KeyboardAndMouse::GetActiveWindow, WindowsAndMessaging::*}, + }, +}; + +use std::ffi::c_void; + +use crate::window::Theme; + +use super::util; + +static HUXTHEME: LazyLock = + LazyLock::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); + +static DARK_MODE_SUPPORTED: LazyLock = LazyLock::new(|| { + // We won't try to do anything for windows versions < 17763 + // (Windows 10 October 2018 update) + let v = *util::WIN_VERSION; + v.major == 10 && v.minor == 0 && v.build >= 17763 +}); + +pub fn allow_dark_mode_for_app(is_dark_mode: bool) { + if *DARK_MODE_SUPPORTED { + const UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL: u16 = 135; + type AllowDarkModeForApp = unsafe extern "system" fn(bool) -> bool; + static ALLOW_DARK_MODE_FOR_APP: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } + + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); + + #[repr(C)] + enum PreferredAppMode { + Default, + AllowDark, + // ForceDark, + // ForceLight, + // Max, + } + const UXTHEME_SETPREFERREDAPPMODE_ORDINAL: u16 = 135; + type SetPreferredAppMode = unsafe extern "system" fn(PreferredAppMode) -> PreferredAppMode; + static SET_PREFERRED_APP_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } + + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); + + if util::WIN_VERSION.build < 18362 { + if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP { + unsafe { _allow_dark_mode_for_app(is_dark_mode) }; + } + } else if let Some(_set_preferred_app_mode) = *SET_PREFERRED_APP_MODE { + let mode = if is_dark_mode { + PreferredAppMode::AllowDark + } else { + PreferredAppMode::Default + }; + unsafe { _set_preferred_app_mode(mode) }; + } + + refresh_immersive_color_policy_state(); + } +} + +fn refresh_immersive_color_policy_state() { + const UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL: u16 = 104; + type RefreshImmersiveColorPolicyState = unsafe extern "system" fn(); + static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } + + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); + + if let Some(_refresh_immersive_color_policy_state) = *REFRESH_IMMERSIVE_COLOR_POLICY_STATE { + unsafe { _refresh_immersive_color_policy_state() } + } +} + +/// Attempt to set a theme on a window, if necessary. +/// Returns the theme that was picked +pub fn try_window_theme( + hwnd: HWND, + preferred_theme: Option, + redraw_title_bar: bool, +) -> Theme { + if *DARK_MODE_SUPPORTED { + let is_dark_mode = match preferred_theme { + Some(theme) => theme == Theme::Dark, + None => should_use_dark_mode(), + }; + + let theme = match is_dark_mode { + true => Theme::Dark, + false => Theme::Light, + }; + + refresh_titlebar_theme_color(hwnd, is_dark_mode, redraw_title_bar); + + theme + } else { + Theme::Light + } +} + +pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { + const UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL: u16 = 133; + type AllowDarkModeForWindow = unsafe extern "system" fn(HWND, bool) -> bool; + static ALLOW_DARK_MODE_FOR_WINDOW: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } + + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); + + if *DARK_MODE_SUPPORTED { + if let Some(_allow_dark_mode_for_window) = *ALLOW_DARK_MODE_FOR_WINDOW { + unsafe { _allow_dark_mode_for_window(hwnd, is_dark_mode) }; + } + } +} + +fn refresh_titlebar_theme_color(hwnd: HWND, is_dark_mode: bool, redraw_title_bar: bool) { + if util::WIN_VERSION.build < 17763 { + let mut is_dark_mode_bigbool: i32 = is_dark_mode.into(); + unsafe { + let _ = SetPropW( + hwnd, + w!("UseImmersiveDarkModeColors"), + Some(HANDLE(&mut is_dark_mode_bigbool as *mut _ as _)), + ); + } + } else { + // https://github.com/MicrosoftDocs/sdk-api/pull/966/files + let dwmwa_use_immersive_dark_mode = if util::WIN_VERSION.build > 18985 { + DWMWINDOWATTRIBUTE(20) + } else { + DWMWINDOWATTRIBUTE(19) + }; + let dark_mode = BOOL::from(is_dark_mode); + unsafe { + let _ = DwmSetWindowAttribute( + hwnd, + dwmwa_use_immersive_dark_mode, + &dark_mode as *const BOOL as *const c_void, + std::mem::size_of::() as u32, + ); + if redraw_title_bar { + if GetActiveWindow() == hwnd { + DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default()); + DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default()); + } else { + DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default()); + DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default()); + } + } + } + } +} + +fn should_use_dark_mode() -> bool { + should_apps_use_dark_mode() && !is_high_contrast() +} + +fn should_apps_use_dark_mode() -> bool { + if let Some(apps_use_light_theme) = read_apps_use_light_theme() { + return !apps_use_light_theme; + } + // This undocumented method `ShouldAppsUseDarkMode` may return + // incorrect values on Windows 11. + // See https://github.com/tauri-apps/tao/pull/1165 + const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: u16 = 132; + type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; + static SHOULD_APPS_USE_DARK_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } + + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); + + SHOULD_APPS_USE_DARK_MODE + .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) + .unwrap_or(false) +} + +fn read_apps_use_light_theme() -> Option { + let mut data: u32 = 0; + let mut data_size = std::mem::size_of::() as u32; + let status = unsafe { + RegGetValueW( + HKEY_CURRENT_USER, + w!(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"), + w!("AppsUseLightTheme"), + RRF_RT_REG_DWORD, + None, + Some(&mut data as *mut _ as _), + Some(&mut data_size), + ) + }; + if status == ERROR_SUCCESS { + Some(data != 0) + } else { + None + } +} + +fn is_high_contrast() -> bool { + const HCF_HIGHCONTRASTON: u32 = 1; + + let mut hc = HIGHCONTRASTA { + cbSize: 0, + dwFlags: Default::default(), + lpszDefaultScheme: PSTR::null(), + }; + + let ok = unsafe { + SystemParametersInfoA( + SPI_GETHIGHCONTRAST, + std::mem::size_of_val(&hc) as _, + Some(&mut hc as *mut _ as _), + Default::default(), + ) + }; + + ok.is_ok() && (HCF_HIGHCONTRASTON & hc.dwFlags.0) != 0 +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs new file mode 100644 index 000000000..25f1850bd --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs @@ -0,0 +1,111 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![allow(non_snake_case, unused_unsafe)] + +use std::sync::Once; + +use windows::Win32::{ + Foundation::HWND, + Graphics::Gdi::*, + UI::{HiDpi::*, WindowsAndMessaging::*}, +}; + +use crate::platform_impl::platform::util::{ + ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE, + SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT, +}; + +pub fn become_dpi_aware() { + static ENABLE_DPI_AWARENESS: Once = Once::new(); + ENABLE_DPI_AWARENESS.call_once(|| { + unsafe { + if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT { + // We are on Windows 10 Anniversary Update (1607) or later. + if !SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).as_bool() { + // V2 only works with Windows 10 Creators Update (1703). Try using the older + // V1 if we can't set V2. + let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } + } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { + // We are on Windows 8.1 or later. + let _ = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { + // We are on Vista or later. + let _ = SetProcessDPIAware(); + } + } + }); +} + +pub fn enable_non_client_dpi_scaling(hwnd: HWND) { + unsafe { + if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { + let _ = EnableNonClientDpiScaling(hwnd); + } + } +} + +pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option { + unsafe { + if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { + // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to + // record one of the values to determine the DPI and respond appropriately". + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx + return Some(dpi_x); + } + } + } + None +} + +pub fn dpi_to_scale_factor(dpi: u32) -> f64 { + dpi as f64 / USER_DEFAULT_SCREEN_DPI as f64 +} + +pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { + if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { + // We are on Windows 10 Anniversary Update (1607) or later. + match GetDpiForWindow(hwnd) { + 0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid + dpi => dpi, + } + } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if monitor.is_invalid() { + return USER_DEFAULT_SCREEN_DPI; + } + + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { + dpi_x + } else { + USER_DEFAULT_SCREEN_DPI + } + } else { + // We are on Vista or later. + if IsProcessDPIAware().as_bool() { + let hdc = GetDC(Some(hwnd)); + if hdc.is_invalid() { + panic!("[tao] `GetDC` returned null!"); + } + // If the process is DPI aware, then scaling must be handled by the application using + // this DPI value. + let dpi = GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32; + ReleaseDC(Some(hwnd), hdc); + dpi + } else { + // If the process is DPI unaware, then scaling is performed by the OS; we thus return + // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the + // application and the WM. + USER_DEFAULT_SCREEN_DPI + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs new file mode 100644 index 000000000..d2685cd12 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs @@ -0,0 +1,181 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{cell::UnsafeCell, ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf, ptr}; + +use windows::{ + core::implement, + Win32::{ + Foundation::{self as win32f, HWND, POINTL}, + System::{ + Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, + Ole::{ + IDropTarget, IDropTarget_Impl, CF_HDROP, DROPEFFECT, DROPEFFECT_COPY, DROPEFFECT_NONE, + }, + SystemServices::MODIFIERKEYS_FLAGS, + }, + UI::Shell::{DragFinish, DragQueryFileW, HDROP}, + }, +}; + +use crate::platform_impl::platform::WindowId; + +use crate::{event::Event, window::WindowId as SuperWindowId}; + +#[implement(IDropTarget)] +pub struct FileDropHandler { + window: HWND, + send_event: Box)>, + cursor_effect: UnsafeCell, + hovered_is_valid: UnsafeCell, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ +} + +impl FileDropHandler { + pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { + Self { + window, + send_event, + cursor_effect: DROPEFFECT_NONE.into(), + hovered_is_valid: false.into(), + } + } + + unsafe fn iterate_filenames( + data_obj: windows_core::Ref<'_, IDataObject>, + callback: F, + ) -> Option + where + F: Fn(PathBuf), + { + let drop_format = FORMATETC { + cfFormat: CF_HDROP.0, + ptd: ptr::null_mut(), + dwAspect: DVASPECT_CONTENT.0, + lindex: -1, + tymed: TYMED_HGLOBAL.0 as u32, + }; + + match data_obj + .as_ref() + .expect("Received null IDataObject") + .GetData(&drop_format) + { + Ok(medium) => { + let hglobal = medium.u.hGlobal; + let hdrop = HDROP(hglobal.0 as _); + + // The second parameter (0xFFFFFFFF) instructs the function to return the item count + let mut lpsz_file = []; + let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, Some(&mut lpsz_file)); + + for i in 0..item_count { + // Get the length of the path string NOT including the terminating null character. + // Previously, this was using a fixed size array of MAX_PATH length, but the + // Windows API allows longer paths under certain circumstances. + let character_count = DragQueryFileW(hdrop, i, Some(&mut lpsz_file)) as usize; + let str_len = character_count + 1; + + // Fill path_buf with the null-terminated file name + let mut path_buf = Vec::with_capacity(str_len); + DragQueryFileW(hdrop, i, std::mem::transmute(path_buf.spare_capacity_mut())); + path_buf.set_len(str_len); + + callback(OsString::from_wide(&path_buf[0..character_count]).into()); + } + + Some(hdrop) + } + Err(error) => { + debug!( + "{}", + match error.code() { + win32f::DV_E_FORMATETC => { + // If the dropped item is not a file this error will occur. + // In this case it is OK to return without taking further action. + "Error occured while processing dropped/hovered item: item is not a file." + } + _ => "Unexpected error occured while processing dropped/hovered item.", + } + ); + None + } + } + } +} + +#[allow(non_snake_case)] +impl IDropTarget_Impl for FileDropHandler_Impl { + fn DragEnter( + &self, + pDataObj: windows_core::Ref<'_, IDataObject>, + _grfKeyState: MODIFIERKEYS_FLAGS, + _pt: &POINTL, + pdwEffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + use crate::event::WindowEvent::HoveredFile; + unsafe { + let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { + (self.send_event)(Event::WindowEvent { + window_id: SuperWindowId(WindowId(self.window.0 as _)), + event: HoveredFile(filename), + }); + }); + let hovered_is_valid = hdrop.is_some(); + let cursor_effect = if hovered_is_valid { + DROPEFFECT_COPY + } else { + DROPEFFECT_NONE + }; + *self.hovered_is_valid.get() = hovered_is_valid; + *self.cursor_effect.get() = cursor_effect; + *pdwEffect = cursor_effect; + } + Ok(()) + } + + fn DragOver( + &self, + _grfKeyState: MODIFIERKEYS_FLAGS, + _pt: &POINTL, + pdwEffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + unsafe { + *pdwEffect = *self.cursor_effect.get(); + } + Ok(()) + } + + fn DragLeave(&self) -> windows::core::Result<()> { + use crate::event::WindowEvent::HoveredFileCancelled; + if unsafe { *self.hovered_is_valid.get() } { + (self.send_event)(Event::WindowEvent { + window_id: SuperWindowId(WindowId(self.window.0 as _)), + event: HoveredFileCancelled, + }); + } + Ok(()) + } + + fn Drop( + &self, + pDataObj: windows_core::Ref<'_, IDataObject>, + _grfKeyState: MODIFIERKEYS_FLAGS, + _pt: &POINTL, + _pdwEffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + use crate::event::WindowEvent::DroppedFile; + unsafe { + let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { + (self.send_event)(Event::WindowEvent { + window_id: SuperWindowId(WindowId(self.window.0 as _)), + event: DroppedFile(filename), + }); + }); + if let Some(hdrop) = hdrop { + DragFinish(hdrop); + } + } + Ok(()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs new file mode 100644 index 000000000..b2fa29cdf --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs @@ -0,0 +1,421 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + char, + os::raw::c_int, + ptr, + sync::atomic::{AtomicBool, AtomicPtr, Ordering}, +}; + +use crate::event::{ModifiersState, ScanCode, VirtualKeyCode}; + +use windows::Win32::{ + Foundation::{HWND, LPARAM, WPARAM}, + UI::{ + Input::KeyboardAndMouse::*, + TextServices::HKL, + WindowsAndMessaging::{self as win32wm, *}, + }, +}; + +fn key_pressed(vkey: c_int) -> bool { + unsafe { (GetKeyState(vkey) & (1 << 15)) == (1 << 15) } +} + +pub fn get_key_mods() -> ModifiersState { + let filter_out_altgr = layout_uses_altgr() && key_pressed(VK_RMENU); + + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); + mods.set( + ModifiersState::CTRL, + key_pressed(VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::LOGO, + key_pressed(VK_LWIN) || key_pressed(VK_RWIN), + ); + mods +} + +bitflags! { + #[derive(Default)] + pub struct ModifiersStateSide: u32 { + const LSHIFT = 0b010 << 0; + const RSHIFT = 0b001 << 0; + + const LCTRL = 0b010 << 3; + const RCTRL = 0b001 << 3; + + const LALT = 0b010 << 6; + const RALT = 0b001 << 6; + + const LLOGO = 0b010 << 9; + const RLOGO = 0b001 << 9; + } +} + +impl ModifiersStateSide { + pub fn filter_out_altgr(&self) -> ModifiersStateSide { + match layout_uses_altgr() && self.contains(Self::RALT) { + false => *self, + true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT), + } + } +} + +impl From for ModifiersState { + fn from(side: ModifiersStateSide) -> Self { + let mut state = ModifiersState::default(); + state.set( + Self::SHIFT, + side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT), + ); + state.set( + Self::CTRL, + side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL), + ); + state.set( + Self::ALT, + side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT), + ); + state.set( + Self::LOGO, + side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO), + ); + state + } +} + +pub fn get_pressed_keys() -> impl Iterator { + let mut keyboard_state = vec![0u8; 256]; + unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) }; + keyboard_state + .into_iter() + .enumerate() + .filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit + .map(|(i, _)| i as c_int) +} + +unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { + let mut unicode_bytes = [0u16; 5]; + let len = ToUnicodeEx( + v_key, + 0, + keyboard_state.as_ptr(), + unicode_bytes.as_mut_ptr(), + unicode_bytes.len() as _, + 0, + hkl, + ); + if len >= 1 { + char::decode_utf16(unicode_bytes.iter().cloned()) + .next() + .and_then(|c| c.ok()) + } else { + None + } +} + +/// Figures out if the keyboard layout has an AltGr key instead of an Alt key. +/// +/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So, +/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every +/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If +/// pressing AltGr outputs characters that are different from the standard characters, the layout +/// uses AltGr. Otherwise, it doesn't. +/// +/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416 +fn layout_uses_altgr() -> bool { + unsafe { + static ACTIVE_LAYOUT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + static USES_ALTGR: AtomicBool = AtomicBool::new(false); + + let hkl = GetKeyboardLayout(0); + let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst); + + if hkl == old_hkl { + return USES_ALTGR.load(Ordering::SeqCst); + } + + let mut keyboard_state_altgr = [0u8; 256]; + // AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses + // we have to emulate to do an AltGr test. + keyboard_state_altgr[VK_MENU as usize] = 0x80; + keyboard_state_altgr[VK_CONTROL as usize] = 0x80; + + let keyboard_state_empty = [0u8; 256]; + + for v_key in 0..=255 { + let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl); + let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl); + if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) { + if noaltgr != altgr { + USES_ALTGR.store(true, Ordering::SeqCst); + return true; + } + } + } + + USES_ALTGR.store(false, Ordering::SeqCst); + false + } +} + +pub fn vkey_to_tao_vkey(vkey: u32) -> Option { + // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx + match vkey { + //win32wm::VK_LBUTTON => Some(VirtualKeyCode::Lbutton), + //win32wm::VK_RBUTTON => Some(VirtualKeyCode::Rbutton), + //win32wm::VK_CANCEL => Some(VirtualKeyCode::Cancel), + //win32wm::VK_MBUTTON => Some(VirtualKeyCode::Mbutton), + //win32wm::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1), + //win32wm::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2), + win32wm::VK_BACK => Some(VirtualKeyCode::Back), + win32wm::VK_TAB => Some(VirtualKeyCode::Tab), + //win32wm::VK_CLEAR => Some(VirtualKeyCode::Clear), + win32wm::VK_RETURN => Some(VirtualKeyCode::Return), + win32wm::VK_LSHIFT => Some(VirtualKeyCode::LShift), + win32wm::VK_RSHIFT => Some(VirtualKeyCode::RShift), + win32wm::VK_LCONTROL => Some(VirtualKeyCode::LControl), + win32wm::VK_RCONTROL => Some(VirtualKeyCode::RControl), + win32wm::VK_LMENU => Some(VirtualKeyCode::LAlt), + win32wm::VK_RMENU => Some(VirtualKeyCode::RAlt), + win32wm::VK_PAUSE => Some(VirtualKeyCode::Pause), + win32wm::VK_CAPITAL => Some(VirtualKeyCode::Capital), + win32wm::VK_KANA => Some(VirtualKeyCode::Kana), + //win32wm::VK_HANGUEL => Some(VirtualKeyCode::Hanguel), + //win32wm::VK_HANGUL => Some(VirtualKeyCode::Hangul), + //win32wm::VK_JUNJA => Some(VirtualKeyCode::Junja), + //win32wm::VK_FINAL => Some(VirtualKeyCode::Final), + //win32wm::VK_HANJA => Some(VirtualKeyCode::Hanja), + win32wm::VK_KANJI => Some(VirtualKeyCode::Kanji), + win32wm::VK_ESCAPE => Some(VirtualKeyCode::Escape), + win32wm::VK_CONVERT => Some(VirtualKeyCode::Convert), + win32wm::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert), + //win32wm::VK_ACCEPT => Some(VirtualKeyCode::Accept), + //win32wm::VK_MODECHANGE => Some(VirtualKeyCode::Modechange), + win32wm::VK_SPACE => Some(VirtualKeyCode::Space), + win32wm::VK_PRIOR => Some(VirtualKeyCode::PageUp), + win32wm::VK_NEXT => Some(VirtualKeyCode::PageDown), + win32wm::VK_END => Some(VirtualKeyCode::End), + win32wm::VK_HOME => Some(VirtualKeyCode::Home), + win32wm::VK_LEFT => Some(VirtualKeyCode::Left), + win32wm::VK_UP => Some(VirtualKeyCode::Up), + win32wm::VK_RIGHT => Some(VirtualKeyCode::Right), + win32wm::VK_DOWN => Some(VirtualKeyCode::Down), + //win32wm::VK_SELECT => Some(VirtualKeyCode::Select), + //win32wm::VK_PRINT => Some(VirtualKeyCode::Print), + //win32wm::VK_EXECUTE => Some(VirtualKeyCode::Execute), + win32wm::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot), + win32wm::VK_INSERT => Some(VirtualKeyCode::Insert), + win32wm::VK_DELETE => Some(VirtualKeyCode::Delete), + //win32wm::VK_HELP => Some(VirtualKeyCode::Help), + 0x30 => Some(VirtualKeyCode::Key0), + 0x31 => Some(VirtualKeyCode::Key1), + 0x32 => Some(VirtualKeyCode::Key2), + 0x33 => Some(VirtualKeyCode::Key3), + 0x34 => Some(VirtualKeyCode::Key4), + 0x35 => Some(VirtualKeyCode::Key5), + 0x36 => Some(VirtualKeyCode::Key6), + 0x37 => Some(VirtualKeyCode::Key7), + 0x38 => Some(VirtualKeyCode::Key8), + 0x39 => Some(VirtualKeyCode::Key9), + 0x41 => Some(VirtualKeyCode::A), + 0x42 => Some(VirtualKeyCode::B), + 0x43 => Some(VirtualKeyCode::C), + 0x44 => Some(VirtualKeyCode::D), + 0x45 => Some(VirtualKeyCode::E), + 0x46 => Some(VirtualKeyCode::F), + 0x47 => Some(VirtualKeyCode::G), + 0x48 => Some(VirtualKeyCode::H), + 0x49 => Some(VirtualKeyCode::I), + 0x4A => Some(VirtualKeyCode::J), + 0x4B => Some(VirtualKeyCode::K), + 0x4C => Some(VirtualKeyCode::L), + 0x4D => Some(VirtualKeyCode::M), + 0x4E => Some(VirtualKeyCode::N), + 0x4F => Some(VirtualKeyCode::O), + 0x50 => Some(VirtualKeyCode::P), + 0x51 => Some(VirtualKeyCode::Q), + 0x52 => Some(VirtualKeyCode::R), + 0x53 => Some(VirtualKeyCode::S), + 0x54 => Some(VirtualKeyCode::T), + 0x55 => Some(VirtualKeyCode::U), + 0x56 => Some(VirtualKeyCode::V), + 0x57 => Some(VirtualKeyCode::W), + 0x58 => Some(VirtualKeyCode::X), + 0x59 => Some(VirtualKeyCode::Y), + 0x5A => Some(VirtualKeyCode::Z), + win32wm::VK_LWIN => Some(VirtualKeyCode::LWin), + win32wm::VK_RWIN => Some(VirtualKeyCode::RWin), + win32wm::VK_APPS => Some(VirtualKeyCode::Apps), + win32wm::VK_SLEEP => Some(VirtualKeyCode::Sleep), + win32wm::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), + win32wm::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1), + win32wm::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2), + win32wm::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3), + win32wm::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4), + win32wm::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5), + win32wm::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6), + win32wm::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), + win32wm::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), + win32wm::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), + win32wm::VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), + win32wm::VK_ADD => Some(VirtualKeyCode::NumpadAdd), + //win32wm::VK_SEPARATOR => Some(VirtualKeyCode::Separator), + win32wm::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), + win32wm::VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), + win32wm::VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), + win32wm::VK_F1 => Some(VirtualKeyCode::F1), + win32wm::VK_F2 => Some(VirtualKeyCode::F2), + win32wm::VK_F3 => Some(VirtualKeyCode::F3), + win32wm::VK_F4 => Some(VirtualKeyCode::F4), + win32wm::VK_F5 => Some(VirtualKeyCode::F5), + win32wm::VK_F6 => Some(VirtualKeyCode::F6), + win32wm::VK_F7 => Some(VirtualKeyCode::F7), + win32wm::VK_F8 => Some(VirtualKeyCode::F8), + win32wm::VK_F9 => Some(VirtualKeyCode::F9), + win32wm::VK_F10 => Some(VirtualKeyCode::F10), + win32wm::VK_F11 => Some(VirtualKeyCode::F11), + win32wm::VK_F12 => Some(VirtualKeyCode::F12), + win32wm::VK_F13 => Some(VirtualKeyCode::F13), + win32wm::VK_F14 => Some(VirtualKeyCode::F14), + win32wm::VK_F15 => Some(VirtualKeyCode::F15), + win32wm::VK_F16 => Some(VirtualKeyCode::F16), + win32wm::VK_F17 => Some(VirtualKeyCode::F17), + win32wm::VK_F18 => Some(VirtualKeyCode::F18), + win32wm::VK_F19 => Some(VirtualKeyCode::F19), + win32wm::VK_F20 => Some(VirtualKeyCode::F20), + win32wm::VK_F21 => Some(VirtualKeyCode::F21), + win32wm::VK_F22 => Some(VirtualKeyCode::F22), + win32wm::VK_F23 => Some(VirtualKeyCode::F23), + win32wm::VK_F24 => Some(VirtualKeyCode::F24), + win32wm::VK_NUMLOCK => Some(VirtualKeyCode::Numlock), + win32wm::VK_SCROLL => Some(VirtualKeyCode::Scroll), + win32wm::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward), + win32wm::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward), + win32wm::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh), + win32wm::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop), + win32wm::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch), + win32wm::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites), + win32wm::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome), + win32wm::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute), + win32wm::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown), + win32wm::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp), + win32wm::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack), + win32wm::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack), + win32wm::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop), + win32wm::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause), + win32wm::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail), + win32wm::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect), + /*win32wm::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1), + win32wm::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/ + win32wm::VK_OEM_PLUS => Some(VirtualKeyCode::Equals), + win32wm::VK_OEM_COMMA => Some(VirtualKeyCode::Comma), + win32wm::VK_OEM_MINUS => Some(VirtualKeyCode::Minus), + win32wm::VK_OEM_PERIOD => Some(VirtualKeyCode::Period), + win32wm::VK_OEM_1 => map_text_keys(vkey), + win32wm::VK_OEM_2 => map_text_keys(vkey), + win32wm::VK_OEM_3 => map_text_keys(vkey), + win32wm::VK_OEM_4 => map_text_keys(vkey), + win32wm::VK_OEM_5 => map_text_keys(vkey), + win32wm::VK_OEM_6 => map_text_keys(vkey), + win32wm::VK_OEM_7 => map_text_keys(vkey), + /* win32wm::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */ + win32wm::VK_OEM_102 => Some(VirtualKeyCode::OEM102), + /*win32wm::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey), + win32wm::VK_PACKET => Some(VirtualKeyCode::Packet), + win32wm::VK_ATTN => Some(VirtualKeyCode::Attn), + win32wm::VK_CRSEL => Some(VirtualKeyCode::Crsel), + win32wm::VK_EXSEL => Some(VirtualKeyCode::Exsel), + win32wm::VK_EREOF => Some(VirtualKeyCode::Ereof), + win32wm::VK_PLAY => Some(VirtualKeyCode::Play), + win32wm::VK_ZOOM => Some(VirtualKeyCode::Zoom), + win32wm::VK_NONAME => Some(VirtualKeyCode::Noname), + win32wm::VK_PA1 => Some(VirtualKeyCode::Pa1), + win32wm::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/ + _ => None, + } +} + +pub fn handle_extended_keys( + vkey: u32, + mut scancode: UINT, + extended: bool, +) -> Option<(c_int, UINT)> { + // Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ + scancode = if extended { 0xE000 } else { 0x0000 } | scancode; + let vkey = match vkey { + win32wm::VK_SHIFT => unsafe { MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) as _ }, + win32wm::VK_CONTROL => { + if extended { + VK_RCONTROL + } else { + VK_LCONTROL + } + } + win32wm::VK_MENU => { + if extended { + VK_RMENU + } else { + VK_LMENU + } + } + _ => { + match scancode { + // When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE + // as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input. + // Don't emit anything for the LeftControl event in the pair... + 0xE01D if vkey == VK_PAUSE => return None, + // ...and emit the Pause event for the second event in the pair. + 0x45 if vkey == VK_PAUSE || vkey == 0xFF as _ => { + scancode = 0xE059; + VK_PAUSE + } + // VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different + // scancode when used with modifiers than when used without + 0xE046 => { + scancode = 0xE059; + VK_PAUSE + } + // VK_SCROLL has an incorrect vkey value when used with modifiers. + 0x46 => VK_SCROLL, + _ => vkey, + } + } + }; + Some((vkey, scancode)) +} + +pub fn process_key_params( + wparam: WPARAM, + lparam: LPARAM, +) -> Option<(ScanCode, Option)> { + let scancode = ((lparam >> 16) & 0xff) as UINT; + let extended = (lparam & 0x01000000) != 0; + handle_extended_keys(wparam as _, scancode, extended) + .map(|(vkey, scancode)| (scancode, vkey_to_tao_vkey(vkey))) +} + +// This is needed as windows doesn't properly distinguish +// some virtual key codes for different keyboard layouts +fn map_text_keys(win_virtual_key: i32) -> Option { + let char_key = unsafe { MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF; + match char::from_u32(char_key) { + Some(';') => Some(VirtualKeyCode::Semicolon), + Some('/') => Some(VirtualKeyCode::Slash), + Some('`') => Some(VirtualKeyCode::Grave), + Some('[') => Some(VirtualKeyCode::LBracket), + Some(']') => Some(VirtualKeyCode::RBracket), + Some('\'') => Some(VirtualKeyCode::Apostrophe), + Some('\\') => Some(VirtualKeyCode::Backslash), + _ => None, + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs new file mode 100644 index 000000000..2d8859767 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs @@ -0,0 +1,2586 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![allow(non_snake_case)] + +mod runner; + +use crossbeam_channel::{self as channel, Receiver, Sender}; +use parking_lot::Mutex; +use std::{ + cell::Cell, + collections::VecDeque, + ffi::c_void, + marker::PhantomData, + mem, panic, + rc::Rc, + sync::{Arc, LazyLock}, + thread, + time::{Duration, Instant}, +}; +use windows::{ + core::{s, w, BOOL, PCWSTR}, + Win32::{ + Foundation::{HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM}, + Graphics::Gdi::*, + System::{ + Ole::{IDropTarget, RevokeDragDrop}, + Threading::{GetCurrentThreadId, INFINITE}, + }, + UI::{ + Controls::{self as win32c, HOVER_DEFAULT}, + Input::{KeyboardAndMouse::*, Pointer::*, Touch::*, *}, + Shell::{ + SHAppBarMessage, ABE_BOTTOM, ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, + }, + WindowsAndMessaging::{self as win32wm, *}, + }, + }, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, PixelUnit}, + error::ExternalError, + event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, + event_loop::{ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW}, + keyboard::{KeyCode, ModifiersState}, + monitor::MonitorHandle as RootMonitorHandle, + platform_impl::platform::{ + dark_mode::try_window_theme, + dpi::{become_dpi_aware, dpi_to_scale_factor}, + ime::ImeContext, + keyboard::KeyEventBuilder, + keyboard_layout::get_agnostic_mods, + monitor::{self, MonitorHandle}, + raw_input, util, + window::{set_skip_taskbar, InitData}, + window_state::{CursorFlags, WindowFlags, WindowState}, + wrap_device_id, WindowId, DEVICE_ID, + }, + window::{Fullscreen, Theme, WindowId as RootWindowId}, +}; +use runner::{EventLoopRunner, EventLoopRunnerShared}; + +use super::{dpi::hwnd_dpi, util::get_system_metrics_for_dpi}; + +// This is defined in `winuser.h` as a macro that expands to `UINT_MAX` +const WHEEL_PAGESCROLL: u32 = u32::MAX; +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLLINES +const DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA: isize = 3; +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLCHARS +const DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA: isize = 3; + +type GetPointerFrameInfoHistory = unsafe extern "system" fn( + pointerId: u32, + entriesCount: *mut u32, + pointerCount: *mut u32, + pointerInfo: *mut POINTER_INFO, +) -> BOOL; + +type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL; + +type GetPointerDeviceRects = unsafe extern "system" fn( + device: HANDLE, + pointerDeviceRect: *mut RECT, + displayRect: *mut RECT, +) -> BOOL; + +type GetPointerTouchInfo = + unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL; + +type GetPointerPenInfo = + unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; + +static GET_POINTER_FRAME_INFO_HISTORY: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); +static SKIP_POINTER_FRAME_MESSAGES: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); +static GET_POINTER_DEVICE_RECTS: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects)); +static GET_POINTER_TOUCH_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +static GET_POINTER_PEN_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo)); + +pub(crate) struct WindowData { + pub window_state: Arc>, + pub event_loop_runner: EventLoopRunnerShared, + pub key_event_builder: KeyEventBuilder, + pub _file_drop_handler: Option, + pub userdata_removed: Cell, + pub recurse_depth: Cell, + pub event_loop_preferred_theme: Arc>>, +} + +impl WindowData { + unsafe fn send_event(&self, event: Event<'_, T>) { + self.event_loop_runner.send_event(event); + } +} + +struct ThreadMsgTargetData { + event_loop_runner: EventLoopRunnerShared, + user_event_receiver: Receiver, +} + +impl ThreadMsgTargetData { + unsafe fn send_event(&self, event: Event<'_, T>) { + self.event_loop_runner.send_event(event); + } +} + +/// The result of a subclass procedure (the message handling callback) +pub(crate) enum ProcResult { + DefWindowProc, // <- this should be the default value + Value(LRESULT), +} + +pub struct EventLoop { + thread_msg_sender: Sender, + window_target: RootELW, + msg_hook: Option bool + 'static>>, +} + +pub(crate) struct PlatformSpecificEventLoopAttributes { + pub(crate) any_thread: bool, + pub(crate) dpi_aware: bool, + pub(crate) msg_hook: Option bool + 'static>>, + pub(crate) preferred_theme: Option, +} + +impl Default for PlatformSpecificEventLoopAttributes { + fn default() -> Self { + Self { + any_thread: false, + dpi_aware: true, + msg_hook: None, + preferred_theme: None, + } + } +} + +#[derive(Clone)] +pub struct EventLoopWindowTarget { + thread_id: u32, + thread_msg_target: HWND, + pub(crate) preferred_theme: Arc>>, + pub(crate) runner_shared: EventLoopRunnerShared, +} + +impl EventLoop { + pub(crate) fn new(attributes: &mut PlatformSpecificEventLoopAttributes) -> EventLoop { + let thread_id = unsafe { GetCurrentThreadId() }; + + if !attributes.any_thread && thread_id != main_thread_id() { + panic!( + "Initializing the event loop outside of the main thread is a significant \ + cross-platform compatibility hazard. If you absolutely need to create an \ + EventLoop on a different thread, you can use the \ + `EventLoopBuilderExtWindows::any_thread` function." + ); + } + + if attributes.dpi_aware { + become_dpi_aware(); + } + + let thread_msg_target = create_event_target_window::(); + + super::dark_mode::allow_dark_mode_for_app(true); + + let send_thread_msg_target = thread_msg_target.0 as isize; + thread::spawn(move || wait_thread(thread_id, HWND(send_thread_msg_target as _))); + let wait_thread_id = get_wait_thread_id(); + + let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + + let thread_msg_sender = + insert_event_target_window_data::(thread_msg_target, runner_shared.clone()); + raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); + + EventLoop { + thread_msg_sender, + window_target: RootELW { + p: EventLoopWindowTarget { + thread_id, + thread_msg_target, + runner_shared, + preferred_theme: Arc::new(Mutex::new(attributes.preferred_theme)), + }, + _marker: PhantomData, + }, + msg_hook: attributes.msg_hook.take(), + } + } + + pub fn window_target(&self) -> &RootELW { + &self.window_target + } + + pub fn run(mut self, event_handler: F) -> ! + where + F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let exit_code = self.run_return(event_handler); + ::std::process::exit(exit_code); + } + + pub fn run_return(&mut self, mut event_handler: F) -> i32 + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let event_loop_windows_ref = &self.window_target; + + unsafe { + self + .window_target + .p + .runner_shared + .set_event_handler(move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow); + }); + } + + let runner = &self.window_target.p.runner_shared; + + let exit_code = unsafe { + let mut msg = MSG::default(); + + runner.poll(); + 'main: loop { + if !GetMessageW(&mut msg, None, 0, 0).as_bool() { + break 'main 0; + } + + let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { + callback(&mut msg as *mut _ as *mut _) + } else { + false + }; + if !handled { + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); + } + + if let ControlFlow::ExitWithCode(code) = runner.control_flow() { + if !runner.handling_events() { + break 'main code; + } + } + } + }; + + unsafe { + runner.loop_destroyed(); + } + runner.reset_runner(); + exit_code + } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + target_window: self.window_target.p.thread_msg_target, + event_send: self.thread_msg_sender.clone(), + } + } +} + +impl EventLoopWindowTarget { + #[inline(always)] + pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor { + EventLoopThreadExecutor { + thread_id: self.thread_id, + target_window: self.thread_msg_target, + } + } + + // TODO: Investigate opportunities for caching + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) + } + + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + monitor::from_point(x, y) + } + + #[cfg(feature = "rwh_05")] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::Windows( + rwh_06::WindowsDisplayHandle::new(), + )) + } + + pub fn set_device_event_filter(&self, filter: DeviceEventFilter) { + raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, filter); + } + + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position().map_err(Into::into) + } + + #[inline] + pub fn set_theme(&self, theme: Option) { + *self.preferred_theme.lock() = theme; + self.runner_shared.owned_windows(|window| { + let _ = unsafe { SendMessageW(window, *CHANGE_THEME_MSG_ID, None, None) }; + }); + } +} + +fn main_thread_id() -> u32 { + static mut MAIN_THREAD_ID: u32 = 0; + #[used] + #[allow(non_upper_case_globals)] + #[link_section = ".CRT$XCU"] + static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = { + unsafe extern "C" fn initer() { + MAIN_THREAD_ID = GetCurrentThreadId(); + } + initer + }; + + unsafe { MAIN_THREAD_ID } +} + +fn get_wait_thread_id() -> u32 { + unsafe { + let mut msg = MSG::default(); + let result = GetMessageW( + &mut msg, + None, + *SEND_WAIT_THREAD_ID_MSG_ID, + *SEND_WAIT_THREAD_ID_MSG_ID, + ); + assert_eq!( + msg.message, *SEND_WAIT_THREAD_ID_MSG_ID, + "this shouldn't be possible. please open an issue with Tauri. error code: {}", + result.0 + ); + msg.lParam.0 as u32 + } +} + +fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { + unsafe { + let mut msg: MSG; + + let cur_thread_id = GetCurrentThreadId(); + let _ = PostThreadMessageW( + parent_thread_id, + *SEND_WAIT_THREAD_ID_MSG_ID, + WPARAM(0), + LPARAM(cur_thread_id as _), + ); + + let mut wait_until_opt = None; + 'main: loop { + // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get + // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't + // additional messages to process. + msg = MSG::default(); + + if wait_until_opt.is_some() { + if PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() { + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } else if !GetMessageW(&mut msg, None, 0, 0).as_bool() { + break 'main; + } else { + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + } + + if msg.message == *WAIT_UNTIL_MSG_ID { + wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam.0 as *mut _)); + } else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID { + wait_until_opt = None; + } + + if let Some(wait_until) = wait_until_opt { + let now = Instant::now(); + if now < wait_until { + // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract + // 1 millisecond from the requested time and spinlock for the remainder to + // compensate for that. + let resume_reason = MsgWaitForMultipleObjectsEx( + None, + dur2timeout(wait_until - now).saturating_sub(1), + QS_ALLEVENTS, + MWMO_INPUTAVAILABLE, + ); + if resume_reason == WAIT_TIMEOUT { + let _ = PostMessageW( + Some(msg_window_id), + *PROCESS_NEW_EVENTS_MSG_ID, + WPARAM(0), + LPARAM(0), + ); + wait_until_opt = None; + } + } else { + let _ = PostMessageW( + Some(msg_window_id), + *PROCESS_NEW_EVENTS_MSG_ID, + WPARAM(0), + LPARAM(0), + ); + wait_until_opt = None; + } + } + } + } +} + +// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs +fn dur2timeout(dur: Duration) -> u32 { + // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the + // timeouts in windows APIs are typically u32 milliseconds. To translate, we + // have two pieces to take care of: + // + // * Nanosecond precision is rounded up + // * Greater than u32::MAX milliseconds (50 days) is rounded up to INFINITE + // (never time out). + dur + .as_secs() + .checked_mul(1000) + .and_then(|ms| ms.checked_add((dur.subsec_nanos() as u64) / 1_000_000)) + .and_then(|ms| { + ms.checked_add(if dur.subsec_nanos() % 1_000_000 > 0 { + 1 + } else { + 0 + }) + }) + .map(|ms| { + if ms > u32::MAX as u64 { + INFINITE + } else { + ms as u32 + } + }) + .unwrap_or(INFINITE) +} + +impl Drop for EventLoop { + fn drop(&mut self) { + unsafe { + let _ = DestroyWindow(self.window_target.p.thread_msg_target); + } + } +} + +pub(crate) struct EventLoopThreadExecutor { + thread_id: u32, + target_window: HWND, +} + +unsafe impl Send for EventLoopThreadExecutor {} +unsafe impl Sync for EventLoopThreadExecutor {} + +impl EventLoopThreadExecutor { + /// Check to see if we're in the parent event loop's thread. + pub(super) fn in_event_loop_thread(&self) -> bool { + let cur_thread_id = unsafe { GetCurrentThreadId() }; + self.thread_id == cur_thread_id + } + + /// Executes a function in the event loop thread. If we're already in the event loop thread, + /// we just call the function directly. + /// + /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is + /// removed automatically if the callback receives a `WM_CLOSE` message for the window. + /// + /// Note that if you are using this to change some property of a window and updating + /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the + /// events may be sent to the other thread in different order to the one in which you set + /// `WindowState`, leaving them out of sync. + /// + /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent + /// to the unstable FnBox. + pub(super) fn execute_in_thread(&self, mut function: F) + where + F: FnMut() + Send + 'static, + { + unsafe { + if self.in_event_loop_thread() { + function(); + } else { + // We double-box because the first box is a fat pointer. + let boxed = Box::new(function) as Box; + let boxed2: ThreadExecFn = Box::new(boxed); + + let raw = Box::into_raw(boxed2); + + let result = PostMessageW( + Some(self.target_window), + *EXEC_MSG_ID, + WPARAM(raw as _), + LPARAM(0), + ); + // This can happen when we outlive the parent [`EventLoopWindowTarget`] at which point the `target_window` has already been destroyed + if let Err(error) = result { + log::error!( + "PostMessage failed ; is the messages queue full? Error code {} - {}", + error.code(), + error.message() + ); + } + } + } + } +} + +type ThreadExecFn = Box>; + +pub struct EventLoopProxy { + target_window: HWND, + event_send: Sender, +} +unsafe impl Send for EventLoopProxy {} +unsafe impl Sync for EventLoopProxy {} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + target_window: self.target_window, + event_send: self.event_send.clone(), + } + } +} + +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + unsafe { + if PostMessageW( + Some(self.target_window), + *USER_EVENT_MSG_ID, + WPARAM(0), + LPARAM(0), + ) + .is_ok() + { + self.event_send.send(event).ok(); + Ok(()) + } else { + Err(EventLoopClosed(event)) + } + } + } +} + +type WaitUntilInstantBox = Box; + +/// Message sent by the `EventLoopProxy` when we want to wake up the thread. +/// WPARAM and LPARAM are unused. +static USER_EVENT_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); +/// Message sent when we want to execute a closure in the thread. +/// WPARAM contains a Box> that must be retrieved with `Box::from_raw`, +/// and LPARAM is unused. +static EXEC_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); +static PROCESS_NEW_EVENTS_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); +/// lparam is the wait thread's message id. +static SEND_WAIT_THREAD_ID_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); +/// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should +/// be sent. +static WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); +static CANCEL_WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); +/// Message sent by a `Window` when it wants to be destroyed by the main thread. +/// WPARAM and LPARAM are unused. +pub static DESTROY_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); +/// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the +/// documentation in the `window_state` module for more information. +pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); +/// Message sent by event loop when event loop's prefered theme changed. +/// WPARAM and LPARAM are unused. +pub static CHANGE_THEME_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); +/// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows +/// When the application receives this message, it should assume that any taskbar icons it added have been removed and add them again. +pub static S_U_TASKBAR_RESTART: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); + +fn create_event_target_window() -> HWND { + let thread_event_target_window_class = w!("Tao Thread Event Target"); + + unsafe { + let class = WNDCLASSEXW { + cbSize: mem::size_of::() as u32, + style: Default::default(), + lpfnWndProc: Some(thread_event_target_callback::), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: util::get_instance_handle(), + hIcon: HICON::default(), + hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly + hbrBackground: HBRUSH::default(), + lpszMenuName: PCWSTR::null(), + lpszClassName: thread_event_target_window_class, + hIconSm: HICON::default(), + }; + + RegisterClassExW(&class); + } + + let window = unsafe { + CreateWindowExW( + WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | + // WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which + // we want to avoid. If you remove this style, this window won't show up in the + // taskbar *initially*, but it can show up at some later point. This can sometimes + // happen on its own after several hours have passed, although this has proven + // difficult to reproduce. Alternatively, it can be manually triggered by killing + // `explorer.exe` and then starting the process back up. + // It is unclear why the bug is triggered by waiting for several hours. + WS_EX_TOOLWINDOW, + thread_event_target_window_class, + PCWSTR::null(), + WS_OVERLAPPED, + 0, + 0, + 0, + 0, + None, + None, + Some(util::get_instance_handle()), + None, + ) + }; + + let window = match window { + Ok(w) => w, + Err(_) => return HWND::default(), + }; + + util::SetWindowLongPtrW( + window, + GWL_STYLE, + // The window technically has to be visible to receive WM_PAINT messages (which are used + // for delivering events during resizes), but it isn't displayed to the user because of + // the LAYERED style. + (WS_VISIBLE | WS_POPUP).0 as isize, + ); + window +} + +fn insert_event_target_window_data( + thread_msg_target: HWND, + event_loop_runner: EventLoopRunnerShared, +) -> Sender { + let (tx, rx) = channel::unbounded(); + + let userdata = ThreadMsgTargetData { + event_loop_runner, + user_event_receiver: rx, + }; + let input_ptr = Box::into_raw(Box::new(userdata)); + + util::SetWindowLongPtrW(thread_msg_target, GWL_USERDATA, input_ptr as isize); + + tx +} + +/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of +/// the window. +unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) { + window_state.mouse.capture_count += 1; + SetCapture(window); +} + +/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor +/// is outside the window. +unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowState>) { + window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1); + if window_state.mouse.capture_count == 0 { + // ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state. + drop(window_state); + let _ = ReleaseCapture(); + } +} + +fn normalize_pointer_pressure(pressure: u32) -> Option { + match pressure { + 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), + _ => None, + } +} + +/// Flush redraw events for Tao's windows. +/// +/// Tao's API guarantees that all redraw events will be clustered together and dispatched all at +/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple +/// windows have had redraws scheduled, but an input event is pushed to the message queue between +/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows +/// will dispatch the input event immediately instead of flushing all the redraw events. This +/// function explicitly pulls all of Tao's redraw events out of the event queue so that they +/// always all get processed in one fell swoop. +/// +/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, +/// it won't flush the redraw events and will return `false`. +#[must_use] +unsafe fn flush_paint_messages( + except: Option, + runner: &EventLoopRunner, +) -> bool { + if !runner.redrawing() { + runner.main_events_cleared(); + let mut msg = MSG::default(); + runner.owned_windows(|redraw_window| { + if Some(redraw_window) == except { + return; + } + + if !PeekMessageW( + &mut msg, + Some(redraw_window), + WM_PAINT, + WM_PAINT, + PM_REMOVE | PM_QS_PAINT, + ) + .as_bool() + { + return; + } + + let _ = TranslateMessage(&msg); + DispatchMessageW(&msg); + }); + true + } else { + false + } +} + +unsafe fn process_control_flow(runner: &EventLoopRunner) { + match runner.control_flow() { + ControlFlow::Poll => { + let _ = PostMessageW( + Some(runner.thread_msg_target()), + *PROCESS_NEW_EVENTS_MSG_ID, + WPARAM(0), + LPARAM(0), + ); + } + ControlFlow::Wait => (), + ControlFlow::WaitUntil(until) => { + let _ = PostThreadMessageW( + runner.wait_thread_id(), + *WAIT_UNTIL_MSG_ID, + WPARAM(0), + LPARAM(Box::into_raw(WaitUntilInstantBox::new(until)) as _), + ); + } + ControlFlow::ExitWithCode(_) => (), + } +} + +/// Emit a `ModifiersChanged` event whenever modifiers have changed. +/// Returns the current modifier state +fn update_modifiers(window: HWND, useredata: &WindowData) -> ModifiersState { + use crate::event::WindowEvent::ModifiersChanged; + + let modifiers = get_agnostic_mods(); + let mut window_state = useredata.window_state.lock(); + if window_state.modifiers_state != modifiers { + window_state.modifiers_state = modifiers; + + // Drop lock + drop(window_state); + + unsafe { + useredata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: ModifiersChanged(modifiers), + }); + } + } + modifiers +} + +unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::WindowEvent::Focused; + update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: Focused(true), + }); +} + +unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::WindowEvent::{Focused, ModifiersChanged}; + + userdata.window_state.lock().modifiers_state = ModifiersState::empty(); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: ModifiersChanged(ModifiersState::empty()), + }); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: Focused(false), + }); +} + +/// Any window whose callback is configured to this function will have its events propagated +/// through the events loop of the thread the window was created in. +// +// This is the callback that is called by `DispatchMessage` in the events loop. +// +// Returning 0 tells the Win32 API that the message has been processed. +// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary +pub(super) unsafe extern "system" fn public_window_callback( + window: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); + let userdata_ptr = match (userdata, msg) { + (0, WM_NCCREATE) => { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); + + return match unsafe { initdata.on_nccreate(window) } { + Some(userdata) => unsafe { + util::SetWindowLongPtrW(window, GWL_USERDATA, userdata); + DefWindowProcW(window, msg, wparam, lparam) + }, + None => LRESULT(-1), // failed to create the window + }; + } + // Getting here should quite frankly be impossible, + // but we'll make window creation fail here just in case. + (0, WM_CREATE) => return LRESULT(-1), + (_, WM_CREATE) => unsafe { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); + + initdata.on_create(); + return DefWindowProcW(window, msg, wparam, lparam); + }, + (0, _) => return DefWindowProcW(window, msg, wparam, lparam), + _ => userdata as *mut WindowData, + }; + + let (result, userdata_removed, recurse_depth) = { + let userdata = &*(userdata_ptr); + + userdata.recurse_depth.set(userdata.recurse_depth.get() + 1); + + let result = public_window_callback_inner(window, msg, wparam, lparam, userdata); + + let userdata_removed = userdata.userdata_removed.get(); + let recurse_depth = userdata.recurse_depth.get() - 1; + userdata.recurse_depth.set(recurse_depth); + + (result, userdata_removed, recurse_depth) + }; + + if userdata_removed && recurse_depth == 0 { + drop(Box::from_raw(userdata_ptr)) + } + + result +} + +unsafe fn public_window_callback_inner( + window: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + userdata: &WindowData, +) -> LRESULT { + let _ = RedrawWindow( + Some(userdata.event_loop_runner.thread_msg_target()), + None, + None, + RDW_INTERNALPAINT, + ); + + let mut result = ProcResult::DefWindowProc; + + // Send new modifiers before sending key events. + let mods_changed_callback = || match msg { + win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { + update_modifiers(window, userdata); + result = ProcResult::Value(LRESULT(0)); + } + _ => (), + }; + userdata + .event_loop_runner + .catch_unwind(mods_changed_callback) + .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); + + let keyboard_callback = || { + use crate::event::WindowEvent::KeyboardInput; + let events = + userdata + .key_event_builder + .process_message(window, msg, wparam, lparam, &mut result); + for event in events { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: KeyboardInput { + device_id: DEVICE_ID, + event: event.event, + is_synthetic: event.is_synthetic, + }, + }); + } + }; + userdata + .event_loop_runner + .catch_unwind(keyboard_callback) + .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { + win32wm::WM_ENTERSIZEMOVE => { + userdata + .window_state + .lock() + .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_EXITSIZEMOVE => { + let mut state = userdata.window_state.lock(); + if state.dragging { + state.dragging = false; + let _ = unsafe { PostMessageW(Some(window), WM_LBUTTONUP, WPARAM::default(), lparam) }; + } + state.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_NCLBUTTONDOWN => { + if wparam.0 == HTCAPTION as _ { + let _ = PostMessageW(Some(window), WM_MOUSEMOVE, WPARAM(0), lparam); + } + + use crate::event::WindowEvent::DecorationsClick; + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: DecorationsClick, + }); + } + + win32wm::WM_CLOSE => { + use crate::event::WindowEvent::CloseRequested; + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: CloseRequested, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_DESTROY => { + use crate::event::WindowEvent::Destroyed; + let _ = RevokeDragDrop(window); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: Destroyed, + }); + userdata.event_loop_runner.remove_window(window); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_NCDESTROY => { + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata.userdata_removed.set(true); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_PAINT => { + if userdata.event_loop_runner.should_buffer() { + // this branch can happen in response to `UpdateWindow`, if win32 decides to + // redraw the window outside the normal flow of the event loop. + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + } else { + let managing_redraw = flush_paint_messages(Some(window), &userdata.event_loop_runner); + userdata.send_event(Event::RedrawRequested(RootWindowId(WindowId( + window.0 as _, + )))); + if managing_redraw { + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); + } + } + } + + win32wm::WM_ERASEBKGND => { + let w = userdata.window_state.lock(); + if let Some(color) = w.background_color { + let hdc = HDC(wparam.0 as *mut _); + let mut rc = RECT::default(); + if GetClientRect(window, &mut rc).is_ok() { + let brush = CreateSolidBrush(util::RGB(color.0, color.1, color.2)); + FillRect(hdc, &rc, brush); + let _ = DeleteObject(brush.into()); + + result = ProcResult::Value(LRESULT(1)); + } else { + result = ProcResult::DefWindowProc; + } + } else { + result = ProcResult::DefWindowProc; + } + } + + win32wm::WM_WINDOWPOSCHANGING => { + let mut window_state = userdata.window_state.lock(); + + if let Some(ref mut fullscreen) = window_state.fullscreen { + let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); + let new_rect = RECT { + left: window_pos.x, + top: window_pos.y, + right: window_pos.x + window_pos.cx, + bottom: window_pos.y + window_pos.cy, + }; + + const NOMOVE_OR_NOSIZE: SET_WINDOW_POS_FLAGS = + SET_WINDOW_POS_FLAGS(SWP_NOMOVE.0 | SWP_NOSIZE.0); + + let new_rect = if (window_pos.flags & NOMOVE_OR_NOSIZE) != SET_WINDOW_POS_FLAGS::default() { + let cur_rect = util::get_window_rect(window) + .expect("Unexpected GetWindowRect failure; please report this error to tauri-apps/tao on GitHub"); + + match window_pos.flags & NOMOVE_OR_NOSIZE { + NOMOVE_OR_NOSIZE => None, + + SWP_NOMOVE => Some(RECT { + left: cur_rect.left, + top: cur_rect.top, + right: cur_rect.left + window_pos.cx, + bottom: cur_rect.top + window_pos.cy, + }), + + SWP_NOSIZE => Some(RECT { + left: window_pos.x, + top: window_pos.y, + right: window_pos.x - cur_rect.left + cur_rect.right, + bottom: window_pos.y - cur_rect.top + cur_rect.bottom, + }), + + _ => unreachable!(), + } + } else { + Some(new_rect) + }; + + if let Some(new_rect) = new_rect { + let new_monitor = MonitorFromRect(&new_rect, MONITOR_DEFAULTTONULL); + match fullscreen { + Fullscreen::Borderless(ref mut fullscreen_monitor) => { + if !new_monitor.is_invalid() + && fullscreen_monitor + .as_ref() + .map(|monitor| new_monitor != monitor.inner.hmonitor()) + .unwrap_or(true) + { + if let Ok(new_monitor_info) = monitor::get_monitor_info(new_monitor) { + let new_monitor_rect = new_monitor_info.monitorInfo.rcMonitor; + window_pos.x = new_monitor_rect.left; + window_pos.y = new_monitor_rect.top; + window_pos.cx = new_monitor_rect.right - new_monitor_rect.left; + window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; + } + *fullscreen_monitor = Some(crate::monitor::MonitorHandle { + inner: MonitorHandle::new(new_monitor), + }); + } + } + Fullscreen::Exclusive(ref video_mode) => { + let old_monitor = video_mode.video_mode.monitor.hmonitor(); + if let Ok(old_monitor_info) = monitor::get_monitor_info(old_monitor) { + let old_monitor_rect = old_monitor_info.monitorInfo.rcMonitor; + window_pos.x = old_monitor_rect.left; + window_pos.y = old_monitor_rect.top; + window_pos.cx = old_monitor_rect.right - old_monitor_rect.left; + window_pos.cy = old_monitor_rect.bottom - old_monitor_rect.top; + } + } + } + } + } + + let window_flags = window_state.window_flags; + if window_flags.contains(WindowFlags::ALWAYS_ON_BOTTOM) { + let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); + window_pos.hwndInsertAfter = HWND_BOTTOM; + } + + result = ProcResult::Value(LRESULT(0)); + } + + // WM_MOVE supplies client area positions, so we send Moved here instead. + win32wm::WM_WINDOWPOSCHANGED => { + use crate::event::WindowEvent::Moved; + + let windowpos = lparam.0 as *const WINDOWPOS; + if (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE { + let physical_position = PhysicalPosition::new((*windowpos).x, (*windowpos).y); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: Moved(physical_position), + }); + } + + // This is necessary for us to still get sent WM_SIZE. + result = ProcResult::DefWindowProc; + } + + win32wm::WM_SIZE => { + use crate::event::WindowEvent::Resized; + let w = u32::from(util::LOWORD(lparam.0 as u32)); + let h = u32::from(util::HIWORD(lparam.0 as u32)); + + let physical_size = PhysicalSize::new(w, h); + let event = Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: Resized(physical_size), + }; + + { + let mut w = userdata.window_state.lock(); + // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. + if !w + .window_flags() + .contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) + { + let maximized = wparam.0 == win32wm::SIZE_MAXIMIZED as _; + w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); + } + } + + userdata.send_event(event); + result = ProcResult::Value(LRESULT(0)); + } + + // TODO: Catch up with winit on the IME events, + // `WindowEvent::ReceivedImeText` currently maps to `WindowEvent::Ime(Ime::Commit(text))` + win32wm::WM_IME_ENDCOMPOSITION => { + let ime_context = unsafe { ImeContext::current(window) }; + if let Some(text) = unsafe { ime_context.get_composed_text() } { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::ReceivedImeText(text), + }); + } + } + + // this is necessary for us to maintain minimize/restore state + win32wm::WM_SYSCOMMAND => { + if wparam.0 == SC_RESTORE as _ { + let mut w = userdata.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); + } + if wparam.0 == SC_MINIMIZE as _ { + let mut w = userdata.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); + } + // Send `WindowEvent::Minimized` here if we decide to implement one + + if wparam.0 == SC_SCREENSAVE as _ { + let window_state = userdata.window_state.lock(); + if window_state.fullscreen.is_some() { + result = ProcResult::Value(LRESULT(0)); + return; + } + } + + result = ProcResult::DefWindowProc; + } + + win32wm::WM_MOUSEMOVE => { + use crate::event::WindowEvent::{CursorEntered, CursorMoved}; + let mouse_was_outside_window = { + let mut w = userdata.window_state.lock(); + + let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); + w.mouse + .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)) + .ok(); + was_outside_window + }; + + if mouse_was_outside_window { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: CursorEntered { + device_id: DEVICE_ID, + }, + }); + + // Calling TrackMouseEvent in order to receive mouse leave events. + let _ = TrackMouseEvent(&mut TRACKMOUSEEVENT { + cbSize: mem::size_of::() as u32, + dwFlags: TME_LEAVE, + hwndTrack: window, + dwHoverTime: HOVER_DEFAULT, + }); + } + + let x = f64::from(util::GET_X_LPARAM(lparam)); + let y = f64::from(util::GET_Y_LPARAM(lparam)); + let position = PhysicalPosition::new(x, y); + let cursor_moved; + { + // handle spurious WM_MOUSEMOVE messages + // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 + // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html + let mut w = userdata.window_state.lock(); + cursor_moved = w.mouse.last_position != Some(position); + w.mouse.last_position = Some(position); + } + if cursor_moved { + let modifiers = update_modifiers(window, userdata); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: CursorMoved { + device_id: DEVICE_ID, + position, + modifiers, + }, + }); + } + + result = ProcResult::Value(LRESULT(0)); + } + + win32c::WM_MOUSELEAVE => { + use crate::event::WindowEvent::CursorLeft; + { + let mut w = userdata.window_state.lock(); + w.mouse + .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) + .ok(); + } + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: CursorLeft { + device_id: DEVICE_ID, + }, + }); + + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_MOUSEWHEEL => { + use crate::event::MouseScrollDelta::LineDelta; + + let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); + let value = value / WHEEL_DELTA as f32; + + let modifiers = update_modifiers(window, userdata); + + let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; + + let _ = SystemParametersInfoW( + SPI_GETWHEELSCROLLLINES, + 0, + Some(&mut scroll_lines as *mut isize as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), + ); + + if scroll_lines as u32 == WHEEL_PAGESCROLL { + // TODO: figure out how to handle page scrolls + scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; + } + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta: LineDelta(0.0, value * scroll_lines as f32), + phase: TouchPhase::Moved, + modifiers, + }, + }); + + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_MOUSEHWHEEL => { + use crate::event::MouseScrollDelta::LineDelta; + + let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); + let value = value / WHEEL_DELTA as f32; + + let modifiers = update_modifiers(window, userdata); + + let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA; + + let _ = SystemParametersInfoW( + SPI_GETWHEELSCROLLCHARS, + 0, + Some(&mut scroll_characters as *mut isize as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), + ); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta: LineDelta(value * scroll_characters as f32, 0.0), + phase: TouchPhase::Moved, + modifiers, + }, + }); + + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { + if msg == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { + result = ProcResult::DefWindowProc; + } + } + + win32wm::WM_LBUTTONDOWN => { + use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; + + capture_mouse(window, &mut userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Pressed, + button: Left, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_LBUTTONUP => { + use crate::event::{ElementState::Released, MouseButton::Left, WindowEvent::MouseInput}; + + release_mouse(userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Released, + button: Left, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_RBUTTONDOWN => { + use crate::event::{ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput}; + + capture_mouse(window, &mut userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Pressed, + button: Right, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_RBUTTONUP => { + use crate::event::{ElementState::Released, MouseButton::Right, WindowEvent::MouseInput}; + + release_mouse(userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Released, + button: Right, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_MBUTTONDOWN => { + use crate::event::{ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput}; + + capture_mouse(window, &mut userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Pressed, + button: Middle, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_MBUTTONUP => { + use crate::event::{ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput}; + + release_mouse(userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Released, + button: Middle, + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_XBUTTONDOWN => { + use crate::event::{ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput}; + let xbutton = util::GET_XBUTTON_WPARAM(wparam); + + capture_mouse(window, &mut userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Pressed, + button: Other(xbutton), + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_XBUTTONUP => { + use crate::event::{ElementState::Released, MouseButton::Other, WindowEvent::MouseInput}; + let xbutton = util::GET_XBUTTON_WPARAM(wparam); + + release_mouse(userdata.window_state.lock()); + + let modifiers = update_modifiers(window, userdata); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: MouseInput { + device_id: DEVICE_ID, + state: Released, + button: Other(xbutton), + modifiers, + }, + }); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_CAPTURECHANGED => { + // lparam here is a handle to the window which is gaining mouse capture. + // If it is the same as our window, then we're essentially retaining the capture. This + // can happen if `SetCapture` is called on our window when it already has the mouse + // capture. + if lparam.0 != window.0 as _ { + userdata.window_state.lock().mouse.capture_count = 0; + } + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_TOUCH => { + let pcount = usize::from(util::LOWORD(wparam.0 as u32)); + let mut inputs: Vec = Vec::with_capacity(pcount); + let uninit_inputs = inputs.spare_capacity_mut(); + let htouch = HTOUCHINPUT(lparam.0 as _); + if GetTouchInputInfo( + htouch, + mem::transmute::< + &mut [std::mem::MaybeUninit], + &mut [windows::Win32::UI::Input::Touch::TOUCHINPUT], + >(uninit_inputs), + mem::size_of::() as i32, + ) + .is_ok() + { + inputs.set_len(pcount); + for input in &inputs { + let mut location = POINT { + x: input.x / 100, + y: input.y / 100, + }; + + if !ScreenToClient(window, &mut location as *mut _).as_bool() { + continue; + } + + let x = location.x as f64 + (input.x % 100) as f64 / 100f64; + let y = location.y as f64 + (input.y % 100) as f64 / 100f64; + let location = PhysicalPosition::new(x, y); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::Touch(Touch { + phase: if (input.dwFlags & TOUCHEVENTF_DOWN) != Default::default() { + TouchPhase::Started + } else if (input.dwFlags & TOUCHEVENTF_UP) != Default::default() { + TouchPhase::Ended + } else if (input.dwFlags & TOUCHEVENTF_MOVE) != Default::default() { + TouchPhase::Moved + } else { + continue; + }, + location, + force: None, // WM_TOUCH doesn't support pressure information + id: input.dwID as u64, + device_id: DEVICE_ID, + }), + }); + } + } + let _ = CloseTouchInputHandle(htouch); + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_POINTERDOWN | win32wm::WM_POINTERUPDATE | win32wm::WM_POINTERUP => { + if let ( + Some(GetPointerFrameInfoHistory), + Some(SkipPointerFrameMessages), + Some(GetPointerDeviceRects), + ) = ( + *GET_POINTER_FRAME_INFO_HISTORY, + *SKIP_POINTER_FRAME_MESSAGES, + *GET_POINTER_DEVICE_RECTS, + ) { + let pointer_id = u32::from(util::LOWORD(wparam.0 as u32)); + let mut entries_count = 0_u32; + let mut pointers_count = 0_u32; + if !GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + std::ptr::null_mut(), + ) + .as_bool() + { + result = ProcResult::Value(LRESULT(0)); + return; + } + + let pointer_info_count = (entries_count * pointers_count) as usize; + let mut pointer_infos: Vec = Vec::with_capacity(pointer_info_count); + let uninit_pointer_infos = pointer_infos.spare_capacity_mut(); + if !GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + uninit_pointer_infos.as_mut_ptr() as *mut _, + ) + .as_bool() + { + result = ProcResult::Value(LRESULT(0)); + return; + } + pointer_infos.set_len(pointer_info_count); + + // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory + // The information retrieved appears in reverse chronological order, with the most recent entry in the first + // row of the returned array + for pointer_info in pointer_infos.iter().rev() { + let mut device_rect = mem::MaybeUninit::uninit(); + let mut display_rect = mem::MaybeUninit::uninit(); + + if !(GetPointerDeviceRects( + pointer_info.sourceDevice, + device_rect.as_mut_ptr(), + display_rect.as_mut_ptr(), + )) + .as_bool() + { + continue; + } + + let device_rect = device_rect.assume_init(); + let display_rect = display_rect.assume_init(); + + // For the most precise himetric to pixel conversion we calculate the ratio between the resolution + // of the display device (pixel) and the touch device (himetric). + let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64 + / (device_rect.right - device_rect.left) as f64; + let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64 + / (device_rect.bottom - device_rect.top) as f64; + + // ptHimetricLocation's origin is 0,0 even on multi-monitor setups. + // On multi-monitor setups we need to translate the himetric location to the rect of the + // display device it's attached to. + let x = display_rect.left as f64 + + pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x; + let y = display_rect.top as f64 + + pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y; + + let mut location = POINT { + x: x.floor() as i32, + y: y.floor() as i32, + }; + + if !ScreenToClient(window, &mut location as *mut _).as_bool() { + continue; + } + + let force = match pointer_info.pointerType { + win32wm::PT_TOUCH => { + let mut touch_info = mem::MaybeUninit::uninit(); + GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { + if GetPointerTouchInfo(pointer_info.pointerId, touch_info.as_mut_ptr()).as_bool() { + normalize_pointer_pressure(touch_info.assume_init().pressure) + } else { + None + } + }) + } + win32wm::PT_PEN => { + let mut pen_info = mem::MaybeUninit::uninit(); + GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| { + if GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr()).as_bool() { + normalize_pointer_pressure(pen_info.assume_init().pressure) + } else { + None + } + }) + } + _ => None, + }; + + let x = location.x as f64 + x.fract(); + let y = location.y as f64 + y.fract(); + let location = PhysicalPosition::new(x, y); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::Touch(Touch { + phase: if (pointer_info.pointerFlags & POINTER_FLAG_DOWN) != Default::default() { + TouchPhase::Started + } else if (pointer_info.pointerFlags & POINTER_FLAG_UP) != Default::default() { + TouchPhase::Ended + } else if (pointer_info.pointerFlags & POINTER_FLAG_UPDATE) != Default::default() { + TouchPhase::Moved + } else { + continue; + }, + location, + force, + id: pointer_info.pointerId as u64, + device_id: DEVICE_ID, + }), + }); + } + + let _ = SkipPointerFrameMessages(pointer_id); + } + + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_NCACTIVATE => { + let is_active = wparam != WPARAM(0); + let active_focus_changed = userdata.window_state.lock().set_active(is_active); + if active_focus_changed { + if is_active { + gain_active_focus(window, userdata); + } else { + lose_active_focus(window, userdata); + } + } + result = ProcResult::DefWindowProc; + } + + win32wm::WM_SETFOCUS => { + let active_focus_changed = userdata.window_state.lock().set_focused(true); + if active_focus_changed { + gain_active_focus(window, userdata); + } + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_KILLFOCUS => { + let active_focus_changed = userdata.window_state.lock().set_focused(false); + if active_focus_changed { + lose_active_focus(window, userdata); + } + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_SETCURSOR => { + let set_cursor_to = { + let window_state = userdata.window_state.lock(); + // The return value for the preceding `WM_NCHITTEST` message is conveniently + // provided through the low-order word of lParam. We use that here since + // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. + let in_client_area = u32::from(util::LOWORD(lparam.0 as u32)) == HTCLIENT; + if in_client_area { + Some(window_state.mouse.cursor) + } else { + None + } + }; + + match set_cursor_to { + Some(cursor) => { + if let Ok(cursor) = LoadCursorW(None, cursor.to_windows_cursor()) { + SetCursor(Some(cursor)); + } + result = ProcResult::Value(LRESULT(0)); + } + None => result = ProcResult::DefWindowProc, + } + } + + win32wm::WM_GETMINMAXINFO => { + let mmi = lparam.0 as *mut MINMAXINFO; + + let window_state = userdata.window_state.lock(); + let is_decorated = window_state + .window_flags() + .contains(WindowFlags::MARKER_DECORATIONS); + + let size_constraints = window_state.size_constraints; + + if size_constraints.has_min() { + let min_size = PhysicalSize::new( + size_constraints + .min_width + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMINTRACK).into())) + .to_physical(window_state.scale_factor) + .0, + size_constraints + .min_height + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMINTRACK).into())) + .to_physical(window_state.scale_factor) + .0, + ); + let (width, height): (u32, u32) = util::adjust_size(window, min_size, is_decorated).into(); + (*mmi).ptMinTrackSize = POINT { + x: width as i32, + y: height as i32, + }; + } + if size_constraints.has_max() { + let max_size = PhysicalSize::new( + size_constraints + .max_width + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMAXTRACK).into())) + .to_physical(window_state.scale_factor) + .0, + size_constraints + .max_height + .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMAXTRACK).into())) + .to_physical(window_state.scale_factor) + .0, + ); + let (width, height): (u32, u32) = util::adjust_size(window, max_size, is_decorated).into(); + (*mmi).ptMaxTrackSize = POINT { + x: width as i32, + y: height as i32, + }; + } + + result = ProcResult::Value(LRESULT(0)); + } + + // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change + // DPI, therefore all applications are closed while DPI is changing. + win32wm::WM_DPICHANGED => { + use crate::event::WindowEvent::ScaleFactorChanged; + + // This message actually provides two DPI values - x and y. However MSDN says that + // "you only need to use either the X-axis or the Y-axis value when scaling your + // application since they are the same". + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx + let new_dpi_x = u32::from(util::LOWORD(wparam.0 as u32)); + let new_scale_factor = dpi_to_scale_factor(new_dpi_x); + let old_scale_factor: f64; + + let (allow_resize, is_decorated) = { + let mut window_state = userdata.window_state.lock(); + old_scale_factor = window_state.scale_factor; + window_state.scale_factor = new_scale_factor; + + if (new_scale_factor - old_scale_factor).abs() < f64::EPSILON { + result = ProcResult::Value(LRESULT(0)); + return; + } + + let window_flags = window_state.window_flags(); + ( + window_state.fullscreen.is_none() && !window_flags.contains(WindowFlags::MAXIMIZED), + window_flags.contains(WindowFlags::MARKER_DECORATIONS), + ) + }; + + let mut style = WINDOW_STYLE(GetWindowLongW(window, GWL_STYLE) as u32); + // if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so + // `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and + // calculates a correct size for the client area. + if !is_decorated { + style &= !WS_CAPTION; + style &= !WS_SIZEBOX; + } + let style_ex = WINDOW_EX_STYLE(GetWindowLongW(window, GWL_EXSTYLE) as u32); + + // New size as suggested by Windows. + let suggested_rect = *(lparam.0 as *const RECT); + + // The window rect provided is the window's outer size, not it's inner size. However, + // win32 doesn't provide an `UnadjustWindowRectEx` function to get the client rect from + // the outer rect, so we instead adjust the window rect to get the decoration margins + // and remove them from the outer size. + let margin_left: i32; + let margin_top: i32; + // let margin_right: i32; + // let margin_bottom: i32; + { + let adjusted_rect = + util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect) + .unwrap_or(suggested_rect); + margin_left = suggested_rect.left - adjusted_rect.left; + margin_top = suggested_rect.top - adjusted_rect.top; + // margin_right = adjusted_rect.right - suggested_rect.right; + // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; + } + + let old_physical_inner_rect = { + let mut old_physical_inner_rect = RECT::default(); + let _ = GetClientRect(window, &mut old_physical_inner_rect); + let mut origin = POINT::default(); + let _ = ClientToScreen(window, &mut origin); + + old_physical_inner_rect.left += origin.x; + old_physical_inner_rect.right += origin.x; + old_physical_inner_rect.top += origin.y; + old_physical_inner_rect.bottom += origin.y; + + old_physical_inner_rect + }; + let old_physical_inner_size = PhysicalSize::new( + (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, + (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, + ); + + // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after + // exiting fullscreen (the restored size is already DPI adjusted). + let mut new_physical_inner_size = match allow_resize { + // We calculate our own size because the default suggested rect doesn't do a great job + // of preserving the window's logical size. + true => old_physical_inner_size + .to_logical::(old_scale_factor) + .to_physical::(new_scale_factor), + false => old_physical_inner_size, + }; + + // When the "Show window contents while dragging" is turned off, there is no need to adjust the window size. + if !is_show_window_contents_while_dragging_enabled() { + new_physical_inner_size = old_physical_inner_size; + } + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: ScaleFactorChanged { + scale_factor: new_scale_factor, + new_inner_size: &mut new_physical_inner_size, + }, + }); + + let dragging_window: bool; + + { + let window_state = userdata.window_state.lock(); + dragging_window = window_state + .window_flags() + .contains(WindowFlags::MARKER_IN_SIZE_MOVE); + // Unset maximized if we're changing the window's size. + if new_physical_inner_size != old_physical_inner_size { + WindowState::set_window_flags(window_state, window, |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + } + } + + let new_outer_rect: RECT; + if util::WIN_VERSION.build < 22000 { + // The window position needs adjustment on Windows 10. + { + let suggested_ul = ( + suggested_rect.left + margin_left, + suggested_rect.top + margin_top, + ); + + let mut conservative_rect = RECT { + left: suggested_ul.0, + top: suggested_ul.1, + right: suggested_ul.0 + new_physical_inner_size.width as i32, + bottom: suggested_ul.1 + new_physical_inner_size.height as i32, + }; + + conservative_rect = + util::adjust_window_rect_with_styles(window, style, style_ex, conservative_rect) + .unwrap_or(conservative_rect); + + // If we're dragging the window, offset the window so that the cursor's + // relative horizontal position in the title bar is preserved. + if dragging_window { + let bias = { + let cursor_pos = { + let mut pos = POINT::default(); + let _ = GetCursorPos(&mut pos); + pos + }; + let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64 + / (suggested_rect.right - suggested_rect.left) as f64; + + (cursor_pos.x + - (suggested_cursor_horizontal_ratio + * (conservative_rect.right - conservative_rect.left) as f64) + as i32) + - conservative_rect.left + }; + conservative_rect.left += bias; + conservative_rect.right += bias; + } + + // Check to see if the new window rect is on the monitor with the new DPI factor. + // If it isn't, offset the window so that it is. + let new_dpi_monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + let conservative_rect_monitor = + MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL); + new_outer_rect = { + if conservative_rect_monitor != new_dpi_monitor { + let get_monitor_rect = |monitor| { + let mut monitor_info = MONITORINFO { + cbSize: mem::size_of::() as _, + ..Default::default() + }; + let _ = GetMonitorInfoW(monitor, &mut monitor_info); + monitor_info.rcMonitor + }; + let wrong_monitor = conservative_rect_monitor; + let wrong_monitor_rect = get_monitor_rect(wrong_monitor); + let new_monitor_rect = get_monitor_rect(new_dpi_monitor); + + // The direction to nudge the window in to get the window onto the monitor with + // the new DPI factor. We calculate this by seeing which monitor edges are + // shared and nudging away from the wrong monitor based on those. + let delta_nudge_to_dpi_monitor = ( + if wrong_monitor_rect.left == new_monitor_rect.right { + -1 + } else if wrong_monitor_rect.right == new_monitor_rect.left { + 1 + } else { + 0 + }, + if wrong_monitor_rect.bottom == new_monitor_rect.top { + 1 + } else if wrong_monitor_rect.top == new_monitor_rect.bottom { + -1 + } else { + 0 + }, + ); + + let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left + + new_monitor_rect.bottom + - new_monitor_rect.top; + for _ in 0..abort_after_iterations { + conservative_rect.left += delta_nudge_to_dpi_monitor.0; + conservative_rect.right += delta_nudge_to_dpi_monitor.0; + conservative_rect.top += delta_nudge_to_dpi_monitor.1; + conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; + + if MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) == new_dpi_monitor { + break; + } + } + } + + conservative_rect + }; + } + } else { + // The suggested position is fine w/o adjustment on Windows 11. + new_outer_rect = suggested_rect + } + + let _ = SetWindowPos( + window, + None, + new_outer_rect.left, + new_outer_rect.top, + new_outer_rect.right - new_outer_rect.left, + new_outer_rect.bottom - new_outer_rect.top, + SWP_NOZORDER | SWP_NOACTIVATE, + ); + + result = ProcResult::Value(LRESULT(0)); + } + + win32wm::WM_SETTINGCHANGE => { + update_theme(userdata, window, true); + } + + win32wm::WM_NCCALCSIZE => { + let window_flags = userdata.window_state.lock().window_flags(); + let is_fullscreen = userdata.window_state.lock().fullscreen.is_some(); + + if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { + result = ProcResult::DefWindowProc; + } else { + // adjust the maximized borderless window so it doesn't cover the taskbar + if util::is_maximized(window).unwrap_or(false) { + let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); + if let Ok(monitor_info) = + monitor::get_monitor_info(MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL)) + { + let mut rect = monitor_info.monitorInfo.rcWork; + + fn has_edge(edge: u32) -> bool { + let mut app_data = APPBARDATA { + cbSize: std::mem::size_of::() as _, + uEdge: edge, + ..Default::default() + }; + unsafe { SHAppBarMessage(ABM_GETAUTOHIDEBAR, &mut app_data) != 0 } + } + + // keep a 1px for taskbar auto-hide to work + if has_edge(ABE_BOTTOM) { + rect.bottom -= 1; + } + if has_edge(ABE_LEFT) { + rect.left += 1; + } + if has_edge(ABE_TOP) { + rect.top += 1; + } + if has_edge(ABE_RIGHT) { + rect.right -= 1; + } + + params.rgrc[0] = rect; + } + } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) && !is_fullscreen { + let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); + + let insets = util::calculate_window_insets(window); + + params.rgrc[0].left += insets.left; + params.rgrc[0].top += insets.top; + params.rgrc[0].right -= insets.right; + params.rgrc[0].bottom -= insets.bottom; + } + result = ProcResult::Value(LRESULT(0)); // return 0 here to make the window borderless + } + } + + WM_NCHITTEST => { + let window_state = userdata.window_state.lock(); + let window_flags = window_state.window_flags(); + + // Allow resizing unmaximized non-fullscreen undecorated window + if !window_flags.contains(WindowFlags::MARKER_DECORATIONS) + && window_flags.contains(WindowFlags::RESIZABLE) + && window_state.fullscreen.is_none() + && !util::is_maximized(window).unwrap_or(false) + { + // cursor location + let (cx, cy) = ( + util::GET_X_LPARAM(lparam) as i32, + util::GET_Y_LPARAM(lparam) as i32, + ); + + let dpi = hwnd_dpi(window); + let border_y = get_system_metrics_for_dpi(SM_CYFRAME, dpi); + + // if we have undecorated shadows, we only need to handle the top edge + if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { + let rect = util::client_rect(window); + let mut cursor_pt = POINT { x: cx, y: cy }; + if ScreenToClient(window, &mut cursor_pt).as_bool() + && cursor_pt.y >= 0 + && cursor_pt.y <= border_y + && cursor_pt.x >= 0 + && cursor_pt.x <= rect.right + { + result = ProcResult::Value(LRESULT(HTTOP as _)); + } + } + // otherwise do full hit testing + else { + let border_x = get_system_metrics_for_dpi(SM_CXFRAME, dpi); + let rect = util::window_rect(window); + let hit_result = crate::window::hit_test( + (rect.left, rect.top, rect.right, rect.bottom), + cx, + cy, + border_x, + border_y, + ) + .map(|d| d.to_win32()); + + result = hit_result + .map(|r| ProcResult::Value(LRESULT(r as _))) + .unwrap_or(ProcResult::DefWindowProc); + } + } else { + result = ProcResult::DefWindowProc; + } + } + + win32wm::WM_SYSCHAR => { + // Handle system shortcut e.g. Alt+Space for window menu + result = ProcResult::DefWindowProc; + } + + _ => { + if msg == *DESTROY_MSG_ID { + let _ = DestroyWindow(window); + result = ProcResult::Value(LRESULT(0)); + } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { + let mut window_state = userdata.window_state.lock(); + window_state.set_window_flags_in_place(|f| { + f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam.0 != 0) + }); + result = ProcResult::Value(LRESULT(0)); + } else if msg == *CHANGE_THEME_MSG_ID { + update_theme(userdata, window, false); + result = ProcResult::Value(LRESULT(0)); + } else if msg == *S_U_TASKBAR_RESTART { + let skip_taskbar = userdata.window_state.lock().skip_taskbar; + if skip_taskbar { + let _ = set_skip_taskbar(window, true); + } + } + } + }; + + userdata + .event_loop_runner + .catch_unwind(callback) + .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); + + match result { + ProcResult::DefWindowProc => DefWindowProcW(window, msg, wparam, lparam), + ProcResult::Value(val) => val, + } +} + +fn update_theme(userdata: &WindowData, window: HWND, from_settings_change_event: bool) { + let mut window_state = userdata.window_state.lock(); + let preferred_theme = window_state + .preferred_theme + .or(*userdata.event_loop_preferred_theme.lock()); + if from_settings_change_event && preferred_theme.is_some() { + return; + } + let new_theme = try_window_theme(window, preferred_theme, !from_settings_change_event); + if window_state.current_theme != new_theme { + window_state.current_theme = new_theme; + mem::drop(window_state); + unsafe { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::ThemeChanged(new_theme), + }) + }; + } +} + +fn is_show_window_contents_while_dragging_enabled() -> bool { + let mut is_enabled: BOOL = BOOL(0); + let result = unsafe { + SystemParametersInfoW( + SPI_GETDRAGFULLWINDOWS, + 0, + Option::from(&mut is_enabled as *mut _ as *mut std::ffi::c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), + ) + }; + result.is_ok() && is_enabled.0 != 0 +} + +unsafe extern "system" fn thread_event_target_callback( + window: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + let userdata_ptr = util::GetWindowLongPtrW(window, GWL_USERDATA) as *mut ThreadMsgTargetData; + if userdata_ptr.is_null() { + // `userdata_ptr` will always be null for the first `WM_GETMINMAXINFO`, as well as + // `WM_NCCREATE` and `WM_CREATE`. + return DefWindowProcW(window, msg, wparam, lparam); + } + let userdata = Box::from_raw(userdata_ptr); + + let mut userdata_removed = false; + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { + win32wm::WM_NCDESTROY => { + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata_removed = true; + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + LRESULT(0) + } + // Because WM_PAINT comes after all other messages, we use it during modal loops to detect + // when the event queue has been emptied. See `process_event` for more details. + win32wm::WM_PAINT => { + let _ = ValidateRect(Some(window), None); + // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw + // events, `handling_events` will return false and we won't emit a second + // `RedrawEventsCleared` event. + if userdata.event_loop_runner.handling_events() { + if userdata.event_loop_runner.should_buffer() { + // This branch can be triggered when a nested win32 event loop is triggered + // inside of the `event_handler` callback. + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + } else { + // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` + // doesn't call WM_PAINT for the thread event target (i.e. this window). + assert!(flush_paint_messages(None, &userdata.event_loop_runner)); + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); + } + } + + // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. + DefWindowProcW(window, msg, wparam, lparam) + } + + win32wm::WM_INPUT_DEVICE_CHANGE => { + let event = match wparam.0 as u32 { + win32wm::GIDC_ARRIVAL => DeviceEvent::Added, + win32wm::GIDC_REMOVAL => DeviceEvent::Removed, + _ => unreachable!(), + }; + + userdata.send_event(Event::DeviceEvent { + device_id: wrap_device_id(lparam.0), + event, + }); + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + + LRESULT(0) + } + + win32wm::WM_INPUT => { + if let Some(data) = raw_input::get_raw_input_data(HRAWINPUT(lparam.0 as _)) { + handle_raw_input(&userdata, data); + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + } + + DefWindowProcW(window, msg, wparam, lparam) + } + + // We don't process `WM_QUERYENDSESSION` yet until we introduce the same mechanism as Tauri's `ExitRequested` event + // win32wm::WM_QUERYENDSESSION => {} + win32wm::WM_ENDSESSION => { + // `wParam` is `FALSE` is for if the shutdown gets canceled, + // and we don't need to handle that case since we didn't do anything prior in response to `WM_QUERYENDSESSION` + if wparam.0 == TRUE.0 as usize { + userdata.event_loop_runner.loop_destroyed(); + // `WM_ENDSESSION` can be sent by Windows during shutdown or by Restart Manager + // + // - From Windows shutdown: Windows will shut us down after we return `0` here + // - From Restart Manager: Restart Manager only sends the message but will wait for us to shutdown ourselves + // + // So, we call `exit` here directly to align this behavior + std::process::exit(0); + } + LRESULT(0) + } + + _ if msg == *USER_EVENT_MSG_ID => { + if let Ok(event) = userdata.user_event_receiver.recv() { + userdata.send_event(Event::UserEvent(event)); + } + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + LRESULT(0) + } + _ if msg == *EXEC_MSG_ID => { + let mut function: ThreadExecFn = Box::from_raw(wparam.0 as *mut _); + function(); + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + LRESULT(0) + } + _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { + let _ = PostThreadMessageW( + userdata.event_loop_runner.wait_thread_id(), + *CANCEL_WAIT_UNTIL_MSG_ID, + WPARAM(0), + LPARAM(0), + ); + + // if the control_flow is WaitUntil, make sure the given moment has actually passed + // before emitting NewEvents + if let ControlFlow::WaitUntil(wait_until) = userdata.event_loop_runner.control_flow() { + let mut msg = MSG::default(); + while Instant::now() < wait_until { + if PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE).as_bool() { + // This works around a "feature" in PeekMessageW. If the message PeekMessageW + // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't + // have an update region), PeekMessageW will remove that window from the + // redraw queue even though we told it not to remove messages from the + // queue. We fix it by re-dispatching an internal paint message to that + // window. + if msg.message == WM_PAINT { + let mut rect = RECT::default(); + if !GetUpdateRect(msg.hwnd, Some(&mut rect), false).as_bool() { + let _ = RedrawWindow(Some(msg.hwnd), None, None, RDW_INTERNALPAINT); + } + } + + break; + } + } + } + userdata.event_loop_runner.poll(); + let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); + LRESULT(0) + } + _ => DefWindowProcW(window, msg, wparam, lparam), + }; + + let result = userdata + .event_loop_runner + .catch_unwind(callback) + .unwrap_or(LRESULT(-1)); + if userdata_removed { + drop(userdata); + } else { + Box::leak(userdata); + } + result +} + +unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { + use crate::event::{ + DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, + ElementState::{Pressed, Released}, + MouseScrollDelta::LineDelta, + }; + + let device_id = wrap_device_id(data.header.hDevice.0 as _); + + if data.header.dwType == RIM_TYPEMOUSE.0 { + let mouse = data.data.mouse; + + if util::has_flag(mouse.usFlags.0, MOUSE_MOVE_RELATIVE.0) { + let x = mouse.lLastX as f64; + let y = mouse.lLastY as f64; + + if x != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 0, value: x }, + }); + } + + if y != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 1, value: y }, + }); + } + + if x != 0.0 || y != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: MouseMotion { delta: (x, y) }, + }); + } + } + + if util::has_flag( + mouse.Anonymous.Anonymous.usButtonFlags, + RI_MOUSE_WHEEL as u16, + ) { + // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. + let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 / WHEEL_DELTA as f32; + userdata.send_event(Event::DeviceEvent { + device_id, + event: MouseWheel { + delta: LineDelta(0.0, delta), + }, + }); + } + + let button_state = + raw_input::get_raw_mouse_button_state(mouse.Anonymous.Anonymous.usButtonFlags); + // Left, middle, and right, respectively. + for (index, state) in button_state.iter().enumerate() { + if let Some(state) = *state { + // This gives us consistency with X11, since there doesn't + // seem to be anything else reasonable to do for a mouse + // button ID. + let button = (index + 1) as _; + userdata.send_event(Event::DeviceEvent { + device_id, + event: Button { button, state }, + }); + } + } + } else if data.header.dwType == RIM_TYPEKEYBOARD.0 { + let keyboard = data.data.keyboard; + + let pressed = keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; + let released = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; + + if !pressed && !released { + return; + } + + let state = if pressed { Pressed } else { Released }; + let extension = { + if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) { + 0xE000 + } else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) { + 0xE100 + } else { + 0x0000 + } + }; + let scancode = if keyboard.MakeCode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 + } else { + keyboard.MakeCode | extension + }; + if scancode == 0xE11D || scancode == 0xE02A { + // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing + // Ctrl+NumLock. + // This equvalence means that if the user presses Pause, the keyboard will emit two + // subsequent keypresses: + // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) + // 2, 0x0045 - Which on its own can be interpreted as Pause + // + // There's another combination which isn't quite an equivalence: + // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing + // PrtSc (print screen) produces the following sequence: + // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) + // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on + // its own it can be interpreted as PrtSc + // + // For this reason, if we encounter the first keypress, we simply ignore it, trusting + // that there's going to be another event coming, from which we can extract the + // appropriate key. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + return; + } + let code = if VIRTUAL_KEY(keyboard.VKey) == VK_NUMLOCK { + // Historically, the NumLock and the Pause key were one and the same physical key. + // The user could trigger Pause by pressing Ctrl+NumLock. + // Now these are often physically separate and the two keys can be differentiated by + // checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045. + // + // However in this event, both keys are reported as 0x0045 even on modern hardware. + // Therefore we use the virtual key instead to determine whether it's a NumLock and + // set the KeyCode accordingly. + // + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + KeyCode::NumLock + } else { + KeyCode::from_scancode(scancode as u32) + }; + if VIRTUAL_KEY(keyboard.VKey) == VK_SHIFT { + match code { + KeyCode::NumpadDecimal + | KeyCode::Numpad0 + | KeyCode::Numpad1 + | KeyCode::Numpad2 + | KeyCode::Numpad3 + | KeyCode::Numpad4 + | KeyCode::Numpad5 + | KeyCode::Numpad6 + | KeyCode::Numpad7 + | KeyCode::Numpad8 + | KeyCode::Numpad9 => { + // On Windows, holding the Shift key makes numpad keys behave as if NumLock + // wasn't active. The way this is exposed to applications by the system is that + // the application receives a fake key release event for the shift key at the + // moment when the numpad key is pressed, just before receiving the numpad key + // as well. + // + // The issue is that in the raw device event (here), the fake shift release + // event reports the numpad key as the scancode. Unfortunately, the event doesn't + // have any information to tell whether it's the left shift or the right shift + // that needs to get the fake release (or press) event so we don't forward this + // event to the application at all. + // + // For more on this, read the article by Raymond Chen, titled: + // "The shift key overrides NumLock" + // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 + return; + } + _ => (), + } + } + userdata.send_event(Event::DeviceEvent { + device_id, + event: Key(RawKeyEvent { + physical_key: code, + state, + }), + }); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs new file mode 100644 index 000000000..ff865de3a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs @@ -0,0 +1,450 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::{HashSet, VecDeque}, + mem, panic, + rc::Rc, + time::Instant, +}; + +use windows::Win32::{ + Foundation::HWND, + Graphics::Gdi::{RedrawWindow, RDW_INTERNALPAINT}, +}; + +use crate::{ + dpi::PhysicalSize, + event::{Event, StartCause, WindowEvent}, + event_loop::ControlFlow, + platform_impl::platform::util, + window::WindowId, +}; + +pub(crate) type EventLoopRunnerShared = Rc>; +pub(crate) struct EventLoopRunner { + // The event loop's win32 handles + thread_msg_target: HWND, + wait_thread_id: u32, + + control_flow: Cell, + runner_state: Cell, + last_events_cleared: Cell, + + event_handler: Cell, &mut ControlFlow)>>>, + event_buffer: RefCell>>, + + owned_windows: Cell>, + + panic_error: Cell>, +} + +pub type PanicError = Box; + +/// See `move_state_to` function for details on how the state loop works. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum RunnerState { + /// The event loop has just been created, and an `Init` event must be sent. + Uninitialized, + /// The event loop is idling. + Idle, + /// The event loop is handling the OS's events and sending them to the user's callback. + /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. + HandlingMainEvents, + /// The event loop is handling the redraw events and sending them to the user's callback. + /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. + HandlingRedrawEvents, + /// The event loop has been destroyed. No other events will be emitted. + Destroyed, +} + +enum BufferedEvent { + Event(Event<'static, T>), + ScaleFactorChanged(WindowId, f64, PhysicalSize), +} + +impl EventLoopRunner { + pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: u32) -> EventLoopRunner { + EventLoopRunner { + thread_msg_target, + wait_thread_id, + runner_state: Cell::new(RunnerState::Uninitialized), + control_flow: Cell::new(ControlFlow::Poll), + panic_error: Cell::new(None), + last_events_cleared: Cell::new(Instant::now()), + event_handler: Cell::new(None), + event_buffer: RefCell::new(VecDeque::new()), + owned_windows: Cell::new(HashSet::new()), + } + } + + pub(crate) unsafe fn set_event_handler(&self, f: F) + where + F: FnMut(Event<'_, T>, &mut ControlFlow), + { + let old_event_handler = self.event_handler.replace(mem::transmute::< + Option, &mut ControlFlow)>>, + Option, &mut ControlFlow)>>, + >(Some(Box::new(f)))); + assert!(old_event_handler.is_none()); + } + + pub(crate) fn reset_runner(&self) { + let EventLoopRunner { + thread_msg_target: _, + wait_thread_id: _, + runner_state, + panic_error, + control_flow, + last_events_cleared: _, + event_handler, + event_buffer: _, + owned_windows: _, + } = self; + runner_state.set(RunnerState::Uninitialized); + panic_error.set(None); + control_flow.set(ControlFlow::Poll); + event_handler.set(None); + } +} + +/// State retrieval functions. +impl EventLoopRunner { + pub fn thread_msg_target(&self) -> HWND { + self.thread_msg_target + } + + pub fn wait_thread_id(&self) -> u32 { + self.wait_thread_id + } + + pub fn redrawing(&self) -> bool { + self.runner_state.get() == RunnerState::HandlingRedrawEvents + } + + pub fn take_panic_error(&self) -> Result<(), PanicError> { + match self.panic_error.take() { + Some(err) => Err(err), + None => Ok(()), + } + } + + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() + } + + pub fn handling_events(&self) -> bool { + self.runner_state.get() != RunnerState::Idle + } + + pub fn should_buffer(&self) -> bool { + let handler = self.event_handler.take(); + let should_buffer = handler.is_none(); + self.event_handler.set(handler); + should_buffer + } +} + +/// Misc. functions +impl EventLoopRunner { + pub fn catch_unwind(&self, f: impl FnOnce() -> R) -> Option { + let panic_error = self.panic_error.take(); + if panic_error.is_none() { + let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); + + // Check to see if the panic error was set in a re-entrant call to catch_unwind inside + // of `f`. If it was, that error takes priority. If it wasn't, check if our call to + // catch_unwind caught any panics and set panic_error appropriately. + match self.panic_error.take() { + None => match result { + Ok(r) => Some(r), + Err(e) => { + self.panic_error.set(Some(e)); + None + } + }, + Some(e) => { + self.panic_error.set(Some(e)); + None + } + } + } else { + self.panic_error.set(panic_error); + None + } + } + pub fn register_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.insert(window.0 as _); + self.owned_windows.set(owned_windows); + } + + pub fn remove_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.remove(&(window.0 as _)); + self.owned_windows.set(owned_windows); + } + + pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { + let mut owned_windows = self.owned_windows.take(); + for hwnd in &owned_windows { + f(HWND(*hwnd as _)); + } + let new_owned_windows = self.owned_windows.take(); + owned_windows.extend(&new_owned_windows); + self.owned_windows.set(owned_windows); + } +} + +/// Event dispatch functions. +impl EventLoopRunner { + pub(crate) unsafe fn poll(&self) { + self.move_state_to(RunnerState::HandlingMainEvents); + } + + pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + if let Event::RedrawRequested(_) = event { + if self.runner_state.get() != RunnerState::HandlingRedrawEvents { + // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. + debug!("RedrawRequested dispatched without explicit MainEventsCleared"); + self.move_state_to(RunnerState::HandlingRedrawEvents); + } + self.call_event_handler(event); + } else if self.should_buffer() { + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self + .event_buffer + .borrow_mut() + .push_back(BufferedEvent::from_event(event)) + } else { + self.move_state_to(RunnerState::HandlingMainEvents); + self.call_event_handler(event); + self.dispatch_buffered_events(); + } + } + + pub(crate) unsafe fn main_events_cleared(&self) { + self.move_state_to(RunnerState::HandlingRedrawEvents); + } + + pub(crate) unsafe fn redraw_events_cleared(&self) { + self.move_state_to(RunnerState::Idle); + } + + pub(crate) unsafe fn loop_destroyed(&self) { + self.move_state_to(RunnerState::Destroyed); + } + + unsafe fn call_event_handler(&self, event: Event<'_, T>) { + self.catch_unwind(|| { + let mut control_flow = self.control_flow.take(); + let mut event_handler = self.event_handler.take() + .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); + + if let ControlFlow::ExitWithCode(code) = control_flow { + event_handler(event, &mut ControlFlow::ExitWithCode(code)); + } else { + event_handler(event, &mut control_flow); + } + + assert!(self.event_handler.replace(Some(event_handler)).is_none()); + self.control_flow.set(control_flow); + }); + } + + unsafe fn dispatch_buffered_events(&self) { + loop { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + let buffered_event_opt = self.event_buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| self.call_event_handler(e)), + None => break, + } + } + } + + /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, `RedrawEventsCleared`, and + /// `LoopDestroyed`) as necessary to bring the internal `RunnerState` to the new runner state. + /// + /// The state transitions are defined as follows: + /// + /// ```text + /// Uninitialized + /// | + /// V + /// HandlingMainEvents + /// ^ | + /// | V + /// Idle <--- HandlingRedrawEvents + /// | + /// V + /// Destroyed + /// ``` + /// + /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to + /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current + /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the + /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and + /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. + unsafe fn move_state_to(&self, new_runner_state: RunnerState) { + use RunnerState::{Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; + + match ( + self.runner_state.replace(new_runner_state), + new_runner_state, + ) { + (Uninitialized, Uninitialized) + | (Idle, Idle) + | (HandlingMainEvents, HandlingMainEvents) + | (HandlingRedrawEvents, HandlingRedrawEvents) + | (Destroyed, Destroyed) => (), + + // State transitions that initialize the event loop. + (Uninitialized, HandlingMainEvents) => { + self.call_new_events(true); + } + (Uninitialized, HandlingRedrawEvents) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + } + (Uninitialized, Idle) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + } + (Uninitialized, Destroyed) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } + (_, Uninitialized) => panic!("cannot move state to Uninitialized"), + + // State transitions that start the event handling process. + (Idle, HandlingMainEvents) => { + self.call_new_events(false); + } + (Idle, HandlingRedrawEvents) => { + self.call_new_events(false); + self.call_event_handler(Event::MainEventsCleared); + } + (Idle, Destroyed) => { + self.call_event_handler(Event::LoopDestroyed); + } + + (HandlingMainEvents, HandlingRedrawEvents) => { + self.call_event_handler(Event::MainEventsCleared); + } + (HandlingMainEvents, Idle) => { + // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. + debug!("RedrawEventsCleared emitted without explicit MainEventsCleared"); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + } + (HandlingMainEvents, Destroyed) => { + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } + + (HandlingRedrawEvents, Idle) => { + self.call_redraw_events_cleared(); + } + (HandlingRedrawEvents, HandlingMainEvents) => { + // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. + debug!("NewEvents emitted without explicit RedrawEventsCleared"); + self.call_redraw_events_cleared(); + self.call_new_events(false); + } + (HandlingRedrawEvents, Destroyed) => { + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } + + (Destroyed, _) => panic!("cannot move state from Destroyed"), + } + } + + unsafe fn call_new_events(&self, init: bool) { + let start_cause = match (init, self.control_flow()) { + (true, _) => StartCause::Init, + (false, ControlFlow::Poll) => StartCause::Poll, + (false, ControlFlow::ExitWithCode(_)) | (false, ControlFlow::Wait) => { + StartCause::WaitCancelled { + requested_resume: None, + start: self.last_events_cleared.get(), + } + } + (false, ControlFlow::WaitUntil(requested_resume)) => { + if Instant::now() < requested_resume { + StartCause::WaitCancelled { + requested_resume: Some(requested_resume), + start: self.last_events_cleared.get(), + } + } else { + StartCause::ResumeTimeReached { + requested_resume, + start: self.last_events_cleared.get(), + } + } + } + }; + self.call_event_handler(Event::NewEvents(start_cause)); + self.dispatch_buffered_events(); + let _ = RedrawWindow(Some(self.thread_msg_target), None, None, RDW_INTERNALPAINT); + } + + unsafe fn call_redraw_events_cleared(&self) { + self.call_event_handler(Event::RedrawEventsCleared); + self.last_events_cleared.set(Instant::now()); + } +} + +impl BufferedEvent { + pub fn from_event(event: Event<'_, T>) -> BufferedEvent { + match event { + Event::WindowEvent { + event: + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + window_id, + } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), + event => BufferedEvent::Event(event.to_static().unwrap()), + } + } + + pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { + match self { + Self::Event(event) => dispatch(event), + Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { + let os_inner_size = new_inner_size; + + dispatch(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if new_inner_size != os_inner_size { + util::set_inner_size_physical( + HWND(window_id.0 .0 as _), + new_inner_size.width as _, + new_inner_size.height as _, + true, + ); + } + } + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs new file mode 100644 index 000000000..871071020 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs @@ -0,0 +1,149 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{fmt, mem, path::Path, sync::Arc}; + +use windows::{ + core::PCWSTR, + Win32::{ + Foundation::{HWND, LPARAM, WPARAM}, + UI::WindowsAndMessaging::*, + }, +}; +use windows_core::Owned; + +use crate::{dpi::PhysicalSize, icon::*, platform_impl::platform::util}; + +impl Pixel { + fn to_bgra(&mut self) { + mem::swap(&mut self.r, &mut self.b); + } +} + +impl RgbaIcon { + fn into_windows_icon(self) -> Result { + let mut rgba = self.rgba; + let pixel_count = rgba.len() / PIXEL_SIZE; + let mut and_mask = Vec::with_capacity(pixel_count); + let pixels = + unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) }; + for pixel in pixels { + and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel + pixel.to_bgra(); + } + assert_eq!(and_mask.len(), pixel_count); + let handle = unsafe { + CreateIcon( + None, + self.width as i32, + self.height as i32, + 1, + (PIXEL_SIZE * 8) as u8, + and_mask.as_ptr(), + rgba.as_ptr(), + ) + } + .map_err(|error| BadIcon::OsError(error.into()))?; + Ok(WinIcon::from_handle(HICON(handle.0))) + } +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum IconType { + Small = ICON_SMALL as isize, + Big = ICON_BIG as isize, +} + +#[derive(Clone)] +pub struct WinIcon { + inner: Arc>, +} + +unsafe impl Send for WinIcon {} + +impl WinIcon { + pub fn as_raw_handle(&self) -> HICON { + **self.inner + } + + pub fn from_path>( + path: P, + size: Option>, + ) -> Result { + let wide_path = util::encode_wide(path.as_ref()); + + // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size + let (width, height) = size.map(Into::into).unwrap_or((0, 0)); + + let handle = unsafe { + LoadImageW( + None, + PCWSTR::from_raw(wide_path.as_ptr()), + IMAGE_ICON, + width, + height, + LR_DEFAULTSIZE | LR_LOADFROMFILE, + ) + } + .map_err(|error| BadIcon::OsError(error.into()))?; + Ok(WinIcon::from_handle(HICON(handle.0))) + } + + pub fn from_resource(resource_id: u16, size: Option>) -> Result { + // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size + let (width, height) = size.map(Into::into).unwrap_or((0, 0)); + let handle = unsafe { + LoadImageW( + Some(util::get_instance_handle()), + PCWSTR::from_raw(resource_id as usize as *const u16), + IMAGE_ICON, + width, + height, + LR_DEFAULTSIZE, + ) + } + .map_err(|error| BadIcon::OsError(error.into()))?; + Ok(WinIcon::from_handle(HICON(handle.0))) + } + + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; + rgba_icon.into_windows_icon() + } + + pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { + unsafe { + SendMessageW( + hwnd, + WM_SETICON, + Some(WPARAM(icon_type as _)), + Some(LPARAM(self.as_raw_handle().0 as _)), + ); + } + } + + fn from_handle(handle: HICON) -> Self { + Self { + inner: Arc::new(unsafe { Owned::new(handle) }), + } + } +} + +impl fmt::Debug for WinIcon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + (*self.inner).fmt(formatter) + } +} + +pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { + unsafe { + SendMessageW( + hwnd, + WM_SETICON, + Some(WPARAM(icon_type as _)), + Some(LPARAM(0)), + ); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs new file mode 100644 index 000000000..51156aba9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs @@ -0,0 +1,163 @@ +use std::{ + ffi::{c_void, OsString}, + os::windows::prelude::OsStringExt, +}; + +use windows::Win32::{ + Foundation::HWND, + UI::Input::Ime::{ + ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, GCS_RESULTSTR, HIMC, + IME_COMPOSITION_STRING, + }, +}; + +pub struct ImeContext { + hwnd: HWND, + himc: HIMC, +} + +impl ImeContext { + pub unsafe fn current(hwnd: HWND) -> Self { + let himc = unsafe { ImmGetContext(hwnd) }; + ImeContext { hwnd, himc } + } + + // pub unsafe fn get_composing_text_and_cursor( + // &self, + // ) -> Option<(String, Option, Option)> { + // let text = unsafe { self.get_composition_string(GCS_COMPSTR) }?; + // let attrs = unsafe { self.get_composition_data(GCS_COMPATTR) }.unwrap_or_default(); + + // let mut first = None; + // let mut last = None; + // let mut boundary_before_char = 0; + // let mut attr_idx = 0; + + // for chr in text.chars() { + // let Some(attr) = attrs.get(attr_idx).copied() else { + // break; + // }; + + // let char_is_targeted = + // attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; + + // if first.is_none() && char_is_targeted { + // first = Some(boundary_before_char); + // } else if first.is_some() && last.is_none() && !char_is_targeted { + // last = Some(boundary_before_char); + // } + + // boundary_before_char += chr.len_utf8(); + // attr_idx += chr.len_utf16(); + // } + + // if first.is_some() && last.is_none() { + // last = Some(text.len()); + // } else if first.is_none() { + // // IME haven't split words and select any clause yet, so trying to retrieve normal + // // cursor. + // let cursor = unsafe { self.get_composition_cursor(&text) }; + // first = cursor; + // last = cursor; + // } + + // Some((text, first, last)) + // } + + pub unsafe fn get_composed_text(&self) -> Option { + unsafe { self.get_composition_string(GCS_RESULTSTR) } + } + + // unsafe fn get_composition_cursor(&self, text: &str) -> Option { + // let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) }; + // (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) + // } + + unsafe fn get_composition_string(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option { + let data = unsafe { self.get_composition_data(gcs_mode) }?; + let (prefix, shorts, suffix) = unsafe { data.align_to::() }; + if prefix.is_empty() && suffix.is_empty() { + OsString::from_wide(shorts).into_string().ok() + } else { + None + } + } + + unsafe fn get_composition_data(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option> { + let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, None, 0) } { + 0 => return Some(Vec::new()), + size if size < 0 => return None, + size => size, + }; + + let mut buf = Vec::::with_capacity(size as _); + let size = unsafe { + ImmGetCompositionStringW( + self.himc, + gcs_mode, + Some(buf.as_mut_ptr() as *mut c_void), + size as _, + ) + }; + + if size < 0 { + None + } else { + unsafe { buf.set_len(size as _) }; + Some(buf) + } + } + + // pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // let (x, y) = spot.to_physical::(scale_factor).into(); + // let (width, height): (i32, i32) = size.to_physical::(scale_factor).into(); + // let rc_area = RECT { + // left: x, + // top: y, + // right: x + width, + // bottom: y + height, + // }; + // let candidate_form = CANDIDATEFORM { + // dwIndex: 0, + // dwStyle: CFS_EXCLUDE, + // ptCurrentPos: POINT { x, y }, + // rcArea: rc_area, + // }; + // let composition_form = COMPOSITIONFORM { + // dwStyle: CFS_POINT, + // ptCurrentPos: POINT { x, y: y + height }, + // rcArea: rc_area, + // }; + + // unsafe { + // ImmSetCompositionWindow(self.himc, &composition_form); + // ImmSetCandidateWindow(self.himc, &candidate_form); + // } + // } + + // pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // if allowed { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_DEFAULT) }; + // } else { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_CHILDREN) }; + // } + // } + + // pub unsafe fn system_has_ime() -> bool { + // unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } + // } +} + +impl Drop for ImeContext { + fn drop(&mut self) { + let _ = unsafe { ImmReleaseContext(self.hwnd, self.himc) }; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs new file mode 100644 index 000000000..aefa9658a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,951 @@ +use std::{ + char, + collections::HashSet, + ffi::OsString, + mem::MaybeUninit, + os::windows::ffi::OsStringExt, + sync::atomic::{AtomicU32, Ordering::Relaxed}, +}; + +use crate::{ + event::{ElementState, KeyEvent}, + platform_impl::KeyEventExtra, +}; +use crate::{ + keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, + platform_impl::platform::keyboard_layout::get_or_insert_str, +}; +use parking_lot::{Mutex, MutexGuard}; +use unicode_segmentation::UnicodeSegmentation; +use windows::Win32::{ + Foundation::{HWND, LPARAM, LRESULT, WPARAM}, + UI::{ + Input::KeyboardAndMouse::{ + GetAsyncKeyState, GetKeyState, GetKeyboardState, MapVirtualKeyExW, HKL, MAPVK_VK_TO_VSC_EX, + MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR, VK_CONTROL, + VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT, VK_LCONTROL, + VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK, VK_NUMPAD0, + VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, + VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, + VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP, + }, + WindowsAndMessaging::{ + PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST, + WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, + }, + }, +}; + +use crate::platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, +}; + +pub type ExScancode = u16; + +pub struct MessageAsKeyEvent { + pub event: KeyEvent, + pub is_synthetic: bool, +} + +/// Stores information required to make `KeyEvent`s. +/// +/// A single Tao `KeyEvent` contains information which the Windows API passes to the application +/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single +/// window message. Therefore, this type keeps track of certain information from previous events so +/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. +/// +/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the +/// current keypress. If it doesn't and the current state represents a key event waiting to be +/// dispatched, then said event is considered complete and is dispatched. +/// +/// The sequence of window messages for a key press event is the following: +/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN +/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR +/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when +/// put together in the sequence they arrived in, forms the text which is the result of pressing the +/// key. +/// +/// Key release messages are a bit different due to the fact that they don't contribute to +/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. +pub struct KeyEventBuilder { + event_info: Mutex>, + pending: PendingEventQueue, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { + event_info: Mutex::new(None), + pending: Default::default(), + } + } +} +impl KeyEventBuilder { + /// Call this function for every window message. + /// Returns Some() if this window message completes a KeyEvent. + /// Returns None otherwise. + pub(crate) fn process_message( + &self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + enum MatchResult { + Nothing, + TokenToRemove(PendingMessageToken), + MessagesToDispatch(Vec), + } + + let mut matcher = || -> MatchResult { + match msg_kind { + WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Pressed, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KILLFOCUS => { + // synthesize keyup events + let kbd_state = get_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KEYDOWN | WM_SYSKEYDOWN => { + if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + if msg_kind == WM_SYSKEYDOWN { + *result = ProcResult::DefWindowProc; + } else { + *result = ProcResult::Value(LRESULT(0)); + } + + let next_msg = next_kbd_msg(hwnd); + + let mut layouts = LAYOUT_CACHE.lock(); + let mut finished_event_info = Some(PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + )); + let mut event_info = self.event_info.lock(); + *event_info = None; + if let Some(next_msg) = next_msg { + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP + ); + if next_belongs_to_this { + // The next OS event belongs to this Winit event, so let's just + // store the partial information, and add to it in the upcoming events + *event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + WM_DEADCHAR | WM_SYSDEADCHAR => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + // At this point, we know that there isn't going to be any more events related + // to this key press + let event_info = self.event_info.lock().take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock(); + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + WM_CHAR | WM_SYSCHAR => { + let mut event_info = self.event_info.lock(); + if event_info.is_none() { + trace!( + "Received a CHAR message but no `event_info` was available. The \ + message is probably IME, returning." + ); + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + let is_high_surrogate = (0xd800..=0xdbff).contains(&wparam.0); + let is_low_surrogate = (0xdc00..=0xdfff).contains(&wparam.0); + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + if is_utf16 { + if let Some(ev_info) = event_info.as_mut() { + ev_info.utf16parts.push(wparam.0 as u16); + } + } else { + // In this case, wparam holds a UTF-32 character. + // Let's encode it as UTF-16 and append it to the end of `utf16parts` + let utf16parts = match event_info.as_mut() { + Some(ev_info) => &mut ev_info.utf16parts, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let start_offset = utf16parts.len(); + let new_size = utf16parts.len() + 2; + utf16parts.resize(new_size, 0); + if let Some(ch) = char::from_u32(wparam.0 as u32) { + let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); + let new_size = start_offset + encode_len; + utf16parts.resize(new_size, 0); + } + } + // It's important that we unlock the mutex, and create the pending event token + // before calling `next_msg` + std::mem::drop(event_info); + let next_msg = next_kbd_msg(hwnd); + let more_char_coming = next_msg + .map(|m| matches!(m.message, WM_CHAR | WM_SYSCHAR)) + .unwrap_or(false); + if more_char_coming { + // No need to produce an event just yet, because there are still more + // characters that need to be appended to this keyboard event + MatchResult::TokenToRemove(pending_token) + } else { + let mut event_info = self.event_info.lock(); + let mut event_info = match event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let mut layouts = LAYOUT_CACHE.lock(); + // It's okay to call `ToUnicode` here, because at this point the dead key + // is already consumed by the character. + let kbd_state = get_kbd_state(); + let mod_state = WindowsModifiers::active_modifiers(&kbd_state); + + let (_, layout) = layouts.get_current_layout(); + let ctrl_on = if layout.has_alt_graph { + let alt_on = mod_state.contains(WindowsModifiers::ALT); + !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + mod_state.contains(WindowsModifiers::CONTROL) + }; + + // If Ctrl is not pressed, just use the text with all + // modifiers because that already consumed the dead key. Otherwise, + // we would interpret the character incorrectly, missing the dead key. + if !ctrl_on { + event_info.text = PartialText::System(event_info.utf16parts.clone()); + } else { + let mod_no_ctrl = mod_state.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + let vkey = event_info.vkey; + let scancode = event_info.scancode; + let keycode = event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + } + WM_KEYUP | WM_SYSKEYUP => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + + let mut layouts = LAYOUT_CACHE.lock(); + let event_info = + PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); + // We MUST release the layout lock before calling `next_kbd_msg`, otherwise it + // may deadlock + drop(layouts); + // It's important that we create the pending token before reading the next + // message. + let next_msg = next_kbd_msg(hwnd); + let mut valid_event_info = Some(event_info); + if let Some(next_msg) = next_msg { + let mut layouts = LAYOUT_CACHE.lock(); + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let mut layouts = LAYOUT_CACHE.lock(); + let event = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + _ => MatchResult::Nothing, + } + }; + let matcher_result = matcher(); + match matcher_result { + MatchResult::TokenToRemove(t) => self.pending.remove_pending(t), + MatchResult::MessagesToDispatch(m) => m, + MatchResult::Nothing => Vec::new(), + } + } + + fn synthesize_kbd_state( + key_state: ElementState, + kbd_state: &[u8; 256], + ) -> Vec { + let mut key_events = Vec::new(); + + let mut layouts = LAYOUT_CACHE.lock(); + let (locale_id, _) = layouts.get_current_layout(); + + let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; + + // Is caps-lock active? Note that this is different from caps-lock + // being held down. + let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // We are synthesizing the press event for caps-lock first for the following reasons: + // 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all + // printable keys, respecting the caps-lock state. + // 2. If caps-lock is held down, we could choose to synthesize its keypress after every + // other key, in which case all other keys *must* be synthesized as if the caps-lock + // state was be the opposite of what it currently is. + // -- + // For the sake of simplicity we are choosing to always synthesize + // caps-lock first, and always use the current caps-lock state + // to determine the produced text + if is_key_pressed(VK_CAPITAL) { + let event = Self::create_synthetic( + VK_CAPITAL, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + &mut layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + for vk in 0..256 { + let vk = VIRTUAL_KEY(vk); + match vk { + VK_CONTROL | VK_LCONTROL | VK_RCONTROL | VK_SHIFT | VK_LSHIFT | VK_RSHIFT | VK_MENU + | VK_LMENU | VK_RMENU | VK_CAPITAL => continue, + _ => (), + } + if !is_key_pressed(vk) { + continue; + } + let event = + Self::create_synthetic(vk, key_state, caps_lock_on, num_lock_on, locale_id, layouts); + if let Some(event) = event { + key_events.push(event); + } + } + }; + let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ + VK_LCONTROL, + VK_LSHIFT, + VK_LMENU, + VK_RCONTROL, + VK_RSHIFT, + VK_RMENU, + ]; + for vk in CLEAR_MODIFIER_VKS.iter() { + if is_key_pressed(*vk) { + let event = Self::create_synthetic( + *vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + } + }; + + // Be cheeky and sequence modifier and non-modifier + // key events such that non-modifier keys are not affected + // by modifiers (except for caps-lock) + match key_state { + ElementState::Pressed => { + do_non_modifier(&mut key_events, &mut layouts); + do_modifier(&mut key_events, &mut layouts); + } + ElementState::Released => { + do_modifier(&mut key_events, &mut layouts); + do_non_modifier(&mut key_events, &mut layouts); + } + } + + key_events + } + + fn create_synthetic( + vk: VIRTUAL_KEY, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; + if scancode == 0 { + return None; + } + let scancode = scancode as ExScancode; + let code = KeyCode::from_scancode(scancode as u32); + let mods = if caps_lock_on { + WindowsModifiers::CAPS_LOCK + } else { + WindowsModifiers::empty() + }; + let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); + let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); + let key_without_modifiers = + layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); + let text = if key_state == ElementState::Pressed { + logical_key.to_text() + } else { + None + }; + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key.clone()), + key_without_modifiers, + key_state, + scancode, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifiers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-perceived characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: VIRTUAL_KEY, + scancode: ExScancode, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let vkey = VIRTUAL_KEY(wparam.0 as u16); + let scancode = if lparam_struct.scancode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + unsafe { + MapVirtualKeyExW( + u32::from(vkey.0), + MAPVK_VK_TO_VSC_EX, + Some(HKL(layout.hkl as _)), + ) as u16 + } + } else { + new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) + }; + let code = KeyCode::from_scancode(scancode as u32); + let location = get_location(scancode, HKL(layout.hkl as _)); + + let kbd_state = get_kbd_state(); + let mods = WindowsModifiers::active_modifiers(&kbd_state); + let mods_without_ctrl = mods.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases + // the KeyCode still stores the real key, so in the name of consistency across platforms, we + // circumvent this mapping and force the key values to match the keycode. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { + match code { + KeyCode::NumLock => Some(Key::NumLock), + KeyCode::Pause => Some(Key::Pause), + _ => None, + } + } else { + None + }; + + let preliminary_logical_key = + layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); + let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); + let is_pressed = state == ElementState::Pressed; + + let logical_key = if let Some(key) = code_as_key.clone() { + PartialLogicalKey::This(key) + } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { + // In some cases we want to use the UNICHAR text for logical_key in order to allow + // dead keys to have an effect on the character reported by `logical_key`. + PartialLogicalKey::TextOr(preliminary_logical_key) + } else { + PartialLogicalKey::This(preliminary_logical_key) + }; + let key_without_modifiers = if let Some(key) = code_as_key { + key + } else { + match layout.get_key(NO_MODS, false, vkey, scancode, code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // regular US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKeyCode::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + scancode, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), + }, + PartialLogicalKey::This(v) => v, + }; + + KeyEvent { + physical_key: self.code, + logical_key, + text, + location: self.location, + state: self.key_state, + repeat: self.is_repeat, + platform_specific: KeyEventExtra { + text_with_all_modifiers: char_with_all_modifiers, + key_without_modifiers: self.key_without_modifiers, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +struct KeyLParam { + pub scancode: u8, + pub extended: bool, + + /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. + pub is_repeat: bool, +} + +fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { + let previous_state = (lparam.0 >> 30) & 0x01; + let transition_state = (lparam.0 >> 31) & 0x01; + KeyLParam { + scancode: ((lparam.0 >> 16) & 0xFF) as u8, + extended: ((lparam.0 >> 24) & 0x01) != 0, + is_repeat: (previous_state ^ transition_state) != 0, + } +} + +#[inline] +fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { + (scancode as u16) | (if extended { 0xE000 } else { 0 }) +} + +#[inline] +fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { + let lparam = destructure_key_lparam(lparam); + new_ex_scancode(lparam.scancode, lparam.extended) +} + +/// Gets the keyboard state as reported by messages that have been removed from the event queue. +/// See also: get_async_kbd_state +fn get_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); + let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); + kbd_state.assume_init() + } +} + +/// Gets the current keyboard state regardless of whether the corresponding keyboard events have +/// been removed from the event queue. See also: get_kbd_state +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = [0; 256]; + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = VIRTUAL_KEY(vk as u16); + let async_state = GetAsyncKeyState(i32::from(vk.0)); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!(vk, VK_CAPITAL | VK_NUMLOCK | VK_SCROLL) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = GetKeyState(i32::from(vk.0)); + let is_active = (toggle_state & 1) != 0; + *state |= u8::from(is_active); + } + } + kbd_state + } +} + +/// On windows, AltGr == Ctrl + Alt +/// +/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceding +/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if +/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the +/// fake Ctrl event. +fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { + let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); + if layout.has_alt_graph { + let next_code = ex_scancode_from_lparam(next_msg.lParam); + let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt + if curr_is_ctrl && next_is_altgr { + return true; + } + } + false +} + +enum PendingMessage { + Incomplete, + Complete(T), +} +struct IdentifiedPendingMessage { + token: PendingMessageToken, + msg: PendingMessage, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PendingMessageToken(u32); + +/// While processing keyboard events, we sometimes need +/// to call `PeekMessageW` (`next_msg`). But `PeekMessageW` +/// can also call the event handler, which means that the new event +/// gets processed before finishing to process the one that came before. +/// +/// This would mean that the application receives events in the wrong order. +/// To avoid this, we keep track whether we are in the middle of processing +/// an event. Such an event is an "incomplete pending event". A +/// "complete pending event" is one that has already finished processing, but +/// hasn't been dispatched to the application because there still are incomplete +/// pending events that came before it. +/// +/// When we finish processing an event, we call `complete_pending`, +/// which returns an empty array if there are incomplete pending events, but +/// if all pending events are complete, then it returns all pending events in +/// the order they were encountered. These can then be dispatched to the application +pub struct PendingEventQueue { + pending: Mutex>>, + next_id: AtomicU32, +} +impl PendingEventQueue { + /// Add a new pending event to the "pending queue" + pub fn add_pending(&self) -> PendingMessageToken { + let token = self.next_token(); + let mut pending = self.pending.lock(); + pending.push(IdentifiedPendingMessage { + token, + msg: PendingMessage::Incomplete, + }); + token + } + + /// Returns all finished pending events + /// + /// If the return value is non empty, it's guaranteed to contain `msg` + /// + /// See also: `add_pending` + pub fn complete_pending(&self, token: PendingMessageToken, msg: T) -> Vec { + let mut pending = self.pending.lock(); + let mut target_is_first = false; + for (i, pending_msg) in pending.iter_mut().enumerate() { + if pending_msg.token == token { + pending_msg.msg = PendingMessage::Complete(msg); + if i == 0 { + target_is_first = true; + } + break; + } + } + if target_is_first { + // If the message that we just finished was the first one in the pending queue, + // then we can empty the queue, and dispatch all of the messages. + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + pub fn complete_multi(&self, msgs: Vec) -> Vec { + let mut pending = self.pending.lock(); + if pending.is_empty() { + return msgs; + } + pending.reserve(msgs.len()); + for msg in msgs { + pending.push(IdentifiedPendingMessage { + token: self.next_token(), + msg: PendingMessage::Complete(msg), + }); + } + Vec::new() + } + + /// Returns all finished pending events + /// + /// It's safe to call this even if the element isn't in the list anymore + /// + /// See also: `add_pending` + pub fn remove_pending(&self, token: PendingMessageToken) -> Vec { + let mut pending = self.pending.lock(); + let mut was_first = false; + if let Some(m) = pending.first() { + if m.token == token { + was_first = true; + } + } + pending.retain(|m| m.token != token); + if was_first { + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + fn drain_pending(pending: &mut Vec>) -> Vec { + pending + .drain(..) + .map(|m| match m.msg { + PendingMessage::Complete(msg) => msg, + PendingMessage::Incomplete => { + panic!( + "Found an incomplete pending message when collecting messages. This \ + indicates a bug in tao." + ) + } + }) + .collect() + } + + fn next_token(&self) -> PendingMessageToken { + // It's okay for the u32 to overflow here. Yes, that could mean + // that two different messages have the same token, + // but that would only happen after having about 4 billion + // messages sitting in the pending queue. + // + // In that case, having two identical tokens is the least of your concerns. + let id = self.next_id.fetch_add(1, Relaxed); + PendingMessageToken(id) + } +} +impl Default for PendingEventQueue { + fn default() -> Self { + PendingEventQueue { + pending: Mutex::new(Vec::new()), + next_id: AtomicU32::new(0), + } + } +} + +/// WARNING: Due to using PeekMessage, the event handler +/// function may get called during this function. +/// (Re-entrance to the event handler) +/// +/// This can cause a deadlock if calling this function +/// while having a mutex locked. +/// +/// It can also cause code to get executed in a surprising order. +pub(crate) fn next_kbd_msg(window: HWND) -> Option { + unsafe { + let mut next_msg = MaybeUninit::uninit(); + if PeekMessageW( + next_msg.as_mut_ptr(), + Some(window), + WM_KEYFIRST, + WM_KEYLAST, + PM_NOREMOVE, + ) + .as_bool() + { + Some(next_msg.assume_init()) + } else { + None + } + } +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = + VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); + + // Use the native VKEY and the extended flag to cover most cases + // This is taken from the `druid` GUI library, specifically + // druid-shell/src/platform/windows/keyboard.rs + match vkey { + VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, + VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, + VK_RETURN if extended => KeyLocation::Numpad, + VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT + | VK_HOME | VK_UP | VK_PRIOR => { + if extended { + KeyLocation::Standard + } else { + KeyLocation::Numpad + } + } + VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 + | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE | VK_MULTIPLY | VK_SUBTRACT + | VK_ADD | VK_ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs new file mode 100644 index 000000000..e4105a3de --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs @@ -0,0 +1,983 @@ +use parking_lot::Mutex; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ffi::OsString, + os::windows::ffi::OsStringExt, + sync::LazyLock, +}; + +use windows::Win32::{ + System::SystemServices::{LANG_JAPANESE, LANG_KOREAN}, + UI::Input::KeyboardAndMouse::{self as win32km, *}, +}; + +use super::keyboard::ExScancode; +use crate::{ + keyboard::{Key, KeyCode, ModifiersState, NativeKeyCode}, + platform_impl::platform::util, +}; + +pub(crate) static LAYOUT_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(LayoutCache::default())); + +pub(crate) fn get_agnostic_mods() -> ModifiersState { + LAYOUT_CACHE.lock().get_agnostic_mods() +} + +fn key_pressed(vkey: VIRTUAL_KEY) -> bool { + unsafe { (GetKeyState(u32::from(vkey.0) as i32) & (1 << 15)) == (1 << 15) } +} + +const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ + VK_NUMPAD0, + VK_NUMPAD1, + VK_NUMPAD2, + VK_NUMPAD3, + VK_NUMPAD4, + VK_NUMPAD5, + VK_NUMPAD6, + VK_NUMPAD7, + VK_NUMPAD8, + VK_NUMPAD9, + VK_MULTIPLY, + VK_ADD, + VK_SEPARATOR, + VK_SUBTRACT, + VK_DECIMAL, + VK_DIVIDE, +]; + +static NUMPAD_KEYCODES: LazyLock> = LazyLock::new(|| { + let mut keycodes = HashSet::new(); + keycodes.insert(KeyCode::Numpad0); + keycodes.insert(KeyCode::Numpad1); + keycodes.insert(KeyCode::Numpad2); + keycodes.insert(KeyCode::Numpad3); + keycodes.insert(KeyCode::Numpad4); + keycodes.insert(KeyCode::Numpad5); + keycodes.insert(KeyCode::Numpad6); + keycodes.insert(KeyCode::Numpad7); + keycodes.insert(KeyCode::Numpad8); + keycodes.insert(KeyCode::Numpad9); + keycodes.insert(KeyCode::NumpadMultiply); + keycodes.insert(KeyCode::NumpadAdd); + keycodes.insert(KeyCode::NumpadComma); + keycodes.insert(KeyCode::NumpadSubtract); + keycodes.insert(KeyCode::NumpadDecimal); + keycodes.insert(KeyCode::NumpadDivide); + keycodes +}); + +bitflags! { + #[derive(Clone, Copy, Eq, PartialEq, Hash)] + pub struct WindowsModifiers : u8 { + const SHIFT = 1 << 0; + const CONTROL = 1 << 1; + const ALT = 1 << 2; + const CAPS_LOCK = 1 << 3; + const FLAGS_END = 1 << 4; + } +} + +impl WindowsModifiers { + pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers { + let shift = key_state[usize::from(VK_SHIFT.0)] & 0x80 != 0; + let lshift = key_state[usize::from(VK_LSHIFT.0)] & 0x80 != 0; + let rshift = key_state[usize::from(VK_RSHIFT.0)] & 0x80 != 0; + + let control = key_state[usize::from(VK_CONTROL.0)] & 0x80 != 0; + let lcontrol = key_state[usize::from(VK_LCONTROL.0)] & 0x80 != 0; + let rcontrol = key_state[usize::from(VK_RCONTROL.0)] & 0x80 != 0; + + let alt = key_state[usize::from(VK_MENU.0)] & 0x80 != 0; + let lalt = key_state[usize::from(VK_LMENU.0)] & 0x80 != 0; + let ralt = key_state[usize::from(VK_RMENU.0)] & 0x80 != 0; + + let caps = key_state[usize::from(VK_CAPITAL.0)] & 0x01 != 0; + + let mut result = WindowsModifiers::empty(); + if shift || lshift || rshift { + result.insert(WindowsModifiers::SHIFT); + } + if control || lcontrol || rcontrol { + result.insert(WindowsModifiers::CONTROL); + } + if alt || lalt || ralt { + result.insert(WindowsModifiers::ALT); + } + if caps { + result.insert(WindowsModifiers::CAPS_LOCK); + } + + result + } + + pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) { + if self.intersects(Self::SHIFT) { + key_state[usize::from(VK_SHIFT.0)] |= 0x80; + } else { + key_state[usize::from(VK_SHIFT.0)] &= !0x80; + key_state[usize::from(VK_LSHIFT.0)] &= !0x80; + key_state[usize::from(VK_RSHIFT.0)] &= !0x80; + } + if self.intersects(Self::CONTROL) { + key_state[usize::from(VK_CONTROL.0)] |= 0x80; + } else { + key_state[usize::from(VK_CONTROL.0)] &= !0x80; + key_state[usize::from(VK_LCONTROL.0)] &= !0x80; + key_state[usize::from(VK_RCONTROL.0)] &= !0x80; + } + if self.intersects(Self::ALT) { + key_state[usize::from(VK_MENU.0)] |= 0x80; + } else { + key_state[usize::from(VK_MENU.0)] &= !0x80; + key_state[usize::from(VK_LMENU.0)] &= !0x80; + key_state[usize::from(VK_RMENU.0)] &= !0x80; + } + if self.intersects(Self::CAPS_LOCK) { + key_state[usize::from(VK_CAPITAL.0)] |= 0x01; + } else { + key_state[usize::from(VK_CAPITAL.0)] &= !0x01; + } + } + + /// Removes the control modifier if the alt modifier is not present. + /// This is useful because on Windows: (Control + Alt) == AltGr + /// but we don't want to interfere with the AltGr state. + pub fn remove_only_ctrl(mut self) -> WindowsModifiers { + if !self.contains(WindowsModifiers::ALT) { + self.remove(WindowsModifiers::CONTROL); + } + self + } +} + +pub(crate) struct Layout { + pub hkl: isize, + + /// Maps numpad keys from Windows virtual key to a `Key`. + /// + /// This is useful because some numpad keys generate different charcaters based on the locale. + /// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual + /// keys are only produced by Windows when the NumLock is active. + /// + /// Making this field separate from the `keys` field saves having to add NumLock as a modifier + /// to `WindowsModifiers`, which would double the number of items in keys. + pub numlock_on_keys: HashMap>, + /// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was + /// off. The keys of this map are identical to the keys of `numlock_on_keys`. + pub numlock_off_keys: HashMap>, + + /// Maps a modifier state to group of key strings + /// We're not using `ModifiersState` here because that object cannot express caps lock, + /// but we need to handle caps lock too. + /// + /// This map shouldn't need to exist. + /// However currently this seems to be the only good way + /// of getting the label for the pressed key. Note that calling `ToUnicode` + /// just when the key is pressed/released would be enough if `ToUnicode` wouldn't + /// change the keyboard state (it clears the dead key). There is a flag to prevent + /// changing the state, but that flag requires Windows 10, version 1607 or newer) + pub keys: HashMap>>, + pub has_alt_graph: bool, +} + +impl Layout { + pub fn get_key( + &self, + mods: WindowsModifiers, + num_lock_on: bool, + vkey: VIRTUAL_KEY, + scancode: ExScancode, + keycode: KeyCode, + ) -> Key<'static> { + let native_code = NativeKeyCode::Windows(scancode); + + let unknown_alt = vkey == VK_MENU; + if !unknown_alt { + // Here we try using the virtual key directly but if the virtual key doesn't distinguish + // between left and right alt, we can't report AltGr. Therefore, we only do this if the + // key is not the "unknown alt" key. + // + // The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when + // building the keys map) sometimes maps virtual keys to odd scancodes that don't match + // the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT` + // is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`. + let key_from_vkey = + vkey_to_non_char_key(vkey, native_code, HKL(self.hkl as _), self.has_alt_graph); + + if !matches!(key_from_vkey, Key::Unidentified(_)) { + return key_from_vkey; + } + } + if num_lock_on { + if let Some(key) = self.numlock_on_keys.get(&vkey.0) { + return key.clone(); + } + } else if let Some(key) = self.numlock_off_keys.get(&vkey.0) { + return key.clone(); + } + + if let Some(keys) = self.keys.get(&mods) { + if let Some(key) = keys.get(&keycode) { + return key.clone(); + } + } + Key::Unidentified(native_code) + } +} + +#[derive(Default)] +pub(crate) struct LayoutCache { + /// Maps locale identifiers (HKL) to layouts + pub layouts: HashMap, + pub strings: HashSet<&'static str>, +} + +impl LayoutCache { + /// Checks whether the current layout is already known and + /// prepares the layout if it isn't known. + /// The current layout is then returned. + pub fn get_current_layout<'a>(&'a mut self) -> (HKL, &'a Layout) { + let locale_id = unsafe { GetKeyboardLayout(0) }; + match self.layouts.entry(locale_id.0 as _) { + Entry::Occupied(entry) => (locale_id, entry.into_mut()), + Entry::Vacant(entry) => { + let layout = Self::prepare_layout(&mut self.strings, locale_id); + (locale_id, entry.insert(layout)) + } + } + } + + fn get_agnostic_mods(&mut self) -> ModifiersState { + let (_, layout) = self.get_current_layout(); + let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); + mods.set( + ModifiersState::CONTROL, + key_pressed(VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::SUPER, + key_pressed(VK_LWIN) || key_pressed(VK_RWIN), + ); + mods + } + + fn prepare_layout(strings: &mut HashSet<&'static str>, locale_id: HKL) -> Layout { + let mut layout = Layout { + hkl: locale_id.0 as _, + numlock_on_keys: Default::default(), + numlock_off_keys: Default::default(), + keys: Default::default(), + has_alt_graph: false, + }; + + // We initialize the keyboard state with all zeros to + // simulate a scenario when no modifier is active. + let mut key_state = [0u8; 256]; + + // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock + // was off. We rely on this behavior to find all virtual keys which are not numpad-specific + // but map to the numpad. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // + // Then we convert the source virtual key into a `Key` and the scancode into a virtual key + // to get the reverse mapping. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // || || + // \/ \/ + // map_value: Key <- map_vkey: VK + layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); + for vk in 0_u16..256 { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk), MAPVK_VK_TO_VSC_EX, Some(locale_id as HKL)) }; + if scancode == 0 { + continue; + } + let vk = VIRTUAL_KEY(vk); + let keycode = KeyCode::from_scancode(scancode); + if !is_numpad_specific(vk) && NUMPAD_KEYCODES.contains(&keycode) { + let native_code = NativeKeyCode::Windows(scancode as u16); + let map_vkey = keycode_to_vkey(keycode, locale_id); + if map_vkey == Default::default() { + continue; + } + let map_value = vkey_to_non_char_key(vk, native_code, locale_id, false); + if matches!(map_value, Key::Unidentified(_)) { + continue; + } + layout.numlock_off_keys.insert(map_vkey.0, map_value); + } + } + + layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); + for vk in NUMPAD_VKEYS.iter() { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id as HKL)) }; + let unicode = Self::to_unicode_string(&key_state, *vk, scancode, locale_id); + if let ToUnicodeResult::Str(s) = unicode { + let static_str = get_or_insert_str(strings, s); + layout + .numlock_on_keys + .insert(vk.0, Key::Character(static_str)); + } + } + + // Iterate through every combination of modifiers + let mods_end = WindowsModifiers::FLAGS_END.bits(); + for mod_state in 0..mods_end { + let mut keys_for_this_mod = HashMap::with_capacity(256); + + let mod_state = WindowsModifiers::from_bits_truncate(mod_state); + mod_state.apply_to_kbd_state(&mut key_state); + + // Virtual key values are in the domain [0, 255]. + // This is reinforced by the fact that the keyboard state array has 256 + // elements. This array is allowed to be indexed by virtual key values + // giving the key state for the virtual key used for indexing. + for vk in 0_u16..256 { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; + if scancode == 0 { + continue; + } + let vk = VIRTUAL_KEY(vk); + let native_code = NativeKeyCode::Windows(scancode as ExScancode); + let key_code = KeyCode::from_scancode(scancode); + // Let's try to get the key from just the scancode and vk + // We don't necessarily know yet if AltGraph is present on this layout so we'll + // assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to + // "AltGr" in case we find out that there's an AltGraph. + let preliminary_key = vkey_to_non_char_key(vk, native_code, locale_id, false); + match preliminary_key { + Key::Unidentified(_) => (), + _ => { + keys_for_this_mod.insert(key_code, preliminary_key); + continue; + } + } + + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + let key = match unicode { + ToUnicodeResult::Str(str) => { + let static_str = get_or_insert_str(strings, str); + Key::Character(static_str) + } + ToUnicodeResult::Dead(dead_char) => { + //#[cfg(debug_assertions)] println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char); + Key::Dead(dead_char) + } + ToUnicodeResult::None => { + let has_alt = mod_state.contains(WindowsModifiers::ALT); + let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); + // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad + // divide key, so we handle that explicitly here + if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { + Key::Character("/") + } else { + // Just use the unidentified key, we got earlier + preliminary_key + } + } + }; + + // Check for alt graph. + // The logic is that if a key pressed with no modifier produces + // a different `Character` from when it's pressed with CTRL+ALT then the layout + // has AltGr. + let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; + let is_in_ctrl_alt = mod_state == ctrl_alt; + if !layout.has_alt_graph && is_in_ctrl_alt { + // Unwrapping here because if we are in the ctrl+alt modifier state + // then the alt modifier state must have come before. + let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); + if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { + if let Key::Character(key) = key.clone() { + layout.has_alt_graph = key != *key_no_altgr; + } + } + } + + keys_for_this_mod.insert(key_code, key); + } + layout.keys.insert(mod_state, keys_for_this_mod); + } + + // Second pass: replace right alt keys with AltGr if the layout has alt graph + if layout.has_alt_graph { + for mod_state in 0..mods_end { + let mod_state = WindowsModifiers::from_bits_truncate(mod_state); + if let Some(keys) = layout.keys.get_mut(&mod_state) { + if let Some(key) = keys.get_mut(&KeyCode::AltRight) { + *key = Key::AltGraph; + } + } + } + } + + layout + } + + fn to_unicode_string( + key_state: &[u8; 256], + vkey: VIRTUAL_KEY, + scancode: u32, + locale_id: HKL, + ) -> ToUnicodeResult { + unsafe { + let mut label_wide = [0u16; 8]; + let mut wide_len = ToUnicodeEx( + u32::from(vkey.0), + scancode, + key_state, + &mut label_wide, + 0, + Some(locale_id), + ); + if wide_len < 0 { + // If it's dead, we run `ToUnicode` again to consume the dead-key + wide_len = ToUnicodeEx( + u32::from(vkey.0), + scancode, + key_state, + &mut label_wide, + 0, + Some(locale_id), + ); + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + if let Some(ch) = label_str.chars().next() { + return ToUnicodeResult::Dead(Some(ch)); + } + } + } + return ToUnicodeResult::Dead(None); + } + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + return ToUnicodeResult::Str(label_str); + } + } + } + ToUnicodeResult::None + } +} + +pub fn get_or_insert_str(strings: &mut HashSet<&'static str>, string: T) -> &'static str +where + T: AsRef, + String: From, +{ + { + let str_ref = string.as_ref(); + if let Some(&existing) = strings.get(str_ref) { + return existing; + } + } + let leaked = Box::leak(Box::from(String::from(string))); + strings.insert(leaked); + leaked +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum ToUnicodeResult { + Str(String), + Dead(Option), + None, +} + +fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { + matches!( + vk, + win32km::VK_NUMPAD0 + | win32km::VK_NUMPAD1 + | win32km::VK_NUMPAD2 + | win32km::VK_NUMPAD3 + | win32km::VK_NUMPAD4 + | win32km::VK_NUMPAD5 + | win32km::VK_NUMPAD6 + | win32km::VK_NUMPAD7 + | win32km::VK_NUMPAD8 + | win32km::VK_NUMPAD9 + | win32km::VK_ADD + | win32km::VK_SUBTRACT + | win32km::VK_DIVIDE + | win32km::VK_DECIMAL + | win32km::VK_SEPARATOR + ) +} + +fn keycode_to_vkey(keycode: KeyCode, hkl: HKL) -> VIRTUAL_KEY { + let primary_lang_id = util::PRIMARYLANGID(hkl); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == LANG_JAPANESE; + + match keycode { + KeyCode::Backquote => VIRTUAL_KEY::default(), + KeyCode::Backslash => VIRTUAL_KEY::default(), + KeyCode::BracketLeft => VIRTUAL_KEY::default(), + KeyCode::BracketRight => VIRTUAL_KEY::default(), + KeyCode::Comma => VIRTUAL_KEY::default(), + KeyCode::Digit0 => VIRTUAL_KEY::default(), + KeyCode::Digit1 => VIRTUAL_KEY::default(), + KeyCode::Digit2 => VIRTUAL_KEY::default(), + KeyCode::Digit3 => VIRTUAL_KEY::default(), + KeyCode::Digit4 => VIRTUAL_KEY::default(), + KeyCode::Digit5 => VIRTUAL_KEY::default(), + KeyCode::Digit6 => VIRTUAL_KEY::default(), + KeyCode::Digit7 => VIRTUAL_KEY::default(), + KeyCode::Digit8 => VIRTUAL_KEY::default(), + KeyCode::Digit9 => VIRTUAL_KEY::default(), + KeyCode::Equal => VIRTUAL_KEY::default(), + KeyCode::IntlBackslash => VIRTUAL_KEY::default(), + KeyCode::IntlRo => VIRTUAL_KEY::default(), + KeyCode::IntlYen => VIRTUAL_KEY::default(), + KeyCode::KeyA => VIRTUAL_KEY::default(), + KeyCode::KeyB => VIRTUAL_KEY::default(), + KeyCode::KeyC => VIRTUAL_KEY::default(), + KeyCode::KeyD => VIRTUAL_KEY::default(), + KeyCode::KeyE => VIRTUAL_KEY::default(), + KeyCode::KeyF => VIRTUAL_KEY::default(), + KeyCode::KeyG => VIRTUAL_KEY::default(), + KeyCode::KeyH => VIRTUAL_KEY::default(), + KeyCode::KeyI => VIRTUAL_KEY::default(), + KeyCode::KeyJ => VIRTUAL_KEY::default(), + KeyCode::KeyK => VIRTUAL_KEY::default(), + KeyCode::KeyL => VIRTUAL_KEY::default(), + KeyCode::KeyM => VIRTUAL_KEY::default(), + KeyCode::KeyN => VIRTUAL_KEY::default(), + KeyCode::KeyO => VIRTUAL_KEY::default(), + KeyCode::KeyP => VIRTUAL_KEY::default(), + KeyCode::KeyQ => VIRTUAL_KEY::default(), + KeyCode::KeyR => VIRTUAL_KEY::default(), + KeyCode::KeyS => VIRTUAL_KEY::default(), + KeyCode::KeyT => VIRTUAL_KEY::default(), + KeyCode::KeyU => VIRTUAL_KEY::default(), + KeyCode::KeyV => VIRTUAL_KEY::default(), + KeyCode::KeyW => VIRTUAL_KEY::default(), + KeyCode::KeyX => VIRTUAL_KEY::default(), + KeyCode::KeyY => VIRTUAL_KEY::default(), + KeyCode::KeyZ => VIRTUAL_KEY::default(), + KeyCode::Minus => VIRTUAL_KEY::default(), + KeyCode::Period => VIRTUAL_KEY::default(), + KeyCode::Quote => VIRTUAL_KEY::default(), + KeyCode::Semicolon => VIRTUAL_KEY::default(), + KeyCode::Slash => VIRTUAL_KEY::default(), + KeyCode::AltLeft => VK_LMENU, + KeyCode::AltRight => VK_RMENU, + KeyCode::Backspace => VK_BACK, + KeyCode::CapsLock => VK_CAPITAL, + KeyCode::ContextMenu => VK_APPS, + KeyCode::ControlLeft => VK_LCONTROL, + KeyCode::ControlRight => VK_RCONTROL, + KeyCode::Enter => VK_RETURN, + KeyCode::SuperLeft => VK_LWIN, + KeyCode::SuperRight => VK_RWIN, + KeyCode::ShiftLeft => VK_RSHIFT, + KeyCode::ShiftRight => VK_LSHIFT, + KeyCode::Space => VK_SPACE, + KeyCode::Tab => VK_TAB, + KeyCode::Convert => VK_CONVERT, + KeyCode::KanaMode => VK_KANA, + KeyCode::Lang1 if is_korean => VK_HANGUL, + KeyCode::Lang1 if is_japanese => VK_KANA, + KeyCode::Lang2 if is_korean => VK_HANJA, + KeyCode::Lang2 if is_japanese => VIRTUAL_KEY::default(), + KeyCode::Lang3 if is_japanese => VK_OEM_FINISH, + KeyCode::Lang4 if is_japanese => VIRTUAL_KEY::default(), + KeyCode::Lang5 if is_japanese => VIRTUAL_KEY::default(), + KeyCode::NonConvert => VK_NONCONVERT, + KeyCode::Delete => VK_DELETE, + KeyCode::End => VK_END, + KeyCode::Help => VK_HELP, + KeyCode::Home => VK_HOME, + KeyCode::Insert => VK_INSERT, + KeyCode::PageDown => VK_NEXT, + KeyCode::PageUp => VK_PRIOR, + KeyCode::ArrowDown => VK_DOWN, + KeyCode::ArrowLeft => VK_LEFT, + KeyCode::ArrowRight => VK_RIGHT, + KeyCode::ArrowUp => VK_UP, + KeyCode::NumLock => VK_NUMLOCK, + KeyCode::Numpad0 => VK_NUMPAD0, + KeyCode::Numpad1 => VK_NUMPAD1, + KeyCode::Numpad2 => VK_NUMPAD2, + KeyCode::Numpad3 => VK_NUMPAD3, + KeyCode::Numpad4 => VK_NUMPAD4, + KeyCode::Numpad5 => VK_NUMPAD5, + KeyCode::Numpad6 => VK_NUMPAD6, + KeyCode::Numpad7 => VK_NUMPAD7, + KeyCode::Numpad8 => VK_NUMPAD8, + KeyCode::Numpad9 => VK_NUMPAD9, + KeyCode::NumpadAdd => VK_ADD, + KeyCode::NumpadBackspace => VK_BACK, + KeyCode::NumpadClear => VK_CLEAR, + KeyCode::NumpadClearEntry => VIRTUAL_KEY::default(), + KeyCode::NumpadComma => VK_SEPARATOR, + KeyCode::NumpadDecimal => VK_DECIMAL, + KeyCode::NumpadDivide => VK_DIVIDE, + KeyCode::NumpadEnter => VK_RETURN, + KeyCode::NumpadEqual => VIRTUAL_KEY::default(), + KeyCode::NumpadHash => VIRTUAL_KEY::default(), + KeyCode::NumpadMemoryAdd => VIRTUAL_KEY::default(), + KeyCode::NumpadMemoryClear => VIRTUAL_KEY::default(), + KeyCode::NumpadMemoryRecall => VIRTUAL_KEY::default(), + KeyCode::NumpadMemoryStore => VIRTUAL_KEY::default(), + KeyCode::NumpadMemorySubtract => VIRTUAL_KEY::default(), + KeyCode::NumpadMultiply => VK_MULTIPLY, + KeyCode::NumpadParenLeft => VIRTUAL_KEY::default(), + KeyCode::NumpadParenRight => VIRTUAL_KEY::default(), + KeyCode::NumpadStar => VIRTUAL_KEY::default(), + KeyCode::NumpadSubtract => VK_SUBTRACT, + KeyCode::Escape => VK_ESCAPE, + KeyCode::Fn => VIRTUAL_KEY::default(), + KeyCode::FnLock => VIRTUAL_KEY::default(), + KeyCode::PrintScreen => VK_SNAPSHOT, + KeyCode::ScrollLock => VK_SCROLL, + KeyCode::Pause => VK_PAUSE, + KeyCode::BrowserBack => VK_BROWSER_BACK, + KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES, + KeyCode::BrowserForward => VK_BROWSER_FORWARD, + KeyCode::BrowserHome => VK_BROWSER_HOME, + KeyCode::BrowserRefresh => VK_BROWSER_REFRESH, + KeyCode::BrowserSearch => VK_BROWSER_SEARCH, + KeyCode::BrowserStop => VK_BROWSER_STOP, + KeyCode::Eject => VIRTUAL_KEY::default(), + KeyCode::LaunchApp1 => VK_LAUNCH_APP1, + KeyCode::LaunchApp2 => VK_LAUNCH_APP2, + KeyCode::LaunchMail => VK_LAUNCH_MAIL, + KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, + KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT, + KeyCode::MediaStop => VK_MEDIA_STOP, + KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK, + KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK, + KeyCode::Power => VIRTUAL_KEY::default(), + KeyCode::Sleep => VIRTUAL_KEY::default(), + KeyCode::AudioVolumeDown => VK_VOLUME_DOWN, + KeyCode::AudioVolumeMute => VK_VOLUME_MUTE, + KeyCode::AudioVolumeUp => VK_VOLUME_UP, + KeyCode::WakeUp => VIRTUAL_KEY::default(), + KeyCode::Hyper => VIRTUAL_KEY::default(), + KeyCode::Turbo => VIRTUAL_KEY::default(), + KeyCode::Abort => VIRTUAL_KEY::default(), + KeyCode::Resume => VIRTUAL_KEY::default(), + KeyCode::Suspend => VIRTUAL_KEY::default(), + KeyCode::Again => VIRTUAL_KEY::default(), + KeyCode::Copy => VIRTUAL_KEY::default(), + KeyCode::Cut => VIRTUAL_KEY::default(), + KeyCode::Find => VIRTUAL_KEY::default(), + KeyCode::Open => VIRTUAL_KEY::default(), + KeyCode::Paste => VIRTUAL_KEY::default(), + KeyCode::Props => VIRTUAL_KEY::default(), + KeyCode::Select => VK_SELECT, + KeyCode::Undo => VIRTUAL_KEY::default(), + KeyCode::Hiragana => VIRTUAL_KEY::default(), + KeyCode::Katakana => VIRTUAL_KEY::default(), + KeyCode::F1 => VK_F1, + KeyCode::F2 => VK_F2, + KeyCode::F3 => VK_F3, + KeyCode::F4 => VK_F4, + KeyCode::F5 => VK_F5, + KeyCode::F6 => VK_F6, + KeyCode::F7 => VK_F7, + KeyCode::F8 => VK_F8, + KeyCode::F9 => VK_F9, + KeyCode::F10 => VK_F10, + KeyCode::F11 => VK_F11, + KeyCode::F12 => VK_F12, + KeyCode::F13 => VK_F13, + KeyCode::F14 => VK_F14, + KeyCode::F15 => VK_F15, + KeyCode::F16 => VK_F16, + KeyCode::F17 => VK_F17, + KeyCode::F18 => VK_F18, + KeyCode::F19 => VK_F19, + KeyCode::F20 => VK_F20, + KeyCode::F21 => VK_F21, + KeyCode::F22 => VK_F22, + KeyCode::F23 => VK_F23, + KeyCode::F24 => VK_F24, + KeyCode::F25 => VIRTUAL_KEY::default(), + KeyCode::F26 => VIRTUAL_KEY::default(), + KeyCode::F27 => VIRTUAL_KEY::default(), + KeyCode::F28 => VIRTUAL_KEY::default(), + KeyCode::F29 => VIRTUAL_KEY::default(), + KeyCode::F30 => VIRTUAL_KEY::default(), + KeyCode::F31 => VIRTUAL_KEY::default(), + KeyCode::F32 => VIRTUAL_KEY::default(), + KeyCode::F33 => VIRTUAL_KEY::default(), + KeyCode::F34 => VIRTUAL_KEY::default(), + KeyCode::F35 => VIRTUAL_KEY::default(), + KeyCode::Unidentified(_) => VIRTUAL_KEY::default(), + _ => VIRTUAL_KEY::default(), + } +} + +/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to +/// a `Key`, with only the information passed in as arguments, are converted. +/// +/// In other words: this function does not need to "prepare" the current layout in order to do +/// the conversion, but as such it cannot convert certain keys, like language-specific character keys. +/// +/// The result includes all non-character keys defined within `Key` plus characters from numpad keys. +/// For example, backspace and tab are included. +fn vkey_to_non_char_key( + vkey: VIRTUAL_KEY, + native_code: NativeKeyCode, + hkl: HKL, + has_alt_graph: bool, +) -> Key<'static> { + // List of the Web key names and their corresponding platform-native key names: + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + + let primary_lang_id = util::PRIMARYLANGID(hkl); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == LANG_JAPANESE; + + match vkey { + win32km::VK_LBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + win32km::VK_RBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + + // I don't think this can be represented with a Key + win32km::VK_CANCEL => Key::Unidentified(native_code), + + win32km::VK_MBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + win32km::VK_XBUTTON1 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + win32km::VK_XBUTTON2 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + win32km::VK_BACK => Key::Backspace, + win32km::VK_TAB => Key::Tab, + win32km::VK_CLEAR => Key::Clear, + win32km::VK_RETURN => Key::Enter, + win32km::VK_SHIFT => Key::Shift, + win32km::VK_CONTROL => Key::Control, + win32km::VK_MENU => Key::Alt, + win32km::VK_PAUSE => Key::Pause, + win32km::VK_CAPITAL => Key::CapsLock, + + //win32km::VK_HANGEUL => Key::HangulMode, // Deprecated in favour of VK_HANGUL + + // VK_HANGUL and VK_KANA are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + win32km::VK_HANGUL if is_korean => Key::HangulMode, + win32km::VK_KANA if is_japanese => Key::KanaMode, + + win32km::VK_JUNJA => Key::JunjaMode, + win32km::VK_FINAL => Key::FinalMode, + + // VK_HANJA and VK_KANJI are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + win32km::VK_HANJA if is_korean => Key::HanjaMode, + win32km::VK_KANJI if is_japanese => Key::KanjiMode, + + win32km::VK_ESCAPE => Key::Escape, + win32km::VK_CONVERT => Key::Convert, + win32km::VK_NONCONVERT => Key::NonConvert, + win32km::VK_ACCEPT => Key::Accept, + win32km::VK_MODECHANGE => Key::ModeChange, + win32km::VK_SPACE => Key::Space, + win32km::VK_PRIOR => Key::PageUp, + win32km::VK_NEXT => Key::PageDown, + win32km::VK_END => Key::End, + win32km::VK_HOME => Key::Home, + win32km::VK_LEFT => Key::ArrowLeft, + win32km::VK_UP => Key::ArrowUp, + win32km::VK_RIGHT => Key::ArrowRight, + win32km::VK_DOWN => Key::ArrowDown, + win32km::VK_SELECT => Key::Select, + win32km::VK_PRINT => Key::Print, + win32km::VK_EXECUTE => Key::Execute, + win32km::VK_SNAPSHOT => Key::PrintScreen, + win32km::VK_INSERT => Key::Insert, + win32km::VK_DELETE => Key::Delete, + win32km::VK_HELP => Key::Help, + win32km::VK_LWIN => Key::Super, + win32km::VK_RWIN => Key::Super, + win32km::VK_APPS => Key::ContextMenu, + win32km::VK_SLEEP => Key::Standby, + + // Numpad keys produce characters + win32km::VK_NUMPAD0 => Key::Unidentified(native_code), + win32km::VK_NUMPAD1 => Key::Unidentified(native_code), + win32km::VK_NUMPAD2 => Key::Unidentified(native_code), + win32km::VK_NUMPAD3 => Key::Unidentified(native_code), + win32km::VK_NUMPAD4 => Key::Unidentified(native_code), + win32km::VK_NUMPAD5 => Key::Unidentified(native_code), + win32km::VK_NUMPAD6 => Key::Unidentified(native_code), + win32km::VK_NUMPAD7 => Key::Unidentified(native_code), + win32km::VK_NUMPAD8 => Key::Unidentified(native_code), + win32km::VK_NUMPAD9 => Key::Unidentified(native_code), + win32km::VK_MULTIPLY => Key::Unidentified(native_code), + win32km::VK_ADD => Key::Unidentified(native_code), + win32km::VK_SEPARATOR => Key::Unidentified(native_code), + win32km::VK_SUBTRACT => Key::Unidentified(native_code), + win32km::VK_DECIMAL => Key::Unidentified(native_code), + win32km::VK_DIVIDE => Key::Unidentified(native_code), + + win32km::VK_F1 => Key::F1, + win32km::VK_F2 => Key::F2, + win32km::VK_F3 => Key::F3, + win32km::VK_F4 => Key::F4, + win32km::VK_F5 => Key::F5, + win32km::VK_F6 => Key::F6, + win32km::VK_F7 => Key::F7, + win32km::VK_F8 => Key::F8, + win32km::VK_F9 => Key::F9, + win32km::VK_F10 => Key::F10, + win32km::VK_F11 => Key::F11, + win32km::VK_F12 => Key::F12, + win32km::VK_F13 => Key::F13, + win32km::VK_F14 => Key::F14, + win32km::VK_F15 => Key::F15, + win32km::VK_F16 => Key::F16, + win32km::VK_F17 => Key::F17, + win32km::VK_F18 => Key::F18, + win32km::VK_F19 => Key::F19, + win32km::VK_F20 => Key::F20, + win32km::VK_F21 => Key::F21, + win32km::VK_F22 => Key::F22, + win32km::VK_F23 => Key::F23, + win32km::VK_F24 => Key::F24, + win32km::VK_NAVIGATION_VIEW => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_MENU => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_UP => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_DOWN => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_LEFT => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), + win32km::VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), + win32km::VK_NUMLOCK => Key::NumLock, + win32km::VK_SCROLL => Key::ScrollLock, + win32km::VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), + //win32km::VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` + win32km::VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), + win32km::VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), + win32km::VK_OEM_FJ_LOYA => Key::Unidentified(native_code), + win32km::VK_OEM_FJ_ROYA => Key::Unidentified(native_code), + win32km::VK_LSHIFT => Key::Shift, + win32km::VK_RSHIFT => Key::Shift, + win32km::VK_LCONTROL => Key::Control, + win32km::VK_RCONTROL => Key::Control, + win32km::VK_LMENU => Key::Alt, + win32km::VK_RMENU => { + if has_alt_graph { + Key::AltGraph + } else { + Key::Alt + } + } + win32km::VK_BROWSER_BACK => Key::BrowserBack, + win32km::VK_BROWSER_FORWARD => Key::BrowserForward, + win32km::VK_BROWSER_REFRESH => Key::BrowserRefresh, + win32km::VK_BROWSER_STOP => Key::BrowserStop, + win32km::VK_BROWSER_SEARCH => Key::BrowserSearch, + win32km::VK_BROWSER_FAVORITES => Key::BrowserFavorites, + win32km::VK_BROWSER_HOME => Key::BrowserHome, + win32km::VK_VOLUME_MUTE => Key::AudioVolumeMute, + win32km::VK_VOLUME_DOWN => Key::AudioVolumeDown, + win32km::VK_VOLUME_UP => Key::AudioVolumeUp, + win32km::VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext, + win32km::VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious, + win32km::VK_MEDIA_STOP => Key::MediaStop, + win32km::VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause, + win32km::VK_LAUNCH_MAIL => Key::LaunchMail, + win32km::VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer, + win32km::VK_LAUNCH_APP1 => Key::LaunchApplication1, + win32km::VK_LAUNCH_APP2 => Key::LaunchApplication2, + + // This function only converts "non-printable" + win32km::VK_OEM_1 => Key::Unidentified(native_code), + win32km::VK_OEM_PLUS => Key::Unidentified(native_code), + win32km::VK_OEM_COMMA => Key::Unidentified(native_code), + win32km::VK_OEM_MINUS => Key::Unidentified(native_code), + win32km::VK_OEM_PERIOD => Key::Unidentified(native_code), + win32km::VK_OEM_2 => Key::Unidentified(native_code), + win32km::VK_OEM_3 => Key::Unidentified(native_code), + + win32km::VK_GAMEPAD_A => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_B => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_X => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_Y => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_MENU => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_VIEW => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + + // This function only converts "non-printable" + win32km::VK_OEM_4 => Key::Unidentified(native_code), + win32km::VK_OEM_5 => Key::Unidentified(native_code), + win32km::VK_OEM_6 => Key::Unidentified(native_code), + win32km::VK_OEM_7 => Key::Unidentified(native_code), + win32km::VK_OEM_8 => Key::Unidentified(native_code), + win32km::VK_OEM_AX => Key::Unidentified(native_code), + win32km::VK_OEM_102 => Key::Unidentified(native_code), + + win32km::VK_ICO_HELP => Key::Unidentified(native_code), + win32km::VK_ICO_00 => Key::Unidentified(native_code), + + win32km::VK_PROCESSKEY => Key::Process, + + win32km::VK_ICO_CLEAR => Key::Unidentified(native_code), + win32km::VK_PACKET => Key::Unidentified(native_code), + win32km::VK_OEM_RESET => Key::Unidentified(native_code), + win32km::VK_OEM_JUMP => Key::Unidentified(native_code), + win32km::VK_OEM_PA1 => Key::Unidentified(native_code), + win32km::VK_OEM_PA2 => Key::Unidentified(native_code), + win32km::VK_OEM_PA3 => Key::Unidentified(native_code), + win32km::VK_OEM_WSCTRL => Key::Unidentified(native_code), + win32km::VK_OEM_CUSEL => Key::Unidentified(native_code), + + win32km::VK_OEM_ATTN => Key::Attn, + win32km::VK_OEM_FINISH => { + if is_japanese { + Key::Katakana + } else { + // This matches IE and Firefox behaviour according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + // At the time of writing, there is no `Key::Finish` variant as + // Finish is not mentionned at https://w3c.github.io/uievents-key/ + // Also see: https://github.com/pyfisch/keyboard-types/issues/9 + Key::Unidentified(native_code) + } + } + win32km::VK_OEM_COPY => Key::Copy, + win32km::VK_OEM_AUTO => Key::Hankaku, + win32km::VK_OEM_ENLW => Key::Zenkaku, + win32km::VK_OEM_BACKTAB => Key::Romaji, + win32km::VK_ATTN => Key::KanaMode, + win32km::VK_CRSEL => Key::CrSel, + win32km::VK_EXSEL => Key::ExSel, + win32km::VK_EREOF => Key::EraseEof, + win32km::VK_PLAY => Key::Play, + win32km::VK_ZOOM => Key::ZoomToggle, + win32km::VK_NONAME => Key::Unidentified(native_code), + win32km::VK_PA1 => Key::Unidentified(native_code), + win32km::VK_OEM_CLEAR => Key::Clear, + _ => Key::Unidentified(native_code), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs new file mode 100644 index 000000000..df8855bb0 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs @@ -0,0 +1,338 @@ +use crate::{ + keyboard::{KeyCode, NativeKeyCode}, + platform_impl::platform::util, +}; +use windows::Win32::{ + System::SystemServices::LANG_KOREAN, UI::Input::KeyboardAndMouse::GetKeyboardLayout, +}; + +pub fn keycode_to_scancode(code: KeyCode) -> Option { + // See `from_scancode` for more info + let hkl = unsafe { GetKeyboardLayout(0) }; + + let primary_lang_id = util::PRIMARYLANGID(hkl); + let is_korean = primary_lang_id == LANG_KOREAN; + + match code { + KeyCode::Backquote => Some(0x0029), + KeyCode::Backslash => Some(0x002B), + KeyCode::Backspace => Some(0x000E), + KeyCode::BracketLeft => Some(0x001A), + KeyCode::BracketRight => Some(0x001B), + KeyCode::Comma => Some(0x0033), + KeyCode::Digit0 => Some(0x000B), + KeyCode::Digit1 => Some(0x0002), + KeyCode::Digit2 => Some(0x0003), + KeyCode::Digit3 => Some(0x0004), + KeyCode::Digit4 => Some(0x0005), + KeyCode::Digit5 => Some(0x0006), + KeyCode::Digit6 => Some(0x0007), + KeyCode::Digit7 => Some(0x0008), + KeyCode::Digit8 => Some(0x0009), + KeyCode::Digit9 => Some(0x000A), + KeyCode::Equal => Some(0x000D), + KeyCode::IntlBackslash => Some(0x0056), + KeyCode::IntlRo => Some(0x0073), + KeyCode::IntlYen => Some(0x007D), + KeyCode::KeyA => Some(0x001E), + KeyCode::KeyB => Some(0x0030), + KeyCode::KeyC => Some(0x002E), + KeyCode::KeyD => Some(0x0020), + KeyCode::KeyE => Some(0x0012), + KeyCode::KeyF => Some(0x0021), + KeyCode::KeyG => Some(0x0022), + KeyCode::KeyH => Some(0x0023), + KeyCode::KeyI => Some(0x0017), + KeyCode::KeyJ => Some(0x0024), + KeyCode::KeyK => Some(0x0025), + KeyCode::KeyL => Some(0x0026), + KeyCode::KeyM => Some(0x0032), + KeyCode::KeyN => Some(0x0031), + KeyCode::KeyO => Some(0x0018), + KeyCode::KeyP => Some(0x0019), + KeyCode::KeyQ => Some(0x0010), + KeyCode::KeyR => Some(0x0013), + KeyCode::KeyS => Some(0x001F), + KeyCode::KeyT => Some(0x0014), + KeyCode::KeyU => Some(0x0016), + KeyCode::KeyV => Some(0x002F), + KeyCode::KeyW => Some(0x0011), + KeyCode::KeyX => Some(0x002D), + KeyCode::KeyY => Some(0x0015), + KeyCode::KeyZ => Some(0x002C), + KeyCode::Minus => Some(0x000C), + KeyCode::Period => Some(0x0034), + KeyCode::Quote => Some(0x0028), + KeyCode::Semicolon => Some(0x0027), + KeyCode::Slash => Some(0x0035), + KeyCode::AltLeft => Some(0x0038), + KeyCode::AltRight => Some(0xE038), + KeyCode::CapsLock => Some(0x003A), + KeyCode::ContextMenu => Some(0xE05D), + KeyCode::ControlLeft => Some(0x001D), + KeyCode::ControlRight => Some(0xE01D), + KeyCode::Enter => Some(0x001C), + KeyCode::SuperLeft => Some(0xE05B), + KeyCode::SuperRight => Some(0xE05C), + KeyCode::ShiftLeft => Some(0x002A), + KeyCode::ShiftRight => Some(0x0036), + KeyCode::Space => Some(0x0039), + KeyCode::Tab => Some(0x000F), + KeyCode::Convert => Some(0x0079), + KeyCode::Lang1 => { + if is_korean { + Some(0xE0F2) + } else { + Some(0x0072) + } + } + KeyCode::Lang2 => { + if is_korean { + Some(0xE0F1) + } else { + Some(0x0071) + } + } + KeyCode::KanaMode => Some(0x0070), + KeyCode::NonConvert => Some(0x007B), + KeyCode::Delete => Some(0xE053), + KeyCode::End => Some(0xE04F), + KeyCode::Home => Some(0xE047), + KeyCode::Insert => Some(0xE052), + KeyCode::PageDown => Some(0xE051), + KeyCode::PageUp => Some(0xE049), + KeyCode::ArrowDown => Some(0xE050), + KeyCode::ArrowLeft => Some(0xE04B), + KeyCode::ArrowRight => Some(0xE04D), + KeyCode::ArrowUp => Some(0xE048), + KeyCode::NumLock => Some(0xE045), + KeyCode::Numpad0 => Some(0x0052), + KeyCode::Numpad1 => Some(0x004F), + KeyCode::Numpad2 => Some(0x0050), + KeyCode::Numpad3 => Some(0x0051), + KeyCode::Numpad4 => Some(0x004B), + KeyCode::Numpad5 => Some(0x004C), + KeyCode::Numpad6 => Some(0x004D), + KeyCode::Numpad7 => Some(0x0047), + KeyCode::Numpad8 => Some(0x0048), + KeyCode::Numpad9 => Some(0x0049), + KeyCode::NumpadAdd => Some(0x004E), + KeyCode::NumpadComma => Some(0x007E), + KeyCode::NumpadDecimal => Some(0x0053), + KeyCode::NumpadDivide => Some(0xE035), + KeyCode::NumpadEnter => Some(0xE01C), + KeyCode::NumpadEqual => Some(0x0059), + KeyCode::NumpadMultiply => Some(0x0037), + KeyCode::NumpadSubtract => Some(0x004A), + KeyCode::Escape => Some(0x0001), + KeyCode::F1 => Some(0x003B), + KeyCode::F2 => Some(0x003C), + KeyCode::F3 => Some(0x003D), + KeyCode::F4 => Some(0x003E), + KeyCode::F5 => Some(0x003F), + KeyCode::F6 => Some(0x0040), + KeyCode::F7 => Some(0x0041), + KeyCode::F8 => Some(0x0042), + KeyCode::F9 => Some(0x0043), + KeyCode::F10 => Some(0x0044), + KeyCode::F11 => Some(0x0057), + KeyCode::F12 => Some(0x0058), + KeyCode::F13 => Some(0x0064), + KeyCode::F14 => Some(0x0065), + KeyCode::F15 => Some(0x0066), + KeyCode::F16 => Some(0x0067), + KeyCode::F17 => Some(0x0068), + KeyCode::F18 => Some(0x0069), + KeyCode::F19 => Some(0x006A), + KeyCode::F20 => Some(0x006B), + KeyCode::F21 => Some(0x006C), + KeyCode::F22 => Some(0x006D), + KeyCode::F23 => Some(0x006E), + KeyCode::F24 => Some(0x0076), + KeyCode::PrintScreen => Some(0xE037), + //KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen + KeyCode::ScrollLock => Some(0x0046), + KeyCode::Pause => Some(0x0045), + //KeyCode::Pause => Some(0xE046), // Ctrl + Pause + KeyCode::BrowserBack => Some(0xE06A), + KeyCode::BrowserFavorites => Some(0xE066), + KeyCode::BrowserForward => Some(0xE069), + KeyCode::BrowserHome => Some(0xE032), + KeyCode::BrowserRefresh => Some(0xE067), + KeyCode::BrowserSearch => Some(0xE065), + KeyCode::BrowserStop => Some(0xE068), + KeyCode::LaunchApp1 => Some(0xE06B), + KeyCode::LaunchApp2 => Some(0xE021), + KeyCode::LaunchMail => Some(0xE06C), + KeyCode::MediaPlayPause => Some(0xE022), + KeyCode::MediaSelect => Some(0xE06D), + KeyCode::MediaStop => Some(0xE024), + KeyCode::MediaTrackNext => Some(0xE019), + KeyCode::MediaTrackPrevious => Some(0xE010), + KeyCode::Power => Some(0xE05E), + KeyCode::AudioVolumeDown => Some(0xE02E), + KeyCode::AudioVolumeMute => Some(0xE020), + KeyCode::AudioVolumeUp => Some(0xE030), + KeyCode::Unidentified(NativeKeyCode::Windows(scancode)) => Some(scancode as u32), + _ => None, + } +} + +pub fn keycode_from_scancode(scancode: u32) -> KeyCode { + // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // and: https://www.w3.org/TR/uievents-code/ + // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source + + match scancode { + 0x0029 => KeyCode::Backquote, + 0x002B => KeyCode::Backslash, + 0x000E => KeyCode::Backspace, + 0x001A => KeyCode::BracketLeft, + 0x001B => KeyCode::BracketRight, + 0x0033 => KeyCode::Comma, + 0x000B => KeyCode::Digit0, + 0x0002 => KeyCode::Digit1, + 0x0003 => KeyCode::Digit2, + 0x0004 => KeyCode::Digit3, + 0x0005 => KeyCode::Digit4, + 0x0006 => KeyCode::Digit5, + 0x0007 => KeyCode::Digit6, + 0x0008 => KeyCode::Digit7, + 0x0009 => KeyCode::Digit8, + 0x000A => KeyCode::Digit9, + 0x000D => KeyCode::Equal, + 0x0056 => KeyCode::IntlBackslash, + 0x0073 => KeyCode::IntlRo, + 0x007D => KeyCode::IntlYen, + 0x001E => KeyCode::KeyA, + 0x0030 => KeyCode::KeyB, + 0x002E => KeyCode::KeyC, + 0x0020 => KeyCode::KeyD, + 0x0012 => KeyCode::KeyE, + 0x0021 => KeyCode::KeyF, + 0x0022 => KeyCode::KeyG, + 0x0023 => KeyCode::KeyH, + 0x0017 => KeyCode::KeyI, + 0x0024 => KeyCode::KeyJ, + 0x0025 => KeyCode::KeyK, + 0x0026 => KeyCode::KeyL, + 0x0032 => KeyCode::KeyM, + 0x0031 => KeyCode::KeyN, + 0x0018 => KeyCode::KeyO, + 0x0019 => KeyCode::KeyP, + 0x0010 => KeyCode::KeyQ, + 0x0013 => KeyCode::KeyR, + 0x001F => KeyCode::KeyS, + 0x0014 => KeyCode::KeyT, + 0x0016 => KeyCode::KeyU, + 0x002F => KeyCode::KeyV, + 0x0011 => KeyCode::KeyW, + 0x002D => KeyCode::KeyX, + 0x0015 => KeyCode::KeyY, + 0x002C => KeyCode::KeyZ, + 0x000C => KeyCode::Minus, + 0x0034 => KeyCode::Period, + 0x0028 => KeyCode::Quote, + 0x0027 => KeyCode::Semicolon, + 0x0035 => KeyCode::Slash, + 0x0038 => KeyCode::AltLeft, + 0xE038 => KeyCode::AltRight, + 0x003A => KeyCode::CapsLock, + 0xE05D => KeyCode::ContextMenu, + 0x001D => KeyCode::ControlLeft, + 0xE01D => KeyCode::ControlRight, + 0x001C => KeyCode::Enter, + 0xE05B => KeyCode::SuperLeft, + 0xE05C => KeyCode::SuperRight, + 0x002A => KeyCode::ShiftLeft, + 0x0036 => KeyCode::ShiftRight, + 0x0039 => KeyCode::Space, + 0x000F => KeyCode::Tab, + 0x0079 => KeyCode::Convert, + 0x0072 => KeyCode::Lang1, // for non-Korean layout + 0xE0F2 => KeyCode::Lang1, // for Korean layout + 0x0071 => KeyCode::Lang2, // for non-Korean layout + 0xE0F1 => KeyCode::Lang2, // for Korean layout + 0x0070 => KeyCode::KanaMode, + 0x007B => KeyCode::NonConvert, + 0xE053 => KeyCode::Delete, + 0xE04F => KeyCode::End, + 0xE047 => KeyCode::Home, + 0xE052 => KeyCode::Insert, + 0xE051 => KeyCode::PageDown, + 0xE049 => KeyCode::PageUp, + 0xE050 => KeyCode::ArrowDown, + 0xE04B => KeyCode::ArrowLeft, + 0xE04D => KeyCode::ArrowRight, + 0xE048 => KeyCode::ArrowUp, + 0xE045 => KeyCode::NumLock, + 0x0052 => KeyCode::Numpad0, + 0x004F => KeyCode::Numpad1, + 0x0050 => KeyCode::Numpad2, + 0x0051 => KeyCode::Numpad3, + 0x004B => KeyCode::Numpad4, + 0x004C => KeyCode::Numpad5, + 0x004D => KeyCode::Numpad6, + 0x0047 => KeyCode::Numpad7, + 0x0048 => KeyCode::Numpad8, + 0x0049 => KeyCode::Numpad9, + 0x004E => KeyCode::NumpadAdd, + 0x007E => KeyCode::NumpadComma, + 0x0053 => KeyCode::NumpadDecimal, + 0xE035 => KeyCode::NumpadDivide, + 0xE01C => KeyCode::NumpadEnter, + 0x0059 => KeyCode::NumpadEqual, + 0x0037 => KeyCode::NumpadMultiply, + 0x004A => KeyCode::NumpadSubtract, + 0x0001 => KeyCode::Escape, + 0x003B => KeyCode::F1, + 0x003C => KeyCode::F2, + 0x003D => KeyCode::F3, + 0x003E => KeyCode::F4, + 0x003F => KeyCode::F5, + 0x0040 => KeyCode::F6, + 0x0041 => KeyCode::F7, + 0x0042 => KeyCode::F8, + 0x0043 => KeyCode::F9, + 0x0044 => KeyCode::F10, + 0x0057 => KeyCode::F11, + 0x0058 => KeyCode::F12, + 0x0064 => KeyCode::F13, + 0x0065 => KeyCode::F14, + 0x0066 => KeyCode::F15, + 0x0067 => KeyCode::F16, + 0x0068 => KeyCode::F17, + 0x0069 => KeyCode::F18, + 0x006A => KeyCode::F19, + 0x006B => KeyCode::F20, + 0x006C => KeyCode::F21, + 0x006D => KeyCode::F22, + 0x006E => KeyCode::F23, + 0x0076 => KeyCode::F24, + 0xE037 => KeyCode::PrintScreen, + 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen + 0x0046 => KeyCode::ScrollLock, + 0x0045 => KeyCode::Pause, + 0xE046 => KeyCode::Pause, // Ctrl + Pause + 0xE06A => KeyCode::BrowserBack, + 0xE066 => KeyCode::BrowserFavorites, + 0xE069 => KeyCode::BrowserForward, + 0xE032 => KeyCode::BrowserHome, + 0xE067 => KeyCode::BrowserRefresh, + 0xE065 => KeyCode::BrowserSearch, + 0xE068 => KeyCode::BrowserStop, + 0xE06B => KeyCode::LaunchApp1, + 0xE021 => KeyCode::LaunchApp2, + 0xE06C => KeyCode::LaunchMail, + 0xE022 => KeyCode::MediaPlayPause, + 0xE06D => KeyCode::MediaSelect, + 0xE024 => KeyCode::MediaStop, + 0xE019 => KeyCode::MediaTrackNext, + 0xE010 => KeyCode::MediaTrackPrevious, + 0xE05E => KeyCode::Power, + 0xE02E => KeyCode::AudioVolumeDown, + 0xE020 => KeyCode::AudioVolumeMute, + 0xE030 => KeyCode::AudioVolumeUp, + _ => KeyCode::Unidentified(NativeKeyCode::Windows(scancode as u16)), + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs new file mode 100644 index 000000000..169d35e03 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs @@ -0,0 +1,157 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use windows::Win32::{ + Foundation::{HANDLE, HWND}, + UI::WindowsAndMessaging::HMENU, +}; + +pub(crate) use self::{ + event_loop::{ + EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, + }, + icon::WinIcon, + keycode::{keycode_from_scancode, keycode_to_scancode}, + monitor::{MonitorHandle, VideoMode}, + window::Window, +}; + +pub use self::icon::WinIcon as PlatformIcon; + +use crate::{event::DeviceId as RootDeviceId, icon::Icon, keyboard::Key}; +mod keycode; + +#[non_exhaustive] +#[derive(Clone)] +pub enum Parent { + None, + ChildOf(HWND), + OwnedBy(HWND), +} + +#[derive(Clone)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub parent: Parent, + pub menu: Option, + pub taskbar_icon: Option, + pub skip_taskbar: bool, + pub window_classname: String, + pub no_redirection_bitmap: bool, + pub drag_and_drop: bool, + pub decoration_shadow: bool, + pub rtl: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + parent: Parent::None, + menu: None, + taskbar_icon: None, + no_redirection_bitmap: false, + drag_and_drop: true, + skip_taskbar: false, + window_classname: "Window Class".to_string(), + decoration_shadow: true, + rtl: false, + } + } +} + +unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} +unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(isize); + +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId(0) + } +} + +impl DeviceId { + pub fn persistent_identifier(&self) -> Option { + if self.0 != 0 { + raw_input::get_raw_input_device_name(HANDLE(self.0 as _)) + } else { + None + } + } +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum OsError { + #[allow(unused)] + CreationError(&'static str), + IoError(std::io::Error), +} +impl std::error::Error for OsError {} + +impl std::fmt::Display for OsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OsError::CreationError(e) => f.pad(e), + OsError::IoError(e) => f.pad(&e.to_string()), + } + } +} + +impl From for OsError { + fn from(value: windows::core::Error) -> Self { + OsError::IoError(value.into()) + } +} + +impl From for crate::error::OsError { + fn from(value: windows::core::Error) -> Self { + os_error!(OsError::IoError(value.into())) + } +} + +impl From for crate::error::ExternalError { + fn from(value: windows::core::Error) -> Self { + crate::error::ExternalError::Os(os_error!(OsError::IoError(value.into()))) + } +} + +// Constant device ID, to be removed when this backend is updated to report real device IDs. +const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); + +fn wrap_device_id(id: isize) -> RootDeviceId { + RootDeviceId(DeviceId(id)) +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifiers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId(isize); +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} + +impl WindowId { + pub unsafe fn dummy() -> Self { + WindowId(0) + } +} + +#[macro_use] +mod util; +mod dark_mode; +mod dpi; +mod drop_handler; +mod event_loop; +mod icon; +mod ime; +mod keyboard; +mod keyboard_layout; +mod monitor; +mod raw_input; +mod window; +mod window_state; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs new file mode 100644 index 000000000..dc62064cf --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs @@ -0,0 +1,280 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use windows::{ + core::{BOOL, PCWSTR}, + Win32::{ + Foundation::{HWND, LPARAM, POINT, RECT}, + Graphics::Gdi::*, + UI::WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI, + }, +}; + +use std::{ + collections::{BTreeSet, VecDeque}, + io, mem, +}; + +use super::util; +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::platform::{ + dpi::{dpi_to_scale_factor, get_monitor_dpi}, + window::Window, + }, +}; + +#[derive(Clone)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, + pub(crate) native_video_mode: DEVMODEW, +} + +impl PartialEq for VideoMode { + fn eq(&self, other: &Self) -> bool { + self.size == other.size + && self.bit_depth == other.bit_depth + && self.refresh_rate == other.refresh_rate + && self.monitor == other.monitor + } +} + +impl Eq for VideoMode {} + +impl std::hash::Hash for VideoMode { + fn hash(&self, state: &mut H) { + self.size.hash(state); + self.bit_depth.hash(state); + self.refresh_rate.hash(state); + self.monitor.hash(state); + } +} + +impl std::fmt::Debug for VideoMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VideoMode") + .field("size", &self.size) + .field("bit_depth", &self.bit_depth) + .field("refresh_rate", &self.refresh_rate) + .field("monitor", &self.monitor) + .finish() + } +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub struct MonitorHandle(isize); + +unsafe extern "system" fn monitor_enum_proc( + hmonitor: HMONITOR, + _hdc: HDC, + _place: *mut RECT, + data: LPARAM, +) -> BOOL { + let monitors = data.0 as *mut VecDeque; + (*monitors).push_back(MonitorHandle::new(hmonitor)); + true.into() // continue enumeration +} + +pub fn available_monitors() -> VecDeque { + let mut monitors: VecDeque = VecDeque::new(); + unsafe { + let _ = EnumDisplayMonitors( + None, + None, + Some(monitor_enum_proc), + LPARAM(&mut monitors as *mut _ as _), + ); + } + monitors +} + +pub fn primary_monitor() -> MonitorHandle { + const ORIGIN: POINT = POINT { x: 0, y: 0 }; + let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) }; + MonitorHandle::new(hmonitor) +} + +pub fn current_monitor(hwnd: HWND) -> MonitorHandle { + let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; + MonitorHandle::new(hmonitor) +} + +pub fn from_point(x: f64, y: f64) -> Option { + let hmonitor = unsafe { + MonitorFromPoint( + POINT { + x: x as i32, + y: y as i32, + }, + MONITOR_DEFAULTTONULL, + ) + }; + if !hmonitor.is_invalid() { + Some(MonitorHandle::new(hmonitor)) + } else { + None + } +} + +impl Window { + pub fn available_monitors(&self) -> VecDeque { + available_monitors() + } + + pub fn primary_monitor(&self) -> Option { + let monitor = primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) + } + + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + from_point(x, y).map(|inner| RootMonitorHandle { inner }) + } +} + +pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { + let mut monitor_info = MONITORINFOEXW::default(); + monitor_info.monitorInfo.cbSize = mem::size_of::() as u32; + let status = unsafe { + GetMonitorInfoW( + hmonitor, + &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO, + ) + }; + if !status.as_bool() { + Err(io::Error::last_os_error()) + } else { + Ok(monitor_info) + } +} + +impl MonitorHandle { + pub(crate) fn new(hmonitor: HMONITOR) -> Self { + MonitorHandle(hmonitor.0 as _) + } + + #[inline] + pub fn name(&self) -> Option { + let monitor_info = get_monitor_info(self.hmonitor()).ok()?; + Some(util::wchar_ptr_to_string(PCWSTR::from_raw( + monitor_info.szDevice.as_ptr(), + ))) + } + + #[inline] + pub fn native_identifier(&self) -> String { + self.name().unwrap() + } + + #[inline] + pub fn hmonitor(&self) -> HMONITOR { + HMONITOR(self.0 as _) + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { + return PhysicalSize { + width: 0, + height: 0, + }; + }; + PhysicalSize { + width: (monitor_info.monitorInfo.rcMonitor.right - monitor_info.monitorInfo.rcMonitor.left) + as u32, + height: (monitor_info.monitorInfo.rcMonitor.bottom - monitor_info.monitorInfo.rcMonitor.top) + as u32, + } + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { + return PhysicalPosition { x: 0, y: 0 }; + }; + PhysicalPosition { + x: monitor_info.monitorInfo.rcMonitor.left, + y: monitor_info.monitorInfo.rcMonitor.top, + } + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + dpi_to_scale_factor(self.dpi()) + } + + pub fn dpi(&self) -> u32 { + get_monitor_dpi(self.hmonitor()).unwrap_or(USER_DEFAULT_SCREEN_DPI) + } + + #[inline] + pub fn video_modes(&self) -> impl Iterator { + // EnumDisplaySettingsExW can return duplicate values (or some of the + // fields are probably changing, but we aren't looking at those fields + // anyway), so we're using a BTreeSet deduplicate + let mut modes = BTreeSet::new(); + + let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { + return modes.into_iter(); + }; + + let device_name = PCWSTR::from_raw(monitor_info.szDevice.as_ptr()); + let mut i = 0; + loop { + unsafe { + let mut mode: DEVMODEW = mem::zeroed(); + mode.dmSize = mem::size_of_val(&mode) as u16; + if !EnumDisplaySettingsExW( + device_name, + ENUM_DISPLAY_SETTINGS_MODE(i), + &mut mode, + ENUM_DISPLAY_SETTINGS_FLAGS(0), + ) + .as_bool() + { + break; + } + i += 1; + + let required_fields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; + assert!(mode.dmFields & required_fields == required_fields); + + modes.insert(RootVideoMode { + video_mode: VideoMode { + size: (mode.dmPelsWidth, mode.dmPelsHeight), + bit_depth: mode.dmBitsPerPel as u16, + refresh_rate: mode.dmDisplayFrequency as u16, + monitor: self.clone(), + native_video_mode: mode, + }, + }); + } + } + + modes.into_iter() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs new file mode 100644 index 000000000..2ca23b5e4 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs @@ -0,0 +1,217 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::mem::{self, size_of}; + +use windows::Win32::{ + Devices::HumanInterfaceDevice::*, + Foundation::{HANDLE, HWND}, + UI::{ + Input::{self as win32i, *}, + WindowsAndMessaging::*, + }, +}; + +use crate::{event::ElementState, event_loop::DeviceEventFilter, platform_impl::platform::util}; + +#[allow(dead_code)] +pub fn get_raw_input_device_list() -> Option> { + let list_size = size_of::() as u32; + + let mut num_devices = 0; + let status = unsafe { GetRawInputDeviceList(None, &mut num_devices, list_size) }; + + if status == u32::MAX { + return None; + } + + let mut buffer = Vec::with_capacity(num_devices as _); + + let num_stored = + unsafe { GetRawInputDeviceList(Some(buffer.as_ptr() as _), &mut num_devices, list_size) }; + + if num_stored == u32::MAX { + return None; + } + + debug_assert_eq!(num_devices, num_stored); + + unsafe { buffer.set_len(num_devices as _) }; + + Some(buffer) +} + +#[non_exhaustive] +#[allow(dead_code)] +pub enum RawDeviceInfo { + Mouse(RID_DEVICE_INFO_MOUSE), + Keyboard(RID_DEVICE_INFO_KEYBOARD), + Hid(RID_DEVICE_INFO_HID), +} + +impl From for RawDeviceInfo { + fn from(info: RID_DEVICE_INFO) -> Self { + unsafe { + match info.dwType { + win32i::RIM_TYPEMOUSE => RawDeviceInfo::Mouse(info.Anonymous.mouse), + win32i::RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard), + win32i::RIM_TYPEHID => RawDeviceInfo::Hid(info.Anonymous.hid), + _ => unreachable!(), + } + } + } +} + +#[allow(dead_code)] +pub fn get_raw_input_device_info(handle: HANDLE) -> Option { + let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; + let info_size = size_of::() as u32; + + info.cbSize = info_size; + + let mut minimum_size = 0; + let status = unsafe { + GetRawInputDeviceInfoW( + Some(handle), + RIDI_DEVICEINFO, + Some(&mut info as *mut _ as _), + &mut minimum_size, + ) + }; + + if status == u32::MAX || status == 0 { + return None; + } + + debug_assert_eq!(info_size, status); + + Some(info.into()) +} + +pub fn get_raw_input_device_name(handle: HANDLE) -> Option { + let mut minimum_size = 0; + let status = + unsafe { GetRawInputDeviceInfoW(Some(handle), RIDI_DEVICENAME, None, &mut minimum_size) }; + + if status != 0 { + return None; + } + + let mut name: Vec = Vec::with_capacity(minimum_size as _); + + let status = unsafe { + GetRawInputDeviceInfoW( + Some(handle), + RIDI_DEVICENAME, + Some(name.as_ptr() as _), + &mut minimum_size, + ) + }; + + if status == u32::MAX || status == 0 { + return None; + } + + debug_assert_eq!(minimum_size, status); + + unsafe { name.set_len(minimum_size as _) }; + + Some(util::wchar_to_string(&name)) +} + +pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { + let device_size = size_of::() as u32; + unsafe { RegisterRawInputDevices(devices, device_size) }.is_ok() +} + +pub fn register_all_mice_and_keyboards_for_raw_input( + mut window_handle: HWND, + filter: DeviceEventFilter, +) -> bool { + // RIDEV_DEVNOTIFY: receive hotplug events + // RIDEV_INPUTSINK: receive events even if we're not in the foreground + // RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget) + let flags = match filter { + DeviceEventFilter::Always => { + window_handle = HWND(std::ptr::null_mut()); + RIDEV_REMOVE + } + DeviceEventFilter::Unfocused => RIDEV_DEVNOTIFY, + DeviceEventFilter::Never => RIDEV_DEVNOTIFY | RIDEV_INPUTSINK, + }; + + let devices: [RAWINPUTDEVICE; 2] = [ + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_MOUSE, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_KEYBOARD, + dwFlags: flags, + hwndTarget: window_handle, + }, + ]; + + register_raw_input_devices(&devices) +} + +pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { + let mut data: RAWINPUT = unsafe { mem::zeroed() }; + let mut data_size = size_of::() as u32; + let header_size = size_of::() as u32; + + let status = unsafe { + GetRawInputData( + handle, + RID_INPUT, + Some(&mut data as *mut _ as _), + &mut data_size, + header_size, + ) + }; + + if status == u32::MAX || status == 0 { + return None; + } + + Some(data) +} + +fn button_flags_to_element_state( + button_flags: u16, + down_flag: u32, + up_flag: u32, +) -> Option { + // We assume the same button won't be simultaneously pressed and released. + if util::has_flag(button_flags, down_flag as u16) { + Some(ElementState::Pressed) + } else if util::has_flag(button_flags, up_flag as u16) { + Some(ElementState::Released) + } else { + None + } +} + +pub fn get_raw_mouse_button_state(button_flags: u16) -> [Option; 3] { + [ + button_flags_to_element_state( + button_flags, + RI_MOUSE_LEFT_BUTTON_DOWN, + RI_MOUSE_LEFT_BUTTON_UP, + ), + button_flags_to_element_state( + button_flags, + RI_MOUSE_MIDDLE_BUTTON_DOWN, + RI_MOUSE_MIDDLE_BUTTON_UP, + ), + button_flags_to_element_state( + button_flags, + RI_MOUSE_RIGHT_BUTTON_DOWN, + RI_MOUSE_RIGHT_BUTTON_UP, + ), + ] +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs new file mode 100644 index 000000000..3535b00ab --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs @@ -0,0 +1,492 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + io, + iter::once, + mem, + ops::BitAnd, + os::windows::prelude::OsStrExt, + slice, + sync::atomic::{AtomicBool, Ordering}, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize}, + window::CursorIcon, +}; + +use std::sync::LazyLock; +use windows::{ + core::{BOOL, HRESULT, PCSTR, PCWSTR}, + Win32::{ + Foundation::{COLORREF, FARPROC, HWND, LPARAM, POINT, RECT, WPARAM}, + Globalization::lstrlenW, + Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, + System::LibraryLoader::*, + UI::{ + HiDpi::*, + Input::KeyboardAndMouse::*, + WindowsAndMessaging::{self as win32wm, *}, + }, + }, +}; + +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + +pub fn wchar_to_string(wchar: &[u16]) -> String { + String::from_utf16_lossy(wchar) +} + +pub fn wchar_ptr_to_string(wchar: PCWSTR) -> String { + let len = unsafe { lstrlenW(wchar) } as usize; + let wchar_slice = unsafe { slice::from_raw_parts(wchar.0, len) }; + wchar_to_string(wchar_slice) +} + +pub fn encode_wide(string: impl AsRef) -> Vec { + string.as_ref().encode_wide().chain(once(0)).collect() +} + +pub unsafe fn get_window_rect(hwnd: HWND) -> Option { + let mut rect = std::mem::zeroed(); + GetWindowRect(hwnd, &mut rect).ok().map(|_| rect) +} + +pub fn get_client_rect(hwnd: HWND) -> Result { + let mut rect = RECT::default(); + let mut top_left = POINT::default(); + + unsafe { + ClientToScreen(hwnd, &mut top_left).ok()?; + GetClientRect(hwnd, &mut rect)?; + } + + rect.left += top_left.x; + rect.top += top_left.y; + rect.right += top_left.x; + rect.bottom += top_left.y; + + Ok(rect) +} + +pub fn adjust_size(hwnd: HWND, size: PhysicalSize, is_decorated: bool) -> PhysicalSize { + let (width, height): (u32, u32) = size.into(); + let rect = RECT { + left: 0, + right: width as i32, + top: 0, + bottom: height as i32, + }; + let rect = adjust_window_rect(hwnd, rect, is_decorated).unwrap_or(rect); + PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _) +} + +pub(crate) fn set_inner_size_physical(window: HWND, x: i32, y: i32, is_decorated: bool) { + unsafe { + let rect = adjust_window_rect( + window, + RECT { + top: 0, + left: 0, + bottom: y, + right: x, + }, + is_decorated, + ) + .expect("adjust_window_rect failed"); + + let outer_x = (rect.right - rect.left).abs(); + let outer_y = (rect.top - rect.bottom).abs(); + let _ = SetWindowPos( + window, + None, + 0, + 0, + outer_x, + outer_y, + SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, + ); + let _ = InvalidateRgn(window, None, false); + } +} + +pub fn adjust_window_rect(hwnd: HWND, rect: RECT, is_decorated: bool) -> Option { + unsafe { + let mut style = WINDOW_STYLE(GetWindowLongW(hwnd, GWL_STYLE) as u32); + // if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so + // `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and + // calculates a correct size for the client area. + if !is_decorated { + style &= !WS_CAPTION; + style &= !WS_SIZEBOX; + } + let style_ex = WINDOW_EX_STYLE(GetWindowLongW(hwnd, GWL_EXSTYLE) as u32); + adjust_window_rect_with_styles(hwnd, style, style_ex, rect) + } +} + +pub fn adjust_window_rect_with_styles( + hwnd: HWND, + style: WINDOW_STYLE, + style_ex: WINDOW_EX_STYLE, + mut rect: RECT, +) -> Option { + let b_menu = !unsafe { GetMenu(hwnd) }.is_invalid(); + + if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = + (*GET_DPI_FOR_WINDOW, *ADJUST_WINDOW_RECT_EX_FOR_DPI) + { + let dpi = unsafe { get_dpi_for_window(hwnd) }; + if unsafe { adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi) } + .as_bool() + { + Some(rect) + } else { + None + } + } else { + unsafe { AdjustWindowRectEx(&mut rect, style, b_menu, style_ex) } + .ok() + .map(|_| rect) + } +} + +pub fn set_cursor_hidden(hidden: bool) { + static HIDDEN: AtomicBool = AtomicBool::new(false); + let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; + if changed { + unsafe { ShowCursor(!hidden) }; + } +} + +pub fn get_cursor_clip() -> windows::core::Result { + unsafe { + let mut rect = RECT::default(); + GetClipCursor(&mut rect).map(|_| rect) + } +} + +/// Sets the cursor's clip rect. +/// +/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event. +pub fn set_cursor_clip(rect: Option) -> windows::core::Result<()> { + unsafe { + let rect_ptr = rect.as_ref().map(|r| r as *const RECT); + ClipCursor(rect_ptr) + } +} + +pub fn get_desktop_rect() -> RECT { + unsafe { + let left = GetSystemMetrics(SM_XVIRTUALSCREEN); + let top = GetSystemMetrics(SM_YVIRTUALSCREEN); + RECT { + left, + top, + right: left + GetSystemMetrics(SM_CXVIRTUALSCREEN), + bottom: top + GetSystemMetrics(SM_CYVIRTUALSCREEN), + } + } +} + +pub fn is_focused(window: HWND) -> bool { + window == unsafe { GetActiveWindow() } +} + +pub fn is_visible(window: HWND) -> bool { + unsafe { IsWindowVisible(window).as_bool() } +} + +pub fn is_maximized(window: HWND) -> windows::core::Result { + let mut placement = WINDOWPLACEMENT { + length: mem::size_of::() as u32, + ..WINDOWPLACEMENT::default() + }; + unsafe { GetWindowPlacement(window, &mut placement)? }; + Ok(placement.showCmd == SW_MAXIMIZE.0 as u32) +} + +pub fn cursor_position() -> windows::core::Result> { + let mut pt = POINT { x: 0, y: 0 }; + unsafe { GetCursorPos(&mut pt)? }; + Ok((pt.x, pt.y).into()) +} + +impl CursorIcon { + pub(crate) fn to_windows_cursor(self) -> PCWSTR { + match self { + CursorIcon::Arrow | CursorIcon::Default => IDC_ARROW, + CursorIcon::Hand => IDC_HAND, + CursorIcon::Crosshair => IDC_CROSS, + CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM, + CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO, + CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => { + IDC_SIZEALL + } + CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize | CursorIcon::ColResize => { + IDC_SIZEWE + } + CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize | CursorIcon::RowResize => { + IDC_SIZENS + } + CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW, + CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE, + CursorIcon::Wait => IDC_WAIT, + CursorIcon::Progress => IDC_APPSTARTING, + CursorIcon::Help => IDC_HELP, + _ => IDC_ARROW, // use arrow for the missing cases. + } + } +} + +// Helper function to dynamically load function pointer. +// `library` and `function` must be zero-terminated. +pub(super) fn get_function_impl(library: &str, function: &str) -> FARPROC { + let library = encode_wide(library); + assert_eq!(function.chars().last(), Some('\0')); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default(); + if module.is_invalid() { + return None; + } + + unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) } +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + crate::platform_impl::platform::util::get_function_impl($lib, concat!(stringify!($func), '\0')) + .map(|f| unsafe { std::mem::transmute::<_, $func>(f) }) + }; +} + +pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL; +pub type SetProcessDpiAwareness = + unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT; +pub type SetProcessDpiAwarenessContext = + unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; +pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32; +pub type GetDpiForMonitor = unsafe extern "system" fn( + hmonitor: HMONITOR, + dpi_type: MONITOR_DPI_TYPE, + dpi_x: *mut u32, + dpi_y: *mut u32, +) -> HRESULT; +type GetSystemMetricsForDpi = + unsafe extern "system" fn(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32; +pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; +#[allow(non_snake_case)] +pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( + rect: *mut RECT, + dwStyle: WINDOW_STYLE, + bMenu: BOOL, + dwExStyle: WINDOW_EX_STYLE, + dpi: u32, +) -> BOOL; + +pub static GET_DPI_FOR_WINDOW: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); +pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); +pub static GET_DPI_FOR_MONITOR: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor)); +pub static GET_SYSTEM_METRICS_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); +pub static ENABLE_NON_CLIENT_DPI_SCALING: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); +pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); +pub static SET_PROCESS_DPI_AWARENESS: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); +pub static SET_PROCESS_DPI_AWARE: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDPIAware)); + +#[allow(non_snake_case)] +#[cfg(target_pointer_width = "32")] +pub fn SetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { + unsafe { win32wm::SetWindowLongW(window, index, value as _) as _ } +} + +#[allow(non_snake_case)] +#[cfg(target_pointer_width = "64")] +pub fn SetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { + unsafe { win32wm::SetWindowLongPtrW(window, index, value) } +} + +#[allow(non_snake_case)] +#[cfg(target_pointer_width = "32")] +pub fn GetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { + unsafe { win32wm::GetWindowLongW(window, index) as _ } +} + +#[allow(non_snake_case)] +#[cfg(target_pointer_width = "64")] +pub fn GetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { + unsafe { win32wm::GetWindowLongPtrW(window, index) } +} + +/// Implementation of the `LOWORD` macro. +#[allow(non_snake_case)] +#[inline] +pub fn LOWORD(dword: u32) -> u16 { + (dword & 0xFFFF) as u16 +} + +/// Implementation of the `HIWORD` macro. +#[allow(non_snake_case)] +#[inline] +pub fn HIWORD(dword: u32) -> u16 { + ((dword & 0xFFFF_0000) >> 16) as u16 +} + +/// Implementation of the `GET_X_LPARAM` macro. +#[allow(non_snake_case)] +#[inline] +pub fn GET_X_LPARAM(lparam: LPARAM) -> i16 { + ((lparam.0 as usize) & 0xFFFF) as u16 as i16 +} + +/// Implementation of the `GET_Y_LPARAM` macro. +#[allow(non_snake_case)] +#[inline] +pub fn GET_Y_LPARAM(lparam: LPARAM) -> i16 { + (((lparam.0 as usize) & 0xFFFF_0000) >> 16) as u16 as i16 +} + +/// Implementation of the `MAKELPARAM` macro. +/// Inverse of [GET_X_LPARAM] and [GET_Y_LPARAM] to put the (`x`, `y`) signed +/// coordinates/values back into an [LPARAM]. +#[allow(non_snake_case)] +#[inline] +pub fn MAKELPARAM(x: i16, y: i16) -> LPARAM { + LPARAM(((x as u16 as u32) | ((y as u16 as u32) << 16)) as usize as _) +} + +/// Implementation of the `GET_WHEEL_DELTA_WPARAM` macro. +#[allow(non_snake_case)] +#[inline] +pub fn GET_WHEEL_DELTA_WPARAM(wparam: WPARAM) -> i16 { + ((wparam.0 & 0xFFFF_0000) >> 16) as u16 as i16 +} + +/// Implementation of the `GET_XBUTTON_WPARAM` macro. +#[allow(non_snake_case)] +#[inline] +pub fn GET_XBUTTON_WPARAM(wparam: WPARAM) -> u16 { + ((wparam.0 & 0xFFFF_0000) >> 16) as u16 +} + +/// Implementation of the `PRIMARYLANGID` macro. +#[allow(non_snake_case)] +#[inline] +pub fn PRIMARYLANGID(hkl: HKL) -> u32 { + ((hkl.0 as usize) & 0x3FF) as u32 +} + +/// Implementation of the `RGB` macro. +#[allow(non_snake_case)] +#[inline] +pub fn RGB>(r: T, g: T, b: T) -> COLORREF { + COLORREF(r.into() | (g.into() << 8) | (b.into() << 16)) +} + +pub fn get_instance_handle() -> windows::Win32::Foundation::HINSTANCE { + // Gets the instance handle by taking the address of the + // pseudo-variable created by the microsoft linker: + // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 + + // This is preferred over GetModuleHandle(NULL) because it also works in DLLs: + // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance + + extern "C" { + static __ImageBase: windows::Win32::System::SystemServices::IMAGE_DOS_HEADER; + } + + windows::Win32::Foundation::HINSTANCE(unsafe { &__ImageBase as *const _ as _ }) +} + +pub static WIN_VERSION: LazyLock = + LazyLock::new(windows_version::OsVersion::current); + +pub fn get_frame_thickness(dpi: u32) -> i32 { + let resize_frame_thickness = unsafe { get_system_metrics_for_dpi(SM_CXSIZEFRAME, dpi) }; + let padding_thickness = unsafe { get_system_metrics_for_dpi(SM_CXPADDEDBORDER, dpi) }; + resize_frame_thickness + padding_thickness +} + +pub fn calculate_insets_for_dpi(dpi: u32) -> RECT { + // - On Windows 10 + // The top inset must be zero, since if there is any nonclient area, Windows will draw + // a full native titlebar outside the client area. (This doesn't occur in the maximized + // case.) + // + // - On Windows 11 + // The top inset is calculated using an empirical formula that I derived through various + // tests. Without this, the top 1-2 rows of pixels in our window would be obscured. + + let frame_thickness = get_frame_thickness(dpi); + + let top_inset = match WIN_VERSION.build { + v if v >= 22000 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32, + _ => 0, + }; + + RECT { + left: frame_thickness, + top: top_inset, + right: frame_thickness, + bottom: frame_thickness, + } +} + +/// Calcuclate window insets, used in WM_NCCALCSIZE +/// +/// Derived of GPUI implementation +/// see +pub fn calculate_window_insets(window: HWND) -> RECT { + let dpi = unsafe { super::dpi::hwnd_dpi(window) }; + calculate_insets_for_dpi(dpi) +} + +pub fn window_rect(hwnd: HWND) -> RECT { + unsafe { + let mut rect = RECT::default(); + if GetWindowRect(hwnd, &mut rect).is_err() { + panic!( + "Unexpected GetWindowRect failure: please report this error to \ + tauri-apps/tao" + ) + } + rect + } +} + +pub fn client_rect(hwnd: HWND) -> RECT { + unsafe { + let mut rect = RECT::default(); + if GetClientRect(hwnd, &mut rect).is_err() { + panic!( + "Unexpected GetClientRect failure: please report this error to \ + tauri-apps/tao" + ) + } + rect + } +} + +pub unsafe fn get_system_metrics_for_dpi(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32 { + #[allow(non_snake_case)] + if let Some(GetSystemMetricsForDpi) = *GET_SYSTEM_METRICS_FOR_DPI { + GetSystemMetricsForDpi(nindex, dpi) + } else { + GetSystemMetrics(nindex) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs new file mode 100644 index 000000000..c92bcfad0 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs @@ -0,0 +1,1568 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(target_os = "windows")] + +use mem::MaybeUninit; +use parking_lot::Mutex; +use std::{ + cell::{Cell, RefCell}, + ffi::OsStr, + io, mem, + os::windows::ffi::OsStrExt, + panic, + sync::Arc, +}; + +use crossbeam_channel as channel; +use windows::{ + core::PCWSTR, + Win32::{ + Foundation::{self as win32f, HINSTANCE, HWND, LPARAM, POINT, POINTS, RECT, WPARAM}, + Graphics::{ + Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, + Gdi::*, + }, + System::{Com::*, Ole::*}, + UI::{ + Input::{Ime::*, KeyboardAndMouse::*, Touch::*}, + Shell::{ITaskbarList4 as ITaskbarList, TaskbarList, *}, + WindowsAndMessaging::*, + }, + }, +}; + +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + icon::Icon, + monitor::MonitorHandle as RootMonitorHandle, + platform_impl::platform::{ + dark_mode::try_window_theme, + dpi::{dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi}, + drop_handler::FileDropHandler, + event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, + icon::{self, IconType}, + monitor::{self}, + util, + window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, + OsError, Parent, PlatformSpecificWindowBuilderAttributes, WindowId, + }, + window::{ + CursorIcon, Fullscreen, ProgressBarState, ProgressState, ResizeDirection, Theme, + UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, + }, +}; + +use super::{ + event_loop::CHANGE_THEME_MSG_ID, keyboard::KeyEventBuilder, util::calculate_insets_for_dpi, +}; + +use super::event_loop::WindowData; + +/// A simple non-owning wrapper around a window. +#[derive(Clone, Copy)] +pub struct WindowWrapper(pub HWND); + +// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually. +// For more info see: +// https://github.com/retep998/winapi-rs/issues/360 +// https://github.com/retep998/winapi-rs/issues/396 +unsafe impl Sync for WindowWrapper {} +unsafe impl Send for WindowWrapper {} + +impl WindowWrapper { + /// This is used for moving `WindowWrapper` instead of `window.0` + /// when using closures + /// + /// ```ignore + /// let window = self.window; + /// let not_send_sync = move || { window.0 }; + /// let send_sync = move || { window.hwnd() }; + /// ``` + fn hwnd(&self) -> HWND { + self.0 + } +} + +/// The Win32 implementation of the main `Window` object. +pub struct Window { + /// Main handle for the window. + window: WindowWrapper, + + /// The current window state. + window_state: Arc>, + + // The events loop proxy. + thread_executor: event_loop::EventLoopThreadExecutor, +} + +impl Window { + pub fn new( + event_loop: &EventLoopWindowTarget, + w_attr: WindowAttributes, + pl_attr: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + // We dispatch an `init` function because of code style. + // First person to remove the need for cloning here gets a cookie! + // + // done. you owe me -- ossi + unsafe { init::(w_attr, pl_attr, event_loop) } + } + + pub fn set_title(&self, text: &str) { + let text = util::encode_wide(text); + unsafe { + let _ = SetWindowTextW(self.hwnd(), PCWSTR::from_raw(text.as_ptr())); + } + } + + pub fn title(&self) -> String { + let len = unsafe { GetWindowTextLengthW(self.hwnd()) }; + let mut buf = vec![0; (len + 1) as usize]; + unsafe { GetWindowTextW(self.hwnd(), &mut buf) }; + String::from_utf16_lossy(&buf[..len as _]) + } + #[inline] + pub fn set_visible(&self, visible: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::VISIBLE, visible) + }); + }); + } + + #[inline] + pub fn set_focus(&self) { + let window = self.window; + let window_flags = self.window_state.lock().window_flags(); + + let is_visible = window_flags.contains(WindowFlags::VISIBLE); + let is_minimized = window_flags.contains(WindowFlags::MINIMIZED); + let is_foreground = window.0 == unsafe { GetForegroundWindow() }; + + if is_visible && !is_minimized && !is_foreground { + unsafe { force_window_active(window.0) }; + } + } + + #[inline] + pub fn set_focusable(&self, focusable: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::FOCUSABLE, focusable) + }); + }); + } + + #[inline] + pub fn is_focused(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.has_active_focus() + } + + #[inline] + pub fn request_redraw(&self) { + unsafe { + let _ = RedrawWindow(Some(self.hwnd()), None, None, RDW_INTERNALPAINT); + } + } + + #[inline] + pub fn outer_position(&self) -> Result, NotSupportedError> { + unsafe { util::get_window_rect(self.hwnd()) } + .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) + .expect("Unexpected GetWindowRect failure") + } + + #[inline] + pub fn inner_position(&self) -> Result, NotSupportedError> { + let mut position = POINT::default(); + if !unsafe { ClientToScreen(self.hwnd(), &mut position) }.as_bool() { + panic!("Unexpected ClientToScreen failure") + } + Ok(PhysicalPosition::new(position.x, position.y)) + } + + #[inline] + pub fn set_outer_position(&self, position: Position) { + let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); + + let window_state = Arc::clone(&self.window_state); + let window = self.window; + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + }); + + unsafe { + let _ = SetWindowPos( + self.hwnd(), + None, + x, + y, + 0, + 0, + SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, + ); + let _ = InvalidateRgn(self.hwnd(), None, false); + } + } + + #[inline] + pub fn inner_size(&self) -> PhysicalSize { + let client_rect = util::client_rect(self.hwnd()); + PhysicalSize::new( + (client_rect.right - client_rect.left) as u32, + (client_rect.bottom - client_rect.top) as u32, + ) + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + let window_rect = util::window_rect(self.hwnd()); + PhysicalSize::new( + (window_rect.right - window_rect.left) as u32, + (window_rect.bottom - window_rect.top) as u32, + ) + } + + #[inline] + pub fn set_inner_size(&self, size: Size) { + let scale_factor = self.scale_factor(); + + let (mut desired_width, mut desired_height) = size.to_physical::(scale_factor).into(); + + let window_flags = self.window_state.lock().window_flags; + + // undecorated windows with shadows have hidden offsets + // we need to calculate them and account for them in new size + // + // implementation derived from GPUI + // see + if window_flags.undecorated_with_shadows() { + let hwnd = self.hwnd(); + + let client_rect = util::client_rect(hwnd); + let window_rect = util::window_rect(hwnd); + + let width_offset = + (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left); + let height_offset = + (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top); + + desired_width += width_offset; + desired_height += height_offset; + } + + let window_state = Arc::clone(&self.window_state); + let window = self.window; + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + }); + + util::set_inner_size_physical( + self.hwnd(), + desired_width, + desired_height, + window_flags.contains(WindowFlags::MARKER_DECORATIONS), + ); + } + + #[inline] + pub fn set_min_inner_size(&self, size: Option) { + let (width, height) = size.map(crate::extract_width_height).unzip(); + + { + let mut window_state = self.window_state.lock(); + window_state.size_constraints.min_width = width; + window_state.size_constraints.min_height = height; + } + + // Make windows re-check the window size bounds. + let size = self.inner_size(); + self.set_inner_size(size.into()); + } + + #[inline] + pub fn set_max_inner_size(&self, size: Option) { + let (width, height) = size.map(crate::extract_width_height).unzip(); + + { + let mut window_state = self.window_state.lock(); + window_state.size_constraints.max_width = width; + window_state.size_constraints.max_height = height; + } + + // Make windows re-check the window size bounds. + let size = self.inner_size(); + self.set_inner_size(size.into()); + } + + #[inline] + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + self.window_state.lock().size_constraints = constraints; + // Make windows re-check the window size bounds. + let size = self.inner_size(); + self.set_inner_size(size.into()); + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::RESIZABLE, resizable) + }); + }); + } + + #[inline] + pub fn set_minimizable(&self, minimizable: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MINIMIZABLE, minimizable) + }); + }); + } + + #[inline] + pub fn set_maximizable(&self, maximizable: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MAXIMIZABLE, maximizable) + }); + }); + } + + #[inline] + pub fn set_closable(&self, closable: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::CLOSABLE, closable) + }); + }); + } + + /// Returns the `hwnd` of this window. + #[inline] + pub fn hwnd(&self) -> HWND { + self.window.hwnd() + } + + #[inline] + pub fn hinstance(&self) -> HINSTANCE { + util::get_instance_handle() + } + + #[cfg(feature = "rwh_04")] + #[inline] + pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { + let mut window_handle = rwh_04::Win32Handle::empty(); + window_handle.hwnd = self.hwnd().0; + let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); + window_handle.hinstance = hinstance as *mut _; + rwh_04::RawWindowHandle::Win32(window_handle) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { + let mut window_handle = rwh_05::Win32WindowHandle::empty(); + window_handle.hwnd = self.hwnd().0; + let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); + window_handle.hinstance = hinstance as *mut _; + rwh_05::RawWindowHandle::Win32(window_handle) + } + + #[cfg(feature = "rwh_05")] + #[inline] + pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { + rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_window_handle_rwh_06(&self) -> Result { + let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { + // SAFETY: Handle will never be zero. + let window = self.hwnd().0; + std::num::NonZeroIsize::new_unchecked(window as _) + }); + let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); + window_handle.hinstance = std::num::NonZeroIsize::new(hinstance); + Ok(rwh_06::RawWindowHandle::Win32(window_handle)) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub fn raw_display_handle_rwh_06(&self) -> Result { + Ok(rwh_06::RawDisplayHandle::Windows( + rwh_06::WindowsDisplayHandle::new(), + )) + } + + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + self.window_state.lock().mouse.cursor = cursor; + self.thread_executor.execute_in_thread(move || unsafe { + let cursor = LoadCursorW(None, cursor.to_windows_cursor()).ok(); + SetCursor(cursor); + }); + } + + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + let (tx, rx) = channel::unbounded(); + + self.thread_executor.execute_in_thread(move || { + let result = window_state + .lock() + .mouse + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::GRABBED, grab)) + .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e)))); + let _ = tx.send(result); + }); + rx.recv().unwrap() + } + + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + let (tx, rx) = channel::unbounded(); + + self.thread_executor.execute_in_thread(move || { + let result = window_state + .lock() + .mouse + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::HIDDEN, !visible)) + .map_err(|e| e.to_string()); + let _ = tx.send(result); + }); + rx.recv().unwrap().ok(); + } + + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + util::cursor_position().map_err(Into::into) + } + + #[inline] + pub fn scale_factor(&self) -> f64 { + self.window_state.lock().scale_factor + } + + #[inline] + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { + let scale_factor = self.scale_factor(); + let (x, y) = position.to_physical::(scale_factor).into(); + + let mut point = POINT { x, y }; + unsafe { + if !ClientToScreen(self.hwnd(), &mut point).as_bool() { + return Err(ExternalError::Os(os_error!(OsError::IoError( + io::Error::last_os_error() + )))); + } + SetCursorPos(point.x, point.y) + .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e.into())))) + } + } + + fn handle_os_dragging(&self, wparam: WPARAM) -> Result<(), ExternalError> { + let points = { + let mut pos = unsafe { mem::zeroed() }; + unsafe { GetCursorPos(&mut pos)? }; + pos + }; + let points = POINTS { + x: points.x as i16, + y: points.y as i16, + }; + unsafe { ReleaseCapture()? }; + + self.window_state.lock().dragging = true; + + unsafe { + PostMessageW( + Some(self.hwnd()), + WM_NCLBUTTONDOWN, + wparam, + LPARAM(&points as *const _ as _), + )? + }; + + Ok(()) + } + + #[inline] + pub fn drag_window(&self) -> Result<(), ExternalError> { + self.handle_os_dragging(WPARAM(HTCAPTION as _)) + } + + #[inline] + pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { + self.handle_os_dragging(WPARAM(direction.to_win32() as _)) + } + + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::IGNORE_CURSOR_EVENT, ignore) + }); + }); + + Ok(()) + } + + #[inline] + pub fn id(&self) -> WindowId { + WindowId(self.hwnd().0 as _) + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + let is_minimized = self.is_minimized(); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags_in_place(&mut window_state.lock(), |f| { + f.set(WindowFlags::MINIMIZED, is_minimized) + }); + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MINIMIZED, minimized) + }); + }); + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MAXIMIZED, maximized) + }); + }); + } + + #[inline] + pub fn is_maximized(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::MAXIMIZED) + } + + #[inline] + pub fn is_always_on_top(&self) -> bool { + let window_state = self.window_state.lock(); + window_state + .window_flags + .contains(WindowFlags::ALWAYS_ON_TOP) + } + + #[inline] + pub fn is_minimized(&self) -> bool { + unsafe { IsIconic(self.hwnd()) }.as_bool() + } + + #[inline] + pub fn is_resizable(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::RESIZABLE) + } + + #[inline] + pub fn is_minimizable(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::MINIMIZABLE) + } + + #[inline] + pub fn is_maximizable(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) + } + + #[inline] + pub fn is_closable(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::CLOSABLE) + } + + #[inline] + pub fn is_decorated(&self) -> bool { + let window_state = self.window_state.lock(); + window_state + .window_flags + .contains(WindowFlags::MARKER_DECORATIONS) + } + + #[inline] + pub fn is_visible(&self) -> bool { + util::is_visible(self.hwnd()) + } + + #[inline] + pub fn fullscreen(&self) -> Option { + let window_state = self.window_state.lock(); + window_state.fullscreen.clone() + } + + #[inline] + pub fn set_fullscreen(&self, fullscreen: Option) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + let mut window_state_lock = window_state.lock(); + let old_fullscreen = window_state_lock.fullscreen.clone(); + + match (&old_fullscreen, &fullscreen) { + // Return if we already in the same fullscreen mode + _ if old_fullscreen == fullscreen => return, + // Return if saved Borderless(monitor) is the same as current monitor when requested fullscreen is Borderless(None) + (Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None))) + if monitor.inner == monitor::current_monitor(window.0) => + { + return + } + _ => {} + } + + window_state_lock.fullscreen = fullscreen.clone(); + drop(window_state_lock); + + let window_isize = window.0 .0 as isize; + self.thread_executor.execute_in_thread(move || { + let hwnd = HWND(window_isize as _); + // Change video mode if we're transitioning to or from exclusive + // fullscreen + match (&old_fullscreen, &fullscreen) { + (&None, &Some(Fullscreen::Exclusive(ref video_mode))) + | (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref video_mode))) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) => { + let monitor = video_mode.monitor(); + + let mut display_name = OsStr::new(&monitor.inner.native_identifier()) + .encode_wide() + .collect::>(); + // `encode_wide` does not add a null-terminator but + // `ChangeDisplaySettingsExW` requires a null-terminated + // string, so add it + display_name.push(0); + + let native_video_mode = video_mode.video_mode.native_video_mode; + + let res = unsafe { + ChangeDisplaySettingsExW( + PCWSTR::from_raw(display_name.as_ptr()), + Some(&native_video_mode), + None, + CDS_FULLSCREEN, + None, + ) + }; + + debug_assert!(res != DISP_CHANGE_BADFLAGS); + debug_assert!(res != DISP_CHANGE_BADMODE); + debug_assert!(res != DISP_CHANGE_BADPARAM); + debug_assert!(res != DISP_CHANGE_FAILED); + assert_eq!(res, DISP_CHANGE_SUCCESSFUL); + } + (&Some(Fullscreen::Exclusive(_)), &None) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { + let res = + unsafe { ChangeDisplaySettingsExW(PCWSTR::null(), None, None, CDS_FULLSCREEN, None) }; + + debug_assert!(res != DISP_CHANGE_BADFLAGS); + debug_assert!(res != DISP_CHANGE_BADMODE); + debug_assert!(res != DISP_CHANGE_BADPARAM); + debug_assert!(res != DISP_CHANGE_FAILED); + assert_eq!(res, DISP_CHANGE_SUCCESSFUL); + } + _ => (), + } + + unsafe { + // There are some scenarios where calling `ChangeDisplaySettingsExW` takes long + // enough to execute that the DWM thinks our program has frozen and takes over + // our program's window. When that happens, the `SetWindowPos` call below gets + // eaten and the window doesn't get set to the proper fullscreen position. + // + // Calling `PeekMessageW` here notifies Windows that our process is still running + // fine, taking control back from the DWM and ensuring that the `SetWindowPos` call + // below goes through. + let mut msg = MSG::default(); + let _ = PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE); + } + + // Update window style + WindowState::set_window_flags(window_state.lock(), HWND(window_isize as _), |f| { + f.set( + WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, + matches!(fullscreen, Some(Fullscreen::Exclusive(_))), + ); + f.set( + WindowFlags::MARKER_BORDERLESS_FULLSCREEN, + matches!(fullscreen, Some(Fullscreen::Borderless(_))), + ); + }); + + // Update window bounds + match &fullscreen { + Some(fullscreen) => { + // Save window bounds before entering fullscreen + let placement = unsafe { + let mut placement = WINDOWPLACEMENT::default(); + let _ = GetWindowPlacement(hwnd, &mut placement); + placement + }; + + window_state.lock().saved_window = Some(SavedWindow { placement }); + + let monitor = match &fullscreen { + Fullscreen::Exclusive(video_mode) => video_mode.monitor(), + Fullscreen::Borderless(Some(monitor)) => monitor.clone(), + Fullscreen::Borderless(None) => RootMonitorHandle { + inner: monitor::current_monitor(hwnd), + }, + }; + + let position: (i32, i32) = monitor.position().into(); + let size: (u32, u32) = monitor.size().into(); + + unsafe { + let _ = SetWindowPos( + hwnd, + None, + position.0, + position.1, + size.0 as i32, + size.1 as i32, + SWP_ASYNCWINDOWPOS | SWP_NOZORDER, + ); + let _ = InvalidateRgn(hwnd, None, false); + } + } + None => { + let mut window_state_lock = window_state.lock(); + if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { + drop(window_state_lock); + unsafe { + let _ = SetWindowPlacement(hwnd, &placement); + let _ = InvalidateRgn(hwnd, None, false); + } + } + } + } + + unsafe { + taskbar_mark_fullscreen(hwnd, fullscreen.is_some()); + } + }); + } + + #[inline] + pub fn set_decorations(&self, decorations: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MARKER_DECORATIONS, decorations) + }); + }); + } + + #[inline] + pub fn set_always_on_bottom(&self, always_on_bottom: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::ALWAYS_ON_BOTTOM, always_on_bottom) + }); + }); + } + + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) + }); + }); + } + + pub fn set_rtl(&self, rtl: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, rtl) + }); + }); + } + + #[inline] + pub fn current_monitor(&self) -> Option { + Some(RootMonitorHandle { + inner: monitor::current_monitor(self.hwnd()), + }) + } + + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + if let Some(ref window_icon) = window_icon { + window_icon + .inner + .set_for_window(self.hwnd(), IconType::Small); + } else { + icon::unset_for_window(self.hwnd(), IconType::Small); + } + self.window_state.lock().window_icon = window_icon; + } + + #[inline] + pub fn set_taskbar_icon(&self, taskbar_icon: Option) { + if let Some(ref taskbar_icon) = taskbar_icon { + taskbar_icon + .inner + .set_for_window(self.hwnd(), IconType::Big); + } else { + icon::unset_for_window(self.hwnd(), IconType::Big); + } + self.window_state.lock().taskbar_icon = taskbar_icon; + } + + pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { + if unsafe { GetSystemMetrics(SM_IMMENABLED) } != 0 { + let composition_form = COMPOSITIONFORM { + dwStyle: CFS_POINT, + ptCurrentPos: POINT { x, y }, + rcArea: RECT::default(), + }; + unsafe { + let himc = ImmGetContext(self.hwnd()); + let _ = ImmSetCompositionWindow(himc, &composition_form); + let _ = ImmReleaseContext(self.hwnd(), himc); + } + } + } + + #[inline] + pub fn set_ime_position(&self, spot: Position) { + let (x, y) = spot.to_physical::(self.scale_factor()).into(); + self.set_ime_position_physical(x, y); + } + + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let window = self.window; + let active_window_handle = unsafe { GetActiveWindow() }; + if window.0 == active_window_handle { + // active window could be minimized, so we skip requesting attention + // if it is not minimized + let window_flags = self.window_state.lock().window_flags(); + let is_minimized = window_flags.contains(WindowFlags::MINIMIZED); + if !is_minimized { + return; + } + } + + let window_isize = window.0 .0 as isize; + + self.thread_executor.execute_in_thread(move || unsafe { + let (flags, count) = request_type + .map(|ty| match ty { + UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX), + UserAttentionType::Informational => (FLASHW_TRAY, 4), + }) + .unwrap_or((FLASHW_STOP, 0)); + + let flash_info = FLASHWINFO { + cbSize: mem::size_of::() as u32, + hwnd: HWND(window_isize as _), + dwFlags: flags, + uCount: count, + dwTimeout: 0, + }; + let _ = FlashWindowEx(&flash_info); + }); + } + + #[inline] + pub fn theme(&self) -> Theme { + self.window_state.lock().current_theme + } + + pub fn set_theme(&self, theme: Option) { + { + let mut window_state = self.window_state.lock(); + if window_state.preferred_theme == theme { + return; + } + window_state.preferred_theme = theme; + } + unsafe { SendMessageW(self.hwnd(), *CHANGE_THEME_MSG_ID, None, None) }; + } + + #[inline] + pub fn reset_dead_keys(&self) { + // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) + // key input which we can call `ToUnicode` with. + unsafe { + let vk = u32::from(VK_SPACE.0); + let scancode = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); + let kbd_state = [0; 256]; + let mut char_buff: [MaybeUninit; 8] = [MaybeUninit::uninit(); 8]; + ToUnicode( + vk, + scancode, + Some(&kbd_state), + mem::transmute::<&mut [std::mem::MaybeUninit], &mut [u16]>(char_buff.as_mut()), + 0, + ); + } + } + + #[inline] + pub fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32) { + unsafe { + let w_param = WPARAM(edge as _); + let l_param = util::MAKELPARAM(x as i16, y as i16); + + let _ = ReleaseCapture(); + let _ = PostMessageW(Some(self.hwnd()), button, w_param, l_param); + } + } + + #[inline] + pub(crate) fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { + self.window_state.lock().skip_taskbar = skip; + unsafe { set_skip_taskbar(self.hwnd(), skip) } + } + + #[inline] + pub fn set_background_color(&self, color: Option) { + self.window_state.lock().background_color = color; + + unsafe { + let _ = InvalidateRect(Some(self.hwnd()), None, true); + let _ = UpdateWindow(self.hwnd()); + } + } + + #[inline] + pub fn set_progress_bar(&self, progress: ProgressBarState) { + unsafe { + let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap(); + let handle = self.hwnd(); + + if let Some(state) = progress.state { + let taskbar_state = { + match state { + ProgressState::None => TBPF_NOPROGRESS, + ProgressState::Indeterminate => TBPF_INDETERMINATE, + ProgressState::Normal => TBPF_NORMAL, + ProgressState::Error => TBPF_ERROR, + ProgressState::Paused => TBPF_PAUSED, + } + }; + + taskbar_list + .SetProgressState(handle, taskbar_state) + .unwrap_or(()); + } + if let Some(value) = progress.progress { + let value = if value > 100 { 100 } else { value }; + + taskbar_list + .SetProgressValue(handle, value, 100) + .unwrap_or(()); + } + } + } + + #[inline] + pub fn set_overlay_icon(&self, icon: Option<&Icon>) { + let taskbar: ITaskbarList = + unsafe { CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap() }; + + let icon = icon.map(|i| i.inner.as_raw_handle()).unwrap_or_default(); + + unsafe { + taskbar + .SetOverlayIcon(self.hwnd(), icon, None) + .unwrap_or(()); + } + } + + #[inline] + pub fn set_undecorated_shadow(&self, shadow: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { + f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) + }); + }); + } + + #[inline] + pub fn has_undecorated_shadow(&self) -> bool { + self + .window_state + .lock() + .window_flags + .contains(WindowFlags::MARKER_UNDECORATED_SHADOW) + } + + pub fn set_content_protection(&self, enabled: bool) { + unsafe { + let _ = SetWindowDisplayAffinity( + self.hwnd(), + if enabled { + WDA_EXCLUDEFROMCAPTURE + } else { + WDA_NONE + }, + ); + } + } +} + +impl Drop for Window { + #[inline] + fn drop(&mut self) { + unsafe { + // The window must be destroyed from the same thread that created it, so we send a + // custom message to be handled by our callback to do the actual work. + let _ = PostMessageW(Some(self.hwnd()), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); + } + } +} + +pub(super) struct InitData<'a, T: 'static> { + // inputs + pub event_loop: &'a EventLoopWindowTarget, + pub attributes: WindowAttributes, + pub pl_attribs: PlatformSpecificWindowBuilderAttributes, + pub window_flags: WindowFlags, + // outputs + pub window: Option, +} + +impl InitData<'_, T> { + unsafe fn create_window(&self, window: HWND) -> Window { + // Register for touch events if applicable + { + let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; + if digitizer & NID_READY != 0 { + let _ = RegisterTouchWindow(window, TWF_WANTPALM); + } + } + + let dpi = hwnd_dpi(window); + let scale_factor = dpi_to_scale_factor(dpi); + + // If the system theme is dark, we need to set the window theme now + // before we update the window flags (and possibly show the + // window for the first time). + let current_theme = try_window_theme( + window, + self + .attributes + .preferred_theme + .or(*self.event_loop.preferred_theme.lock()), + false, + ); + + let window_state = { + let window_state = WindowState::new( + &self.attributes, + None, + scale_factor, + current_theme, + self.attributes.preferred_theme, + self.attributes.background_color, + ); + let window_state = Arc::new(Mutex::new(window_state)); + WindowState::set_window_flags(window_state.lock(), window, |f| *f = self.window_flags); + window_state + }; + + enable_non_client_dpi_scaling(window); + + Window { + window: WindowWrapper(window), + window_state, + thread_executor: self.event_loop.create_thread_executor(), + } + } + + unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData { + let file_drop_handler = if self.pl_attribs.drag_and_drop { + // It is ok if the initialize result is `S_FALSE` because it might happen that + // multiple windows are created on the same thread. + if let Err(error) = OleInitialize(None) { + match error.code() { + win32f::OLE_E_WRONGCOMPOBJ => { + panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") + } + win32f::RPC_E_CHANGED_MODE => panic!( + "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ + Make sure other crates are not using multithreaded COM library \ + on the same thread or disable drag and drop support." + ), + _ => (), + }; + } + + let file_drop_runner = self.event_loop.runner_shared.clone(); + let file_drop_handler: IDropTarget = FileDropHandler::new( + win.hwnd(), + Box::new(move |event| { + if let Ok(e) = event.map_nonuser_event() { + file_drop_runner.send_event(e) + } + }), + ) + .into(); + + assert!(RegisterDragDrop(win.hwnd(), &file_drop_handler).is_ok()); + Some(file_drop_handler) + } else { + None + }; + + self.event_loop.runner_shared.register_window(win.hwnd()); + + WindowData { + window_state: win.window_state.clone(), + event_loop_runner: self.event_loop.runner_shared.clone(), + key_event_builder: KeyEventBuilder::default(), + _file_drop_handler: file_drop_handler, + userdata_removed: Cell::new(false), + recurse_depth: Cell::new(0), + event_loop_preferred_theme: self.event_loop.preferred_theme.clone(), + } + } + + // Returns a pointer to window user data on success. + // The user data will be registered for the window and can be accessed within the window event + // callback. + pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option { + let runner = self.event_loop.runner_shared.clone(); + let (win, userdata) = runner.catch_unwind(|| { + let window = unsafe { self.create_window(window) }; + let window_data = unsafe { self.create_window_data(&window) }; + (window, window_data) + })?; + + self.window = Some(win); + let userdata = Box::into_raw(Box::new(userdata)); + Some(userdata as _) + } + + pub unsafe fn on_create(&mut self) { + let win = self.window.as_mut().expect("failed window creation"); + + // making the window transparent + if self.attributes.transparent && !self.pl_attribs.no_redirection_bitmap { + // Empty region for the blur effect, so the window is fully transparent + let region = CreateRectRgn(0, 0, -1, -1); + + let bb = DWM_BLURBEHIND { + dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, + fEnable: true.into(), + hRgnBlur: region, + fTransitionOnMaximized: false.into(), + }; + + let _ = DwmEnableBlurBehindWindow(win.hwnd(), &bb); + let _ = DeleteObject(region.into()); + } + + if self.pl_attribs.skip_taskbar { + let _ = win.set_skip_taskbar(true); + } + win.set_window_icon(self.attributes.window_icon.clone()); + win.set_taskbar_icon(self.pl_attribs.taskbar_icon.clone()); + + if self.attributes.content_protection { + win.set_content_protection(true); + } + + win.set_visible(self.attributes.visible); + win.set_closable(self.attributes.closable); + } +} + +unsafe fn init( + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + event_loop: &EventLoopWindowTarget, +) -> Result { + // registering the window class + let class_name = register_window_class::(&pl_attribs.window_classname); + + let mut window_flags = WindowFlags::empty(); + window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); + window_flags.set( + WindowFlags::MARKER_UNDECORATED_SHADOW, + pl_attribs.decoration_shadow, + ); + window_flags.set(WindowFlags::ALWAYS_ON_BOTTOM, attributes.always_on_bottom); + window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top); + window_flags.set( + WindowFlags::NO_BACK_BUFFER, + pl_attribs.no_redirection_bitmap, + ); + window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent); + // WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured. + window_flags.set(WindowFlags::RESIZABLE, attributes.resizable); + window_flags.set(WindowFlags::MINIMIZABLE, attributes.minimizable); + window_flags.set(WindowFlags::MAXIMIZABLE, attributes.maximizable); + // will be changed later using `window.set_closable` + // but we need to have a default for the diffing to work + window_flags.set(WindowFlags::CLOSABLE, true); + + window_flags.set(WindowFlags::FOCUSABLE, attributes.focusable); + + window_flags.set(WindowFlags::MARKER_DONT_FOCUS, !attributes.focused); + + window_flags.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, pl_attribs.rtl); + + let parent = match pl_attribs.parent { + Parent::ChildOf(parent) => { + window_flags.set(WindowFlags::CHILD, true); + if pl_attribs.menu.is_some() { + warn!("Setting a menu on a child window is unsupported"); + } + Some(parent) + } + Parent::OwnedBy(parent) => { + window_flags.set(WindowFlags::POPUP, true); + Some(parent) + } + Parent::None => { + window_flags.set(WindowFlags::ON_TASKBAR, true); + None + } + }; + + let (style, ex_style) = window_flags.to_window_styles(); + let title = util::encode_wide(&attributes.title); + + let (target_monitor, position) = attributes + .position + .and_then(|p| { + monitor::available_monitors() + .into_iter() + .find_map(|monitor| { + let dpi = monitor.dpi(); + let scale_factor = dpi_to_scale_factor(dpi); + let position = p.to_physical::(scale_factor); + let (x, y): (i32, i32) = monitor.position().into(); + let (width, height): (i32, i32) = monitor.size().into(); + + let frame_thickness = if window_flags.contains_shadow() { + util::get_frame_thickness(dpi) + } else { + 0 + }; + + // Only the starting position x needs to be accounted + if x <= position.x + frame_thickness + && position.x <= x + width + && y <= position.y + && position.y <= y + height + { + Some((monitor, position.into())) + } else { + None + } + }) + }) + .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); + + let desired_size = attributes + .inner_size + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); + let clamped_size = attributes + .inner_size_constraints + .clamp(desired_size, target_monitor.scale_factor()); + + // Best effort: try to create the window with the requested inner size + let adjusted_size = { + let (mut w, mut h): (i32, i32) = clamped_size + .to_physical::(target_monitor.scale_factor()) + .into(); + + if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { + let mut rect = RECT { + left: 0, + top: 0, + right: w, + bottom: h, + }; + + unsafe { + AdjustWindowRectEx( + &mut rect, + window_flags.to_adjusted_window_styles().0, + pl_attribs.menu.is_some(), + ex_style, + )?; + } + + w = rect.right - rect.left; + h = rect.bottom - rect.top; + } else if window_flags.undecorated_with_shadows() { + let dpi = target_monitor.dpi(); + let insets = calculate_insets_for_dpi(dpi); + w += insets.left + insets.right; + h += insets.top + insets.bottom; + } + + (w, h) + }; + + let fullscreen = attributes.fullscreen.clone(); + let maximized = attributes.maximized; + let focused = attributes.focused; + let menu = pl_attribs.menu; + + let mut initdata = InitData { + event_loop, + attributes, + pl_attribs, + window_flags, + window: None, + }; + + let result = CreateWindowExW( + ex_style, + PCWSTR::from_raw(class_name.as_ptr()), + PCWSTR::from_raw(title.as_ptr()), + style, + position.0, + position.1, + adjusted_size.0, + adjusted_size.1, + parent, + menu, + Some(util::get_instance_handle()), + Some(&mut initdata as *mut _ as *mut _), + ); + + // If the window creation in `InitData` panicked, then should resume panicking here + if let Err(panic_error) = event_loop.runner_shared.take_panic_error() { + panic::resume_unwind(panic_error) + } + + let handle = result?; + + super::dark_mode::allow_dark_mode_for_window(handle, true); + + // If the handle is non-null, then window creation must have succeeded, which means + // that we *must* have populated the `InitData.window` field. + let window = initdata.window.unwrap(); + + // Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx + // This is because if the size is changed in WM_CREATE, the restored size will be stored in that + // size. + if fullscreen.is_some() { + window.set_fullscreen(fullscreen); + force_window_active(window.hwnd()); + } else if maximized { + window.set_maximized(true); + } + + if !focused { + window + .window_state + .lock() + .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_DONT_FOCUS)); + } + + Ok(window) +} + +unsafe fn register_window_class(window_classname: &str) -> Vec { + let class_name = util::encode_wide(window_classname); + + let class = WNDCLASSEXW { + cbSize: mem::size_of::() as u32, + style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + lpfnWndProc: Some(super::event_loop::public_window_callback::), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: util::get_instance_handle(), + hIcon: HICON::default(), + hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly + hbrBackground: HBRUSH::default(), + lpszMenuName: PCWSTR::null(), + lpszClassName: PCWSTR::from_raw(class_name.as_ptr()), + hIconSm: HICON::default(), + }; + + // We ignore errors because registering the same window class twice would trigger + // an error, and because errors here are detected during CreateWindowEx anyway. + // Also since there is no weird element in the struct, there is no reason for this + // call to fail. + RegisterClassExW(&class); + + class_name +} + +struct ComInitialized(Option<()>); +impl Drop for ComInitialized { + fn drop(&mut self) { + if let Some(()) = self.0.take() { + unsafe { CoUninitialize() }; + } + } +} + +thread_local! { + static COM_INITIALIZED: ComInitialized = { + unsafe { + ComInitialized(match CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok() { + Ok(()) => Some(()), + Err(_) => None, + }) + } + }; + + static TASKBAR_LIST: RefCell> = const { RefCell::new(None) }; +} + +pub fn com_initialized() { + COM_INITIALIZED.with(|_| {}); +} + +// Reference Implementation: +// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc +// +// As per MSDN marking the window as fullscreen should ensure that the +// taskbar is moved to the bottom of the Z-order when the fullscreen window +// is activated. If the window is not fullscreen, the Shell falls back to +// heuristics to determine how the window should be treated, which means +// that it could still consider the window as fullscreen. :( +unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { + com_initialized(); + + TASKBAR_LIST.with(|task_bar_list_ptr| { + let mut task_bar_list = task_bar_list_ptr.borrow().clone(); + + if task_bar_list.is_none() { + let result: windows::core::Result = + CoCreateInstance(&TaskbarList, None, CLSCTX_ALL); + if let Ok(created) = result { + if let Ok(()) = created.HrInit() { + task_bar_list = Some(created); + } + } + + if task_bar_list.is_none() { + return; + } + + *task_bar_list_ptr.borrow_mut() = task_bar_list.clone(); + } + + let _ = task_bar_list + .unwrap() + .MarkFullscreenWindow(handle, fullscreen); + }) +} + +unsafe fn force_window_active(handle: HWND) { + // Try to focus the window without the hack first. + if SetForegroundWindow(handle).as_bool() { + return; + } + + // In some situations, calling SetForegroundWindow could not bring up the window, + // This is a little hack which can "steal" the foreground window permission. + // We only call this function in the window creation, so it should be fine. + // See: https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open + let alt_sc = MapVirtualKeyW(u32::from(VK_MENU.0), MAPVK_VK_TO_VSC); + + let mut inputs: [INPUT; 2] = mem::zeroed(); + inputs[0].r#type = INPUT_KEYBOARD; + inputs[0].Anonymous.ki.wVk = VK_LMENU as _; + inputs[0].Anonymous.ki.wScan = alt_sc as _; + inputs[0].Anonymous.ki.dwFlags = KEYEVENTF_EXTENDEDKEY; + + inputs[1].r#type = INPUT_KEYBOARD; + inputs[1].Anonymous.ki.wVk = VK_LMENU as _; + inputs[1].Anonymous.ki.wScan = alt_sc as _; + inputs[1].Anonymous.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP; + + // Simulate a key press and release + SendInput(&inputs, mem::size_of::() as _); + + let _ = SetForegroundWindow(handle); +} + +pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) -> Result<(), ExternalError> { + com_initialized(); + let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER)?; + if skip { + taskbar_list.DeleteTab(hwnd)?; + } else { + taskbar_list.AddTab(hwnd)?; + } + + Ok(()) +} + +impl ResizeDirection { + pub(crate) fn to_win32(&self) -> u32 { + match self { + ResizeDirection::East => HTRIGHT, + ResizeDirection::North => HTTOP, + ResizeDirection::NorthEast => HTTOPRIGHT, + ResizeDirection::NorthWest => HTTOPLEFT, + ResizeDirection::South => HTBOTTOM, + ResizeDirection::SouthEast => HTBOTTOMRIGHT, + ResizeDirection::SouthWest => HTBOTTOMLEFT, + ResizeDirection::West => HTLEFT, + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs new file mode 100644 index 000000000..4595b14f9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs @@ -0,0 +1,518 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + dpi::PhysicalPosition, + icon::Icon, + keyboard::ModifiersState, + platform_impl::platform::{event_loop, util}, + window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints, RGBA}, +}; +use parking_lot::MutexGuard; +use std::io; +use windows::Win32::{ + Foundation::{HWND, LPARAM, RECT, WPARAM}, + Graphics::Gdi::InvalidateRgn, + UI::WindowsAndMessaging::*, +}; + +/// Contains information about states and the window that the callback is going to use. +pub struct WindowState { + pub mouse: MouseProperties, + + /// Used by `WM_GETMINMAXINFO`. + pub size_constraints: WindowSizeConstraints, + + pub window_icon: Option, + pub taskbar_icon: Option, + + pub saved_window: Option, + pub scale_factor: f64, + + pub dragging: bool, + + pub skip_taskbar: bool, + + pub modifiers_state: ModifiersState, + pub fullscreen: Option, + pub current_theme: Theme, + pub preferred_theme: Option, + + pub window_flags: WindowFlags, + + // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS + pub is_active: bool, + pub is_focused: bool, + + pub background_color: Option, +} + +unsafe impl Send for WindowState {} +unsafe impl Sync for WindowState {} + +#[derive(Clone)] +pub struct SavedWindow { + pub placement: WINDOWPLACEMENT, +} + +#[derive(Clone)] +pub struct MouseProperties { + pub cursor: CursorIcon, + pub capture_count: u32, + cursor_flags: CursorFlags, + pub last_position: Option>, +} + +bitflags! { + #[derive(Clone, Copy)] + pub struct CursorFlags: u8 { + const GRABBED = 1 << 0; + const HIDDEN = 1 << 1; + const IN_WINDOW = 1 << 2; + } +} +bitflags! { + #[derive(Clone, Copy, PartialEq)] + pub struct WindowFlags: u32 { + const RESIZABLE = 1 << 0; + const VISIBLE = 1 << 1; + const ON_TASKBAR = 1 << 2; + const ALWAYS_ON_TOP = 1 << 3; + const NO_BACK_BUFFER = 1 << 4; + const TRANSPARENT = 1 << 5; + const CHILD = 1 << 6; + const MAXIMIZED = 1 << 7; + const POPUP = 1 << 8; + const ALWAYS_ON_BOTTOM = 1 << 9; + const MINIMIZABLE = 1 << 10; + const MAXIMIZABLE = 1 << 11; + const CLOSABLE = 1 << 12; + const MINIMIZED = 1 << 13; + + const IGNORE_CURSOR_EVENT = 1 << 14; + + /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is + /// included here to make masking easier. + const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 15; + const MARKER_BORDERLESS_FULLSCREEN = 1 << 16; + + /// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`. + /// In most cases, it's okay to let those parameters change the state. However, when we're + /// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to + /// effect our stored state, because the purpose of `apply_diff` is to update the actual + /// window's state to match our stored state. This controls whether to accept those changes. + const MARKER_RETAIN_STATE_ON_SIZE = 1 << 17; + + const MARKER_IN_SIZE_MOVE = 1 << 18; + + const MARKER_DONT_FOCUS = 1 << 19; + + /// Fully decorated window (incl. caption, border and drop shadow). + const MARKER_DECORATIONS = 1 << 20; + /// Drop shadow for undecorated windows. + const MARKER_UNDECORATED_SHADOW = 1 << 21; + + const RIGHT_TO_LEFT_LAYOUT = 1 << 22; + + const FOCUSABLE = 1 << 23; + + const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); + } +} + +impl WindowState { + pub fn new( + attributes: &WindowAttributes, + taskbar_icon: Option, + scale_factor: f64, + current_theme: Theme, + preferred_theme: Option, + background_color: Option, + ) -> WindowState { + WindowState { + mouse: MouseProperties { + cursor: CursorIcon::default(), + capture_count: 0, + cursor_flags: CursorFlags::empty(), + last_position: None, + }, + + size_constraints: attributes.inner_size_constraints, + + window_icon: attributes.window_icon.clone(), + taskbar_icon, + + saved_window: None, + scale_factor, + + dragging: false, + + skip_taskbar: false, + + modifiers_state: ModifiersState::default(), + fullscreen: None, + current_theme, + preferred_theme, + window_flags: WindowFlags::empty(), + is_active: false, + is_focused: false, + + background_color, + } + } + + pub fn window_flags(&self) -> WindowFlags { + self.window_flags + } + + pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) + where + F: FnOnce(&mut WindowFlags), + { + let old_flags = this.window_flags; + f(&mut this.window_flags); + let new_flags = this.window_flags; + + drop(this); + old_flags.apply_diff(window, new_flags); + } + + pub fn set_window_flags_in_place(&mut self, f: F) + where + F: FnOnce(&mut WindowFlags), + { + f(&mut self.window_flags); + } + + pub fn has_active_focus(&self) -> bool { + self.is_active && self.is_focused + } + + // Updates is_active and returns whether active-focus state has changed + pub fn set_active(&mut self, is_active: bool) -> bool { + let old = self.has_active_focus(); + self.is_active = is_active; + old != self.has_active_focus() + } + + // Updates is_focused and returns whether active-focus state has changed + pub fn set_focused(&mut self, is_focused: bool) -> bool { + let old = self.has_active_focus(); + self.is_focused = is_focused; + old != self.has_active_focus() + } +} + +impl MouseProperties { + pub fn cursor_flags(&self) -> CursorFlags { + self.cursor_flags + } + + pub fn set_cursor_flags(&mut self, window: HWND, f: F) -> Result<(), io::Error> + where + F: FnOnce(&mut CursorFlags), + { + let old_flags = self.cursor_flags; + f(&mut self.cursor_flags); + match self.cursor_flags.refresh_os_cursor(window) { + Ok(()) => (), + Err(e) => { + self.cursor_flags = old_flags; + return Err(e); + } + } + + Ok(()) + } +} + +impl WindowFlags { + fn mask(mut self) -> WindowFlags { + if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { + self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; + } + + self + } + + pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { + let (mut style, mut style_ex) = (Default::default(), Default::default()); + style |= WS_CAPTION | WS_CLIPSIBLINGS | WS_SYSMENU; + style_ex |= WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + if self.contains(WindowFlags::RESIZABLE) { + style |= WS_SIZEBOX; + } + if self.contains(WindowFlags::MAXIMIZABLE) { + style |= WS_MAXIMIZEBOX; + } + if self.contains(WindowFlags::MINIMIZABLE) { + style |= WS_MINIMIZEBOX; + } + if self.contains(WindowFlags::VISIBLE) { + style |= WS_VISIBLE; + } + if self.contains(WindowFlags::ON_TASKBAR) { + style_ex |= WS_EX_APPWINDOW; + } + if self.contains(WindowFlags::ALWAYS_ON_TOP) { + style_ex |= WS_EX_TOPMOST; + } + if self.contains(WindowFlags::NO_BACK_BUFFER) { + style_ex |= WS_EX_NOREDIRECTIONBITMAP; + } + if self.contains(WindowFlags::CHILD) { + style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. + + // Remove decorations window styles for child + if !self.contains(WindowFlags::MARKER_DECORATIONS) { + style &= !WS_CAPTION; + style_ex &= !WS_EX_WINDOWEDGE; + } + } + if self.contains(WindowFlags::POPUP) { + style |= WS_POPUP; + } + if self.contains(WindowFlags::MINIMIZED) { + style |= WS_MINIMIZE; + } + if self.contains(WindowFlags::MAXIMIZED) { + style |= WS_MAXIMIZE; + } + if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) { + style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; + } + if self.intersects( + WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, + ) { + style &= !WS_OVERLAPPEDWINDOW; + } + if self.contains(WindowFlags::RIGHT_TO_LEFT_LAYOUT) { + style_ex |= WS_EX_LAYOUTRTL | WS_EX_RTLREADING | WS_EX_RIGHT; + } + if !self.contains(WindowFlags::FOCUSABLE) { + style_ex |= WS_EX_NOACTIVATE; + } + + (style, style_ex) + } + + /// Returns the appropriate window styles for `AdjustWindowRectEx` + pub fn to_adjusted_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { + let (mut style, style_ex) = self.to_window_styles(); + + if !self.contains(WindowFlags::MARKER_DECORATIONS) { + style &= !(WS_CAPTION | WS_THICKFRAME) + } + + (style, style_ex) + } + + /// Adjust the window client rectangle to the return value, if present. + fn apply_diff(self, window: HWND, new: WindowFlags) { + let old = self.mask(); + let new = new.mask(); + + let mut diff = old ^ new; + + if diff == WindowFlags::empty() { + return; + } + + if diff.contains(WindowFlags::ALWAYS_ON_TOP) { + unsafe { + let _ = SetWindowPos( + window, + Some(if new.contains(WindowFlags::ALWAYS_ON_TOP) { + HWND_TOPMOST + } else { + HWND_NOTOPMOST + }), + 0, + 0, + 0, + 0, + SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, + ); + let _ = InvalidateRgn(window, None, false); + } + } + + if diff.contains(WindowFlags::ALWAYS_ON_BOTTOM) { + unsafe { + let _ = SetWindowPos( + window, + Some(if new.contains(WindowFlags::ALWAYS_ON_BOTTOM) { + HWND_BOTTOM + } else { + HWND_NOTOPMOST + }), + 0, + 0, + 0, + 0, + SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, + ); + let _ = InvalidateRgn(window, None, false); + } + } + + if (diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED)) + // This is to avoid the window from flashing + && !(new.contains(WindowFlags::MAXIMIZED) && !new.contains(WindowFlags::VISIBLE)) + { + unsafe { + let _ = ShowWindow( + window, + match new.contains(WindowFlags::MAXIMIZED) { + true => SW_MAXIMIZE, + false => SW_RESTORE, + }, + ); + } + } + + // Minimize operations should execute after maximize for proper window animations + if diff.contains(WindowFlags::MINIMIZED) { + unsafe { + let _ = ShowWindow( + window, + match new.contains(WindowFlags::MINIMIZED) { + true => SW_MINIMIZE, + false => SW_RESTORE, + }, + ); + } + + diff.remove(WindowFlags::MINIMIZED); + } + + if diff.contains(WindowFlags::CLOSABLE) || new.contains(WindowFlags::CLOSABLE) { + unsafe { + let system_menu = GetSystemMenu(window, false); + let _ = EnableMenuItem( + system_menu, + SC_CLOSE, + MF_BYCOMMAND + | if new.contains(WindowFlags::CLOSABLE) { + MF_ENABLED + } else { + MF_GRAYED + }, + ); + } + } + + if !new.contains(WindowFlags::VISIBLE) { + unsafe { + let _ = ShowWindow(window, SW_HIDE); + } + } + + if diff != WindowFlags::empty() { + let (mut style, style_ex) = new.to_window_styles(); + // Remove `WS_VISIBLE` if we were hidden, this is required for the `ShowWindow` below to work + if !old.contains(WindowFlags::VISIBLE) { + style &= !WS_VISIBLE; + } + + unsafe { + SendMessageW( + window, + *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, + Some(WPARAM(1)), + Some(LPARAM(0)), + ); + + // This condition is necessary to avoid having an unrestorable window + if !new.contains(WindowFlags::MINIMIZED) { + SetWindowLongW(window, GWL_STYLE, style.0 as i32); + SetWindowLongW(window, GWL_EXSTYLE, style_ex.0 as i32); + } + + let mut flags = SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED; + + // We generally don't want style changes here to affect window + // focus, but for fullscreen windows they must be activated + // (i.e. focused) so that they appear on top of the taskbar + if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) + && !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) + { + flags |= SWP_NOACTIVATE; + } + + // Refresh the window frame + let _ = SetWindowPos(window, None, 0, 0, 0, 0, flags); + SendMessageW( + window, + *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, + Some(WPARAM(0)), + Some(LPARAM(0)), + ); + } + } + + // This needs to be after the `SetWindowPos` above or there will be + // a title bar flicker on undecorated windows's creation + if new.contains(WindowFlags::VISIBLE) { + unsafe { + let _ = ShowWindow( + window, + if old.contains(WindowFlags::MARKER_DONT_FOCUS) { + SW_SHOWNOACTIVATE + } else { + SW_SHOW + }, + ); + } + } + } + + pub fn undecorated_with_shadows(&self) -> bool { + self.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) + && !self.contains(WindowFlags::MARKER_DECORATIONS) + } + + pub fn contains_shadow(&self) -> bool { + self.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) + || self.contains(WindowFlags::MARKER_DECORATIONS) + } +} + +impl CursorFlags { + fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { + let client_rect = util::get_client_rect(window)?; + + if util::is_focused(window) { + let cursor_clip = match self.contains(CursorFlags::GRABBED) { + true => Some(client_rect), + false => None, + }; + + let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom); + let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?); + let desktop_rect = rect_to_tuple(util::get_desktop_rect()); + + let active_cursor_clip = match desktop_rect == active_cursor_clip { + true => None, + false => Some(active_cursor_clip), + }; + + // We do this check because calling `set_cursor_clip` incessantly will flood the event + // loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags` + // which at times gets called once every iteration of the eventloop. + if active_cursor_clip != cursor_clip.map(rect_to_tuple) { + util::set_cursor_clip(cursor_clip)?; + } + } + + let cursor_in_client = self.contains(CursorFlags::IN_WINDOW); + if cursor_in_client { + util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN)); + } else { + util::set_cursor_hidden(false); + } + + Ok(()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs new file mode 100644 index 000000000..e43c72eff --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs @@ -0,0 +1,1699 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +//! The `Window` struct and associated types. +use std::fmt; + +use crate::{ + dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Pixel, PixelUnit, Position, Size}, + error::{ExternalError, NotSupportedError, OsError}, + event_loop::EventLoopWindowTarget, + monitor::{MonitorHandle, VideoMode}, + platform_impl, +}; + +pub use crate::icon::{BadIcon, Icon}; + +/// Progress State +#[derive(Debug, Clone, Copy)] +pub enum ProgressState { + None, + Normal, + /// **Treated as Normal in linux and macOS** + Indeterminate, + /// **Treated as Normal in linux** + Paused, + /// **Treated as Normal in linux** + Error, +} + +pub struct ProgressBarState { + /// The progress bar state. + pub state: Option, + /// The progress bar progress. This can be a value ranging from `0` to `100` + pub progress: Option, + /// The `.desktop` filename with the Unity desktop window manager, for example `myapp.desktop` **Linux Only** + pub desktop_filename: Option, +} + +/// Represents a window. +/// +/// # Example +/// +/// ```no_run +/// use tao::{ +/// event::{Event, WindowEvent}, +/// event_loop::{ControlFlow, EventLoop}, +/// window::Window, +/// }; +/// +/// let mut event_loop = EventLoop::new(); +/// let window = Window::new(&event_loop).unwrap(); +/// +/// event_loop.run(move |event, _, control_flow| { +/// *control_flow = ControlFlow::Wait; +/// +/// match event { +/// Event::WindowEvent { +/// event: WindowEvent::CloseRequested, +/// .. +/// } => *control_flow = ControlFlow::Exit, +/// _ => (), +/// } +/// }); +/// ``` +pub struct Window { + pub(crate) window: platform_impl::Window, +} + +impl fmt::Debug for Window { + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + fmtr.pad("Window { .. }") + } +} + +impl Drop for Window { + fn drop(&mut self) { + // If the window is in exclusive fullscreen, we must restore the desktop + // video mode (generally this would be done on application exit, but + // closing the window doesn't necessarily always mean application exit, + // such as when there are multiple windows) + if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() { + self.set_fullscreen(None); + } + } +} + +/// Type alias for a color in the RGBA format. +/// +/// Each value can be 0..255 inclusive. +pub type RGBA = (u8, u8, u8, u8); + +/// Identifier of a window. Unique for each window. +/// +/// Can be obtained with `window.id()`. +/// +/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you +/// can then compare to the ids of your windows. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WindowId(pub(crate) platform_impl::WindowId); + +impl WindowId { + /// # Safety + /// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `WindowId`. + /// + /// **Passing this into a tao function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + WindowId(platform_impl::WindowId::dummy()) + } +} + +/// Object that allows you to build windows. +#[derive(Clone, Default)] +pub struct WindowBuilder { + /// The attributes to use to create the window. + pub window: WindowAttributes, + + // Platform-specific configuration. + pub(crate) platform_specific: platform_impl::PlatformSpecificWindowBuilderAttributes, +} + +impl fmt::Debug for WindowBuilder { + fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { + fmtr + .debug_struct("WindowBuilder") + .field("window", &self.window) + .finish() + } +} + +/// Attributes to use when creating a window. +#[derive(Debug, Clone)] +pub struct WindowAttributes { + /// The dimensions of the window. If this is `None`, some platform-specific dimensions will be + /// used. + /// + /// The default is `None`. + pub inner_size: Option, + + /// The window size constraints + pub inner_size_constraints: WindowSizeConstraints, + + /// The desired position of the window. If this is `None`, some platform-specific position + /// will be chosen. + /// + /// The default is `None`. + /// + /// ## Platform-specific + /// + /// - **macOS**: The top left corner position of the window content, the window's "inner" + /// position. The window title bar will be placed above it. + /// The window will be positioned such that it fits on screen, maintaining + /// set `inner_size` if any. + /// If you need to precisely position the top left corner of the whole window you have to + /// use [`Window::set_outer_position`] after creating the window. + /// - **Windows**: The top left corner position of the window title bar, the window's "outer" + /// position. + /// There may be a small gap between this position and the window due to the specifics of the + /// Window Manager. + /// - **Linux**: The top left corner of the window, the window's "outer" position. + /// - **Linux(Wayland)**: Unsupported. + /// - **Others**: Ignored. + /// + /// See [`Window::set_outer_position`]. + /// + /// [`Window::set_outer_position`]: crate::window::Window::set_outer_position + pub position: Option, + + /// Whether the window is resizable or not. + /// + /// The default is `true`. + pub resizable: bool, + + /// Whether the window is minimizable or not. + /// + /// The default is `true`. + /// + /// See [`Window::set_minimizable`] for details. + pub minimizable: bool, + + /// Whether the window is maximizable or not. + /// + /// The default is `true`. + /// + /// See [`Window::set_maximizable`] for details. + pub maximizable: bool, + + /// Whether the window is closable or not. + /// + /// The default is `true`. + /// + /// See [`Window::set_closable`] for details. + pub closable: bool, + + /// Whether the window should be set as fullscreen upon creation. + /// + /// The default is `None`. + pub fullscreen: Option, + + /// The title of the window in the title bar. + /// + /// The default is `"tao window"`. + pub title: String, + + /// Whether the window should be maximized upon creation. + /// + /// The default is `false`. + pub maximized: bool, + + /// Whether the window should be immediately visible upon creation. + /// + /// The default is `true`. + pub visible: bool, + + /// Whether the the window should be transparent. If this is true, writing colors + /// with alpha values different than `1.0` will produce a transparent window. + /// + /// The default is `false`. + pub transparent: bool, + + /// Whether the window should have borders and bars. + /// + /// The default is `true`. + pub decorations: bool, + + /// Whether the window should always be on top of other windows. + /// + /// The default is `false`. + /// + /// ## Platform-specific: + /// + /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. + /// - **Linux(Wayland):** Unsupported. + // TODO: Unsupported in gtk4 + pub always_on_top: bool, + + /// Whether the window should always be on bottom of other windows. + /// + /// The default is `false`. + /// + /// ## Platform-specific: + /// + /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. + /// - **Linux(Wayland):** Unsupported. + // TODO: Unsupported in gtk4 + pub always_on_bottom: bool, + + /// The window icon. + /// + /// The default is `None`. + pub window_icon: Option, + + pub preferred_theme: Option, + + /// Whether the window should be initially focused or not. + /// + /// ## Platform-specific: + /// + /// **Android / iOS:** Unsupported. + pub focused: bool, + + /// Whether the window should be focusable or not. + /// + /// ## Platform-specific: + /// + /// **Android / iOS:** Unsupported. + pub focusable: bool, + + /// Prevents the window contents from being captured by other apps. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux:** Unsupported. + pub content_protection: bool, + + /// Sets whether the window should be visible on all workspaces. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Windows:** Unsupported. + pub visible_on_all_workspaces: bool, + + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + pub background_color: Option, +} + +impl Default for WindowAttributes { + #[inline] + fn default() -> WindowAttributes { + WindowAttributes { + inner_size: None, + inner_size_constraints: Default::default(), + position: None, + resizable: true, + minimizable: true, + maximizable: true, + closable: true, + title: "tao window".to_owned(), + maximized: false, + fullscreen: None, + visible: true, + transparent: false, + decorations: true, + always_on_top: false, + always_on_bottom: false, + window_icon: None, + preferred_theme: None, + focused: true, + focusable: true, + content_protection: false, + visible_on_all_workspaces: false, + background_color: None, + } + } +} + +impl WindowBuilder { + /// Initializes a new `WindowBuilder` with default values. + #[inline] + pub fn new() -> Self { + Default::default() + } + + /// Requests the window to be of specific dimensions. + /// + /// See [`Window::set_inner_size`] for details. + /// + /// [`Window::set_inner_size`]: crate::window::Window::set_inner_size + #[inline] + pub fn with_inner_size>(mut self, size: S) -> Self { + self.window.inner_size = Some(size.into()); + self + } + + /// Sets a minimum dimension size for the window. + /// + /// See [`Window::set_min_inner_size`] for details. + /// + /// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size + #[inline] + pub fn with_min_inner_size>(mut self, min_size: S) -> Self { + let size: Size = min_size.into(); + let (width, height) = crate::extract_width_height(size); + self.window.inner_size_constraints.min_width = Some(width); + self.window.inner_size_constraints.min_height = Some(height); + self + } + + /// Sets a maximum dimension size for the window. + /// + /// See [`Window::set_max_inner_size`] for details. + /// + /// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size + #[inline] + pub fn with_max_inner_size>(mut self, max_size: S) -> Self { + let size: Size = max_size.into(); + let (width, height) = crate::extract_width_height(size); + self.window.inner_size_constraints.max_width = Some(width); + self.window.inner_size_constraints.max_height = Some(height); + self + } + + /// Sets inner size constraints for the window. + /// + /// See [`Window::set_inner_size_constraints`] for details. + /// + /// [`Window::set_inner_size_constraints`]: crate::window::Window::set_inner_size_constraints + #[inline] + pub fn with_inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self { + self.window.inner_size_constraints = constraints; + self + } + + /// Sets a desired initial position for the window. + /// + /// See [`WindowAttributes::position`] for details. + /// + /// [`WindowAttributes::position`]: crate::window::WindowAttributes::position + #[inline] + pub fn with_position>(mut self, position: P) -> Self { + self.window.position = Some(position.into()); + self + } + + /// Sets whether the window is resizable or not. + /// + /// See [`Window::set_resizable`] for details. + /// + /// [`Window::set_resizable`]: crate::window::Window::set_resizable + #[inline] + pub fn with_resizable(mut self, resizable: bool) -> Self { + self.window.resizable = resizable; + self + } + + /// Sets whether the window is minimizable or not. + /// + /// See [`Window::set_minimizable`] for details. + /// + /// [`Window::set_minimizable`]: crate::window::Window::set_minimizable + #[inline] + pub fn with_minimizable(mut self, minimizable: bool) -> Self { + self.window.minimizable = minimizable; + self + } + + /// Sets whether the window is maximizable or not. + /// + /// See [`Window::set_maximizable`] for details. + /// + /// [`Window::set_maximizable`]: crate::window::Window::set_maximizable + #[inline] + pub fn with_maximizable(mut self, maximizable: bool) -> Self { + self.window.maximizable = maximizable; + self + } + + /// Sets whether the window is closable or not. + /// + /// See [`Window::set_closable`] for details. + /// + /// [`Window::set_closable`]: crate::window::Window::set_closable + #[inline] + pub fn with_closable(mut self, closable: bool) -> Self { + self.window.closable = closable; + self + } + + /// Requests a specific title for the window. + /// + /// See [`Window::set_title`] for details. + /// + /// [`Window::set_title`]: crate::window::Window::set_title + #[inline] + pub fn with_title>(mut self, title: T) -> Self { + self.window.title = title.into(); + self + } + + /// Sets the window fullscreen state. + /// + /// See [`Window::set_fullscreen`] for details. + /// + /// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen + #[inline] + pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { + self.window.fullscreen = fullscreen; + self + } + + /// Requests maximized mode. + /// + /// See [`Window::set_maximized`] for details. + /// + /// [`Window::set_maximized`]: crate::window::Window::set_maximized + #[inline] + pub fn with_maximized(mut self, maximized: bool) -> Self { + self.window.maximized = maximized; + self + } + + /// Sets whether the window will be initially hidden or visible. + /// + /// See [`Window::set_visible`] for details. + /// + /// [`Window::set_visible`]: crate::window::Window::set_visible + #[inline] + pub fn with_visible(mut self, visible: bool) -> Self { + self.window.visible = visible; + self + } + + /// Sets whether the background of the window should be transparent. + #[inline] + pub fn with_transparent(mut self, transparent: bool) -> Self { + self.window.transparent = transparent; + self + } + + /// Sets whether the window should have a border, a title bar, etc. + /// + /// See [`Window::set_decorations`] for details. + /// + /// [`Window::set_decorations`]: crate::window::Window::set_decorations + #[inline] + pub fn with_decorations(mut self, decorations: bool) -> Self { + self.window.decorations = decorations; + self + } + + /// Sets whether or not the window will always be below other windows. + /// + /// See [`Window::set_always_on_bottom`] for details. + /// + /// [`Window::set_always_on_bottom`]: crate::window::Window::set_always_on_bottom + #[inline] + pub fn with_always_on_bottom(mut self, always_on_bottom: bool) -> Self { + self.window.always_on_top = false; + self.window.always_on_bottom = always_on_bottom; + self + } + + /// Sets whether or not the window will always be on top of other windows. + /// + /// See [`Window::set_always_on_top`] for details. + /// + /// [`Window::set_always_on_top`]: crate::window::Window::set_always_on_top + #[inline] + pub fn with_always_on_top(mut self, always_on_top: bool) -> Self { + self.window.always_on_bottom = false; + self.window.always_on_top = always_on_top; + self + } + + /// Sets the window icon. + /// + /// See [`Window::set_window_icon`] for details. + /// + /// [`Window::set_window_icon`]: crate::window::Window::set_window_icon + #[inline] + pub fn with_window_icon(mut self, window_icon: Option) -> Self { + self.window.window_icon = window_icon; + self + } + + /// Forces a theme or uses the system settings if `None` was provided. + /// + /// ## Platform-specific: + /// + /// - **Windows**: It is recommended to always use the same theme used + /// in [`EventLoopBuilderExtWindows::with_theme`] for this method also + /// or use `None` so it automatically uses the theme used in [`EventLoopBuilderExtWindows::with_theme`] + /// or falls back to the system preference, because [`EventLoopBuilderExtWindows::with_theme`] changes + /// the theme for some controls like context menus which is app-wide and can't be changed by this method. + /// + /// [`EventLoopBuilderExtWindows::with_theme`]: crate::platform::windows::EventLoopBuilderExtWindows::with_theme + #[allow(rustdoc::broken_intra_doc_links)] + #[inline] + pub fn with_theme(mut self, theme: Option) -> WindowBuilder { + self.window.preferred_theme = theme; + self + } + + /// Whether the window will be initially focused or not. + /// + /// ## Platform-specific: + /// + /// **Android / iOS:** Unsupported. + #[inline] + pub fn with_focused(mut self, focused: bool) -> WindowBuilder { + self.window.focused = focused; + self + } + + /// Whether the window will be focusable or not. + /// + /// ## Platform-specific: + /// **Android / iOS:** Unsupported. + #[inline] + pub fn with_focusable(mut self, focusable: bool) -> WindowBuilder { + self.window.focusable = focusable; + self + } + + /// Prevents the window contents from being captured by other apps. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux:** Unsupported. + #[inline] + pub fn with_content_protection(mut self, protected: bool) -> WindowBuilder { + self.window.content_protection = protected; + self + } + + /// Sets whether the window should be visible on all workspaces. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Windows:** Unsupported. + #[inline] + pub fn with_visible_on_all_workspaces(mut self, visible: bool) -> WindowBuilder { + self.window.visible_on_all_workspaces = visible; + self + } + + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn with_background_color(mut self, color: RGBA) -> WindowBuilder { + self.window.background_color = Some(color); + self + } + + /// Builds the window. + /// + /// Possible causes of error include denied permission, incompatible system, and lack of memory. + #[inline] + pub fn build( + self, + window_target: &EventLoopWindowTarget, + ) -> Result { + platform_impl::Window::new(&window_target.p, self.window, self.platform_specific).map( + |window| { + window.request_redraw(); + Window { window } + }, + ) + } +} + +/// Base Window functions. +impl Window { + /// Creates a new Window for platforms where this is appropriate. + /// + /// This function is equivalent to [`WindowBuilder::new().build(event_loop)`]. + /// + /// Error should be very rare and only occur in case of permission denied, incompatible system, + /// out of memory, etc. + /// + /// [`WindowBuilder::new().build(event_loop)`]: crate::window::WindowBuilder::build + #[inline] + pub fn new(event_loop: &EventLoopWindowTarget) -> Result { + let builder = WindowBuilder::new(); + builder.build(event_loop) + } + + /// Returns an identifier unique to the window. + #[inline] + pub fn id(&self) -> WindowId { + WindowId(self.window.id()) + } + + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. + /// + /// See the [`dpi`](crate::dpi) module for more information. + /// + /// Note that this value can change depending on user action (for example if the window is + /// moved to another screen); as such, tracking `WindowEvent::ScaleFactorChanged` events is + /// the most robust way to track the DPI you need to use to draw. + /// + /// ## Platform-specific + /// + /// - **Android:** Always returns 1.0. + /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s + /// [`contentScaleFactor`]. + /// + /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc + #[inline] + pub fn scale_factor(&self) -> f64 { + self.window.scale_factor() + } + + /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS + /// events have been processed by the event loop. + /// + /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with + /// OS-requested redraws (e.g. when a window gets resized). + /// + /// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared` + /// but before `Event::NewEvents` if called in the following circumstances: + /// * While processing `MainEventsCleared`. + /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any + /// directly subsequent `RedrawRequested` event. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Unsupported. + #[inline] + pub fn request_redraw(&self) { + self.window.request_redraw() + } +} + +/// Position and size functions. +impl Window { + /// Returns the position of the top-left hand corner of the window's client area relative to the + /// top-left hand corner of the desktop. + /// + /// The same conditions that apply to `outer_position` apply to this method. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window's [safe area] in the screen space coordinate system. + /// - **Android:** Always returns [`NotSupportedError`]. + /// + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[inline] + pub fn inner_position(&self) -> Result, NotSupportedError> { + self.window.inner_position() + } + + /// Returns the position of the top-left hand corner of the window relative to the + /// top-left hand corner of the desktop. + /// + /// Note that the top-left hand corner of the desktop is not necessarily the same as + /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner + /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. + /// + /// The coordinates can be negative if the top-left hand corner of the window is outside + /// of the visible screen region. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the + /// window in the screen space coordinate system. + /// - **Android:** Always returns [`NotSupportedError`]. + /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global coordinate system + #[inline] + pub fn outer_position(&self) -> Result, NotSupportedError> { + self.window.outer_position() + } + + /// Modifies the position of the window. + /// + /// See `outer_position` for more information about the coordinates. This automatically un-maximizes the + /// window if it's maximized. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the + /// window in the screen space coordinate system. + /// - **Android / Linux(Wayland):** Unsupported. + #[inline] + pub fn set_outer_position>(&self, position: P) { + self.window.set_outer_position(position.into()) + } + + /// Returns the physical size of the window's client area. + /// + /// The client area is the content of the window, excluding the title bar and borders. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's + /// [safe area] in screen space coordinates. + /// + /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[inline] + pub fn inner_size(&self) -> PhysicalSize { + self.window.inner_size() + } + + /// Modifies the inner size of the window. + /// + /// See `inner_size` for more information about the values. This automatically un-maximizes the + /// window if it's maximized. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_inner_size>(&self, size: S) { + self.window.set_inner_size(size.into()) + } + + /// Returns the physical size of the entire window. + /// + /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), + /// use `inner_size` instead. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in + /// screen space coordinates. + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + self.window.outer_size() + } + + /// Sets a minimum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_min_inner_size>(&self, min_size: Option) { + self.window.set_min_inner_size(min_size.map(|s| s.into())) + } + + /// Sets a maximum dimension size for the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_max_inner_size>(&self, max_size: Option) { + self.window.set_max_inner_size(max_size.map(|s| s.into())) + } + + /// Sets inner size constraints for the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { + self.window.set_inner_size_constraints(constraints) + } +} + +/// Misc. attribute functions. +impl Window { + /// Modifies the title of the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_title(&self, title: &str) { + self.window.set_title(title) + } + + /// Gets the current title of the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. Returns an empty string. + #[inline] + pub fn title(&self) -> String { + self.window.title() + } + + /// Modifies the window's visibility. + /// + /// If `false`, this will hide the window. If `true`, this will show the window. + /// ## Platform-specific + /// + /// - **Android:** Unsupported. + /// - **iOS:** Can only be called on the main thread. + #[inline] + pub fn set_visible(&self, visible: bool) { + self.window.set_visible(visible) + } + + /// Bring the window to front and focus. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_focus(&self) { + self.window.set_focus() + } + + /// Sets whether the window is focusable or not. + /// + /// ## Platform-specific + /// + /// - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`. + /// In this case, you might consider calling [`Window::set_focus`] but it will move the window to the back i.e. at the bottom in terms of z-order. + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_focusable(&self, focusable: bool) { + self.window.set_focusable(focusable) + } + + /// Is window active and focused? + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_focused(&self) -> bool { + self.window.is_focused() + } + + /// Indicates whether the window is always on top of other windows. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_always_on_top(&self) -> bool { + self.window.is_always_on_top() + } + + /// Sets whether the window is resizable or not. + /// + /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be + /// triggered by DPI scaling, entering fullscreen mode, etc. + /// + /// ## Platform-specific + /// + /// This only has an effect on desktop platforms. + /// + /// Due to a bug in XFCE, this has no effect on Xfwm. + /// + /// ## Platform-specific + /// + /// - **Linux:** Most size methods like maximized are async and do not work well with calling + /// sequentailly. For setting inner or outer size, you don't need to set resizable to true before + /// it. It can resize no matter what. But if you insist to do so, it has a `100, 100` minimum + /// limitation somehow. For maximizing, it requires resizable is true. If you really want to set + /// resizable to false after it. You might need a mechanism to check the window is really + /// maximized. + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_resizable(&self, resizable: bool) { + self.window.set_resizable(resizable) + } + + /// Sets whether the window is minimizable or not. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[inline] + pub fn set_minimizable(&self, minimizable: bool) { + self.window.set_minimizable(minimizable) + } + + /// Sets whether the window is maximizable or not. + /// + /// ## Platform-specific + /// + /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. + /// - **Linux / iOS / Android:** Unsupported. + #[inline] + pub fn set_maximizable(&self, maximizable: bool) { + self.window.set_maximizable(maximizable) + } + + /// Sets whether the window is closable or not. + /// + /// ## Platform-specific + /// + /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. + /// Depending on the system, this function may not have any effect when called on a window that is already visible" + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_closable(&self, closable: bool) { + self.window.set_closable(closable) + } + + /// Sets the window to minimized or back + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_minimized(&self, minimized: bool) { + self.window.set_minimized(minimized); + } + + /// Sets the window to maximized or back. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_maximized(&self, maximized: bool) { + self.window.set_maximized(maximized) + } + + /// Gets the window's current maximized state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_maximized(&self) -> bool { + self.window.is_maximized() + } + + /// Gets the window's current minimized state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_minimized(&self) -> bool { + self.window.is_minimized() + } + + /// Gets the window's current visibility state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_visible(&self) -> bool { + self.window.is_visible() + } + + /// Gets the window's current resizable state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_resizable(&self) -> bool { + self.window.is_resizable() + } + + /// Gets the window's current minimizable state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[inline] + pub fn is_minimizable(&self) -> bool { + self.window.is_minimizable() + } + + /// Gets the window's current maximizable state. + /// + /// ## Platform-specific + /// + /// - **Linux / iOS / Android:** Unsupported. + #[inline] + pub fn is_maximizable(&self) -> bool { + self.window.is_maximizable() + } + + /// Gets the window's current closable state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn is_closable(&self) -> bool { + self.window.is_closable() + } + + /// Gets the window's current decoration state. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + pub fn is_decorated(&self) -> bool { + self.window.is_decorated() + } + + /// Sets the window to fullscreen or back. + /// + /// ## Platform-specific + /// + /// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a + /// video mode change. *Caveat!* macOS doesn't provide task switching (or + /// spaces!) while in exclusive fullscreen mode. This mode should be used + /// when a video mode change is desired, but for a better user experience, + /// borderless fullscreen might be preferred. + /// + /// `Fullscreen::Borderless` provides a borderless fullscreen window on a + /// separate space. This is the idiomatic way for fullscreen games to work + /// on macOS. See `WindowExtMacOs::set_simple_fullscreen` if + /// separate spaces are not preferred. + /// + /// The dock and the menu bar are always disabled in fullscreen mode. + /// - **iOS:** Can only be called on the main thread. + /// - **Windows:** Screen saver is disabled in fullscreen mode. + /// - **Linux:** The window will only fullscreen to current monitor no matter which enum variant. + /// - **Android:** Unsupported. + #[inline] + pub fn set_fullscreen(&self, fullscreen: Option) { + self.window.set_fullscreen(fullscreen) + } + + /// Gets the window's current fullscreen state. + /// + /// ## Platform-specific + /// + /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Will always return `None`. + #[inline] + pub fn fullscreen(&self) -> Option { + self.window.fullscreen() + } + + /// Turn window decorations on or off. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + /// + /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc + #[inline] + pub fn set_decorations(&self, decorations: bool) { + self.window.set_decorations(decorations) + } + + /// Change whether or not the window will always be below other windows. + /// + /// ## Platform-specific + /// + /// - **Windows**: There is no guarantee that the window will be the bottom most but it will try to be. + /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. + /// - **Linux(Wayland) / iOS / Android:** Unsupported. + // TODO: Unsupported in gtk4 + #[inline] + pub fn set_always_on_bottom(&self, always_on_bottom: bool) { + self.window.set_always_on_bottom(always_on_bottom) + } + + /// Change whether or not the window will always be on top of other windows. + /// + /// ## Platform-specific + /// + /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. + /// - **Linux(Wayland) / iOS / Android:** Unsupported. + // TODO: Unsupported in gtk4 + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + self.window.set_always_on_top(always_on_top) + } + + /// Sets the window icon. On Windows and Linux, this is typically the small icon in the top-left + /// corner of the title bar. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / macOS:** Unsupported. + /// + /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's + /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. + #[inline] + pub fn set_window_icon(&self, window_icon: Option) { + self.window.set_window_icon(window_icon) + } + + /// Sets location of IME candidate box in client area coordinates relative to the top left. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_ime_position>(&self, position: P) { + self.window.set_ime_position(position.into()) + } + + /// Sets the taskbar progress state. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS**: Unlike windows, progress bar is app-wide and not specific to this window. Only supported desktop environments with `libunity` (e.g. GNOME). + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_progress_bar(&self, _progress: ProgressBarState) { + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "macos", + ))] + self.window.set_progress_bar(_progress) + } + + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see `UserAttentionType` for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **Linux:** Urgency levels have the same effect. + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + self.window.request_user_attention(request_type) + } + + /// Returns the current window theme. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn theme(&self) -> Theme { + self.window.theme() + } + + /// Sets the theme for this window. + /// + /// ## Platform-specific + /// + /// - **Linux / macOS**: Theme is app-wide and not specific to this window. + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_theme(&self, #[allow(unused)] theme: Option) { + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "macos", + ))] + self.window.set_theme(theme) + } + + /// Prevents the window contents from being captured by other apps. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux:** Unsupported. + pub fn set_content_protection(&self, #[allow(unused)] enabled: bool) { + #[cfg(any(target_os = "macos", target_os = "windows"))] + self.window.set_content_protection(enabled); + } + + /// Sets whether the window should be visible on all workspaces. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Windows:** Unsupported. + pub fn set_visible_on_all_workspaces(&self, #[allow(unused)] visible: bool) { + #[cfg(any(target_os = "macos", target_os = "linux"))] + self.window.set_visible_on_all_workspaces(visible) + } + + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_background_color(&self, color: Option) { + self.window.set_background_color(color) + } +} + +/// Cursor functions. +impl Window { + /// Modifies the cursor icon of the window. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + self.window.set_cursor_icon(cursor); + } + + /// Changes the position of the cursor in window coordinates. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + #[inline] + pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { + self.window.set_cursor_position(position.into()) + } + + /// Grabs the cursor, preventing it from leaving the window. + /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + /// + /// ## Platform-specific + /// + /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + self.window.set_cursor_grab(grab) + } + + /// Modifies the cursor's visibility. + /// + /// If `false`, this will hide the cursor. If `true`, this will show the cursor. + /// + /// ## Platform-specific + /// + /// - **Windows:** The cursor is only hidden within the confines of the window. + /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is + /// outside of the window. + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + self.window.set_cursor_visible(visible) + } + + /// Moves the window with the left mouse button until the button is released. + /// + /// There's no guarantee that this will work unless the left mouse button was pressed + /// immediately before this function is called. + /// + /// ## Platform-specific + /// + /// - **macOS:** May prevent the button release event to be triggered. + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + #[inline] + pub fn drag_window(&self) -> Result<(), ExternalError> { + self.window.drag_window() + } + + /// Resizes the window with the left mouse button until the button is released. + /// + /// There's no guarantee that this will work unless the left mouse button was pressed + /// immediately before this function is called. + /// + /// ## Platform-specific + /// + /// - **macOS / iOS / Android:** Always returns an [`ExternalError::NotSupported`]. + #[inline] + pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { + self.window.drag_resize_window(direction) + } + + /// Modifies whether the window catches cursor events. + /// + /// If `true`, the events are passed through the window such that any other window behind it receives them. + /// If `false` the window will catch the cursor events. By default cursor events are not ignored. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`] + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + self.window.set_ignore_cursor_events(ignore) + } + + /// Returns the current cursor position + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`. + #[inline] + pub fn cursor_position(&self) -> Result, ExternalError> { + self.window.cursor_position() + } +} + +/// Monitor info functions. +impl Window { + /// Returns the monitor on which the window currently resides. + /// + /// Returns `None` if current monitor can't be detected. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn current_monitor(&self) -> Option { + self.window.current_monitor() + } + + #[inline] + /// Returns the monitor that contains the given point. + /// + /// ## Platform-specific: + /// + /// - **Android / iOS:** Unsupported. + pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { + self.window.monitor_from_point(x, y) + } + + /// Returns the list of all the monitors available on the system. + /// + /// This is the same as `EventLoopWindowTarget::available_monitors`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn available_monitors(&self) -> impl Iterator { + self + .window + .available_monitors() + .into_iter() + .map(|inner| MonitorHandle { inner }) + } + + /// Returns the primary monitor of the system. + /// + /// Returns `None` if it can't identify any monitor as a primary one. + /// + /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. + /// + /// ## Platform-specific + /// + /// **iOS:** Can only be called on the main thread. + #[inline] + pub fn primary_monitor(&self) -> Option { + self.window.primary_monitor() + } +} + +#[cfg(feature = "rwh_04")] +unsafe impl rwh_04::HasRawWindowHandle for Window { + fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { + self.window.raw_window_handle_rwh_04() + } +} + +#[cfg(feature = "rwh_05")] +unsafe impl rwh_05::HasRawWindowHandle for Window { + fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { + self.window.raw_window_handle_rwh_05() + } +} + +#[cfg(feature = "rwh_05")] +unsafe impl rwh_05::HasRawDisplayHandle for Window { + fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { + self.window.raw_display_handle_rwh_05() + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasWindowHandle for Window { + fn window_handle(&self) -> Result, rwh_06::HandleError> { + let raw = self.window.raw_window_handle_rwh_06()?; + // SAFETY: The window handle will never be deallocated while the window is alive. + Ok(unsafe { rwh_06::WindowHandle::borrow_raw(raw) }) + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasDisplayHandle for Window { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + let raw = self.window.raw_display_handle_rwh_06()?; + // SAFETY: The window handle will never be deallocated while the window is alive. + Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) + } +} + +/// Describes the appearance of the mouse cursor. +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Default)] +pub enum CursorIcon { + /// The platform-dependent default cursor. + #[default] + Default, + /// A simple crosshair. + Crosshair, + /// A hand (often used to indicate links in web browsers). + Hand, + /// Self explanatory. + Arrow, + /// Indicates something is to be moved. + Move, + /// Indicates text that may be selected or edited. + Text, + /// Program busy indicator. + Wait, + /// Help indicator (often rendered as a "?") + Help, + /// Progress indicator. Shows that processing is being done. But in contrast + /// with "Wait" the user may still interact with the program. Often rendered + /// as a spinning beach ball, or an arrow with a watch or hourglass. + Progress, + + /// Cursor showing that something cannot be done. + NotAllowed, + ContextMenu, + Cell, + VerticalText, + Alias, + Copy, + NoDrop, + /// Indicates something can be grabbed. + Grab, + /// Indicates something is grabbed. + Grabbing, + AllScroll, + ZoomIn, + ZoomOut, + + /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor + /// is used when the movement starts from the south-east corner of the box. + EResize, + NResize, + NeResize, + NwResize, + SResize, + SeResize, + SwResize, + WResize, + EwResize, + NsResize, + NeswResize, + NwseResize, + ColResize, + RowResize, +} + +/// Fullscreen modes. +#[non_exhaustive] +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, PartialEq)] +pub enum Fullscreen { + Exclusive(VideoMode), + + /// Providing `None` to `Borderless` will fullscreen on the current monitor. + Borderless(Option), +} + +#[non_exhaustive] +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub enum Theme { + #[default] + Light, + Dark, +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum UserAttentionType { + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + #[default] + Informational, +} + +/// Window size constraints +#[derive(Clone, Copy, PartialEq, Debug, Default)] +pub struct WindowSizeConstraints { + /// The minimum width a window can be, If this is `None`, the window will have no minimum width (aside from reserved). + /// + /// The default is `None`. + pub min_width: Option, + /// The minimum height a window can be, If this is `None`, the window will have no minimum height (aside from reserved). + /// + /// The default is `None`. + pub min_height: Option, + /// The maximum width a window can be, If this is `None`, the window will have no maximum width (aside from reserved). + /// + /// The default is `None`. + pub max_width: Option, + /// The maximum height a window can be, If this is `None`, the window will have no maximum height (aside from reserved). + /// + /// The default is `None`. + pub max_height: Option, +} + +impl WindowSizeConstraints { + pub fn new( + min_width: Option, + min_height: Option, + max_width: Option, + max_height: Option, + ) -> Self { + Self { + min_width, + min_height, + max_width, + max_height, + } + } + + /// Returns true if `min_width` or `min_height` is set. + pub fn has_min(&self) -> bool { + self.min_width.is_some() || self.min_height.is_some() + } + /// Returns true if `max_width` or `max_height` is set. + pub fn has_max(&self) -> bool { + self.max_width.is_some() || self.max_height.is_some() + } + + /// Returns a physical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values + pub fn min_size_physical(&self, scale_factor: f64) -> PhysicalSize { + PhysicalSize::new( + self + .min_width + .unwrap_or(PixelUnit::MIN) + .to_physical(scale_factor) + .0, + self + .min_height + .unwrap_or(PixelUnit::MIN) + .to_physical(scale_factor) + .0, + ) + } + + /// Returns a logical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values + pub fn min_size_logical(&self, scale_factor: f64) -> LogicalSize { + LogicalSize::new( + self + .min_width + .unwrap_or(PixelUnit::MIN) + .to_logical(scale_factor) + .0, + self + .min_height + .unwrap_or(PixelUnit::MIN) + .to_logical(scale_factor) + .0, + ) + } + + /// Returns a physical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values + pub fn max_size_physical(&self, scale_factor: f64) -> PhysicalSize { + PhysicalSize::new( + self + .max_width + .unwrap_or(PixelUnit::MAX) + .to_physical(scale_factor) + .0, + self + .max_height + .unwrap_or(PixelUnit::MAX) + .to_physical(scale_factor) + .0, + ) + } + + /// Returns a logical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values + pub fn max_size_logical(&self, scale_factor: f64) -> LogicalSize { + LogicalSize::new( + self + .max_width + .unwrap_or(PixelUnit::MAX) + .to_logical(scale_factor) + .0, + self + .max_height + .unwrap_or(PixelUnit::MAX) + .to_logical(scale_factor) + .0, + ) + } + + /// Clamps the desired size based on the constraints set + pub fn clamp(&self, desired_size: Size, scale_factor: f64) -> Size { + let min_size: PhysicalSize = self.min_size_physical(scale_factor); + let max_size: PhysicalSize = self.max_size_physical(scale_factor); + Size::clamp(desired_size, min_size.into(), max_size.into(), scale_factor) + } +} + +/// Defines the orientation that a window resize will be performed. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ResizeDirection { + East, + North, + NorthEast, + NorthWest, + South, + SouthEast, + SouthWest, + West, +} + +#[cfg(any(target_os = "windows", target_os = "linux"))] +pub(crate) fn hit_test( + (left, top, right, bottom): (i32, i32, i32, i32), + cx: i32, + cy: i32, + border_x: i32, + border_y: i32, +) -> Option { + const LEFT: isize = 0b0001; + const RIGHT: isize = 0b0010; + const TOP: isize = 0b0100; + const BOTTOM: isize = 0b1000; + const TOPLEFT: isize = TOP | LEFT; + const TOPRIGHT: isize = TOP | RIGHT; + const BOTTOMLEFT: isize = BOTTOM | LEFT; + const BOTTOMRIGHT: isize = BOTTOM | RIGHT; + + #[rustfmt::skip] + let result = (LEFT * (cx < left + border_x) as isize) + | (RIGHT * (cx >= right - border_x) as isize) + | (TOP * (cy < top + border_y) as isize) + | (BOTTOM * (cy >= bottom - border_y) as isize); + + match result { + LEFT => Some(ResizeDirection::West), + RIGHT => Some(ResizeDirection::East), + TOP => Some(ResizeDirection::North), + BOTTOM => Some(ResizeDirection::South), + TOPLEFT => Some(ResizeDirection::NorthWest), + TOPRIGHT => Some(ResizeDirection::NorthEast), + BOTTOMLEFT => Some(ResizeDirection::SouthWest), + BOTTOMRIGHT => Some(ResizeDirection::SouthEast), + _ => None, + } +} From 640932ea0a7cadcf2606223acfd1de08a5884b57 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:48:12 +0000 Subject: [PATCH 02/14] research: upgrade to Tauri 2.12 Tauri 2.12 pulls in tao 0.37, the first release with working single-window UIScene support, which iOS 27 SDK builds require (UIKit aborts at launch otherwise). It also carries the Xcode 27 fixes in tauri-cli and in the Swift plugin entry points of log and opener. - tauri 2.12.4, tauri-build/macros/codegen 2.7.3, tauri-runtime-wry 2.12.2, wry 0.57.0 - plugins: log 2.10.1, updater 2.14.0, opener 2.7.2, os 2.4.0, fs 2.7.1, dialog 2.8.3; deep-link and single-instance stay on their pins - @tauri-apps/cli 2.12.3, @tauri-apps/api 2.12.4, JS plugins to match - tao is patched from the vendored 0.37.1 copy instead of 0.35.2 - Android: Gradle wrapper 8.11.1 -> 8.14.3 (with its checksum); Tauri 2.12's Android library and plugins use the compilerOptions DSL, which needs Gradle 8.13+. AGP, Kotlin, SDK, NDK and JDK stay as they are. All versions are at least seven days old (bunfig minimumReleaseAge). Co-Authored-By: Claude Opus 5.5 --- apps/maple-research/frontend/bun.lock | 44 +- apps/maple-research/frontend/package.json | 12 +- .../frontend/src-tauri/Cargo.lock | 600 ++-- .../frontend/src-tauri/Cargo.toml | 24 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../src-tauri/patches/tao-0.35.2/Cargo.toml | 406 --- .../patches/tao-0.35.2/Cargo.toml.orig | 172 -- .../src-tauri/patches/tao-0.35.2/LICENSE | 201 -- .../src-tauri/patches/tao-0.35.2/LICENSE.spdx | 18 - .../src-tauri/patches/tao-0.35.2/README.md | 80 - .../tao-0.35.2/examples/control_flow.rs | 120 - .../patches/tao-0.35.2/examples/cursor.rs | 89 - .../tao-0.35.2/examples/cursor_grab.rs | 66 - .../tao-0.35.2/examples/custom_events.rs | 51 - .../tao-0.35.2/examples/decorations.rs | 51 - .../tao-0.35.2/examples/drag_window.rs | 78 - .../patches/tao-0.35.2/examples/fullscreen.rs | 129 - .../tao-0.35.2/examples/handling_close.rs | 87 - .../tao-0.35.2/examples/min_max_size.rs | 87 - .../patches/tao-0.35.2/examples/minimize.rs | 47 - .../tao-0.35.2/examples/monitor_list.rs | 14 - .../tao-0.35.2/examples/mouse_wheel.rs | 54 - .../tao-0.35.2/examples/multithreaded.rs | 187 -- .../tao-0.35.2/examples/multiwindow.rs | 55 - .../patches/tao-0.35.2/examples/overlay.rs | 128 - .../tao-0.35.2/examples/parentwindow.rs | 70 - .../tao-0.35.2/examples/progress_bar.rs | 89 - .../tao-0.35.2/examples/reopen_event.rs | 45 - .../tao-0.35.2/examples/request_redraw.rs | 43 - .../examples/request_redraw_threaded.rs | 45 - .../patches/tao-0.35.2/examples/resizable.rs | 51 - .../tao-0.35.2/examples/set_ime_position.rs | 56 - .../patches/tao-0.35.2/examples/theme.rs | 50 - .../patches/tao-0.35.2/examples/timer.rs | 43 - .../tao-0.35.2/examples/transparent.rs | 66 - .../tao-0.35.2/examples/video_modes.rs | 24 - .../patches/tao-0.35.2/examples/window.rs | 52 - .../tao-0.35.2/examples/window_debug.rs | 176 -- .../tao-0.35.2/examples/window_icon.rs | 64 - .../tao-0.35.2/examples/window_run_return.rs | 59 - .../src-tauri/patches/tao-0.35.2/src/error.rs | 88 - .../src-tauri/patches/tao-0.35.2/src/event.rs | 964 ------ .../patches/tao-0.35.2/src/event_loop.rs | 416 --- .../src-tauri/patches/tao-0.35.2/src/icon.rs | 169 -- .../patches/tao-0.35.2/src/keyboard.rs | 1596 ---------- .../src-tauri/patches/tao-0.35.2/src/lib.rs | 196 -- .../patches/tao-0.35.2/src/monitor.rs | 163 - .../tao-0.35.2/src/platform/android.rs | 72 - .../patches/tao-0.35.2/src/platform/ios.rs | 378 --- .../patches/tao-0.35.2/src/platform/linux.rs | 5 - .../patches/tao-0.35.2/src/platform/macos.rs | 458 --- .../patches/tao-0.35.2/src/platform/mod.rs | 28 - .../tao-0.35.2/src/platform/run_return.rs | 50 - .../patches/tao-0.35.2/src/platform/unix.rs | 308 -- .../tao-0.35.2/src/platform/windows.rs | 449 --- .../src/platform_impl/android/mod.rs | 1490 ---------- .../src/platform_impl/android/ndk_glue.rs | 786 ----- .../src/platform_impl/ios/app_state.rs | 1092 ------- .../tao-0.35.2/src/platform_impl/ios/badge.rs | 15 - .../src/platform_impl/ios/event_loop.rs | 385 --- .../tao-0.35.2/src/platform_impl/ios/ffi.rs | 433 --- .../src/platform_impl/ios/keycode.rs | 13 - .../tao-0.35.2/src/platform_impl/ios/mod.rs | 124 - .../src/platform_impl/ios/monitor.rs | 300 -- .../tao-0.35.2/src/platform_impl/ios/scene.rs | 185 -- .../tao-0.35.2/src/platform_impl/ios/view.rs | 787 ----- .../src/platform_impl/ios/window.rs | 877 ------ .../src/platform_impl/linux/device.rs | 77 - .../src/platform_impl/linux/event_loop.rs | 1312 -------- .../src/platform_impl/linux/icon.rs | 47 - .../src/platform_impl/linux/keyboard.rs | 287 -- .../src/platform_impl/linux/keycode.rs | 325 -- .../tao-0.35.2/src/platform_impl/linux/mod.rs | 94 - .../src/platform_impl/linux/monitor.rs | 96 - .../src/platform_impl/linux/portal.rs | 78 - .../src/platform_impl/linux/taskbar.rs | 161 - .../src/platform_impl/linux/util.rs | 111 - .../src/platform_impl/linux/wayland/header.rs | 36 - .../src/platform_impl/linux/wayland/mod.rs | 5 - .../src/platform_impl/linux/window.rs | 1082 ------- .../src/platform_impl/linux/x11/ffi.rs | 8 - .../src/platform_impl/linux/x11/mod.rs | 8 - .../src/platform_impl/linux/x11/xdisplay.rs | 169 -- .../tao-0.35.2/src/platform_impl/macos/app.rs | 125 - .../src/platform_impl/macos/app_delegate.rs | 222 -- .../src/platform_impl/macos/app_state.rs | 469 --- .../src/platform_impl/macos/badge.rs | 13 - .../src/platform_impl/macos/dock.rs | 91 - .../src/platform_impl/macos/event.rs | 334 --- .../src/platform_impl/macos/event_loop.rs | 356 --- .../tao-0.35.2/src/platform_impl/macos/ffi.rs | 273 -- .../src/platform_impl/macos/icon.rs | 14 - .../src/platform_impl/macos/keycode.rs | 264 -- .../tao-0.35.2/src/platform_impl/macos/mod.rs | 97 - .../src/platform_impl/macos/monitor.rs | 338 --- .../src/platform_impl/macos/observer.rs | 305 -- .../src/platform_impl/macos/progress_bar.rs | 161 - .../src/platform_impl/macos/util/async.rs | 257 -- .../src/platform_impl/macos/util/cursor.rs | 177 -- .../src/platform_impl/macos/util/mod.rs | 145 - .../src/platform_impl/macos/view.rs | 1182 -------- .../src/platform_impl/macos/window.rs | 1793 ----------- .../platform_impl/macos/window_delegate.rs | 678 ----- .../tao-0.35.2/src/platform_impl/mod.rs | 40 - .../src/platform_impl/windows/dark_mode.rs | 270 -- .../src/platform_impl/windows/dpi.rs | 109 - .../src/platform_impl/windows/drop_handler.rs | 181 -- .../src/platform_impl/windows/event.rs | 421 --- .../src/platform_impl/windows/event_loop.rs | 2637 ----------------- .../windows/event_loop/runner.rs | 450 --- .../src/platform_impl/windows/icon.rs | 150 - .../src/platform_impl/windows/keyboard.rs | 816 ----- .../platform_impl/windows/keyboard_layout.rs | 979 ------ .../src/platform_impl/windows/keycode.rs | 338 --- .../src/platform_impl/windows/minimal_ime.rs | 88 - .../src/platform_impl/windows/mod.rs | 157 - .../src/platform_impl/windows/monitor.rs | 280 -- .../src/platform_impl/windows/raw_input.rs | 217 -- .../src/platform_impl/windows/util.rs | 501 ---- .../src/platform_impl/windows/window.rs | 1554 ---------- .../src/platform_impl/windows/window_state.rs | 513 ---- .../patches/tao-0.35.2/src/window.rs | 1698 ----------- .../.tauri/tauri-api/.gitignore | 10 + .../.tauri/tauri-api/Package.swift | 40 + .../.tauri/tauri-api/README.md | 3 + .../tauri-api/Sources/Tauri/Channel.swift | 65 + .../tauri-api/Sources/Tauri/Invoke.swift | 118 + .../tauri-api/Sources/Tauri/JSTypes.swift | 123 + .../tauri-api/Sources/Tauri/JsonValue.swift | 58 + .../tauri-api/Sources/Tauri/Logger.swift | 139 + .../Sources/Tauri/Plugin/Plugin.swift | 82 + .../tauri-api/Sources/Tauri/Tauri.swift | 157 + .../tauri-api/Sources/Tauri/UiUtils.swift | 15 + .../ios/Package.resolved | 16 + 134 files changed, 1097 insertions(+), 37881 deletions(-) delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs delete mode 100644 apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Plugin/Plugin.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved diff --git a/apps/maple-research/frontend/bun.lock b/apps/maple-research/frontend/bun.lock index 5dd9297b2..f99757ebd 100644 --- a/apps/maple-research/frontend/bun.lock +++ b/apps/maple-research/frontend/bun.lock @@ -21,11 +21,11 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-dialog": "^2.8.3", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -49,7 +49,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", @@ -76,7 +76,7 @@ "overrides": { "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "browserslist": "^4.28.8", "flatted": "^3.4.2", "glob": "^10.5.0", @@ -447,39 +447,39 @@ "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], - "@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="], + "@tauri-apps/api": ["@tauri-apps/api@2.12.4", "", {}, "sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.11.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.1", "@tauri-apps/cli-darwin-x64": "2.11.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1", "@tauri-apps/cli-linux-arm64-gnu": "2.11.1", "@tauri-apps/cli-linux-arm64-musl": "2.11.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-musl": "2.11.1", "@tauri-apps/cli-win32-arm64-msvc": "2.11.1", "@tauri-apps/cli-win32-ia32-msvc": "2.11.1", "@tauri-apps/cli-win32-x64-msvc": "2.11.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ=="], + "@tauri-apps/cli": ["@tauri-apps/cli@2.12.3", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.12.3", "@tauri-apps/cli-darwin-x64": "2.12.3", "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.3", "@tauri-apps/cli-linux-arm64-gnu": "2.12.3", "@tauri-apps/cli-linux-arm64-musl": "2.12.3", "@tauri-apps/cli-linux-riscv64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-musl": "2.12.3", "@tauri-apps/cli-win32-arm64-msvc": "2.12.3", "@tauri-apps/cli-win32-ia32-msvc": "2.12.3", "@tauri-apps/cli-win32-x64-msvc": "2.12.3" }, "bin": { "tauri": "tauri.js" } }, "sha512-f7/LsY74SVb1ChmOPw1ajOpxii0GSzDAgmWHRci8JUYuuai04rXsSbFobpmB0sP2DdlvMD/B5ZotEn8GOGMRUg=="], - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg=="], + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.12.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xmVNoJOOKzKLj7GLHXmXLimRby9M42aBIJa0sB80N7o24eKV5JiVmToKTRLVH9yhL05GZ/B6rSSOmbzNFJwBAA=="], - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig=="], + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.12.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-URd+0usfmUQQyb5kNPiKt2OT8gp7oGU7QR+qOppB6e35mpVBFFVNhWc9oWb3p0u71fDJ09DpF4T6m9xYxoIB8g=="], - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q=="], + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.12.3", "", { "os": "linux", "cpu": "arm" }, "sha512-0kwUVx2LbmOoTRlwF1oDww7th4/NSluv4ZBQ6KRZsFKg4g0TlTcIiJ0VehqkQYUeC9xIT1Mk6XtF52mI155VSQ=="], - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA=="], + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-gKSGDx6fx+HBLQmG6guChFdisbrCSdx9Yli+j5teOa64gm1CeM+C6NLru5Pj3ZkkEep05BmCFjxvX+cjqAycZQ=="], - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg=="], + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Yr59qMy/gcvAfBuFdCGlAmGiLsYWMFDbsP4bAI6iNBzcJTGrYWZGeiQhGDo29MQwfF/NUBqyM2qljXu5tZ6wjQ=="], - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw=="], + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.12.3", "", { "os": "linux", "cpu": "none" }, "sha512-jKr0/REBMnLKC0OwPQZkyge0mKDaNZ0b/AbfGwVyCgFfwS730EVLfVDo3nt7MUu6jJaytRq08+xSj5qfy+U1+Q=="], - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw=="], + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-gTkEqJAGCMROGanxHnzaJzQ+/rXl39HWVg0/hDODS1wDWBGqoceqGC6Qm1+mQbyT11xnsB8mKu/gyVID/YpFIw=="], - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA=="], + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-T2dTxL1svRym71fZtaW4pZYXbthbfFsEJr1NeOUg/p7laWXZbUt/8DMSZf8recu9f7+1F/Lv00HtzsEu6vDO9w=="], - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ=="], + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.12.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-eoiQwpEZWbpsVHcM9BAzqqe4TCHszvr3rsM7pUQ4ofLK0lF1i1d/jX0Qo5DYMqcrUtwQuDdDuOCbcT0Izf1VwQ=="], - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA=="], + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.12.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-kE+KUg6Dbbkgvcw0m99wJVIToFQzJqpMBAXah74WqxaLzssNwSTUccrHp6+QVzMhjQP330tvekFmnQF+0NbE6Q=="], - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A=="], + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.12.3", "", { "os": "win32", "cpu": "x64" }, "sha512-L7wndWAgX2mLW7alswpL3d1R0P9FN2WYBgHaQkAYEBxHHGkY4vFX9CjcRQ1LAxDAMIJxrY7/aDziFJeChS84NQ=="], "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="], - "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.1", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ=="], + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.8.3", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-Hd/yBfk2R5v2MFrz/0VArSXeh8xKLLXGunqSiTtcHKn1zNZwaKujuA0VSwUvNfwC4hDoF1PveS+gBDNe3nHspw=="], - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ=="], + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.7.2", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-LyybP1VSzTMQiaFYcsbAse47KpivlyXdLZMQzGLEnkgQacGESnpjAfFoQ6h5sqWgAdZDH9szYjPzHMpv0Sz+TQ=="], - "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], + "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-XFQjntNNI3Vlqdy4WUGwiXX8DByzKVvoemAcUVkBm9NJPGmoDtII5i+vfuAzWaJfOE5cUJJLUDqXBnd4reSAIA=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], diff --git a/apps/maple-research/frontend/package.json b/apps/maple-research/frontend/package.json index db1243c09..ab5f1a696 100644 --- a/apps/maple-research/frontend/package.json +++ b/apps/maple-research/frontend/package.json @@ -29,7 +29,7 @@ "resolutions": { "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "mdast-util-gfm-autolink-literal": "2.0.0", "browserslist": "^4.28.8", "flatted": "^3.4.2", @@ -64,11 +64,11 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-dialog": "^2.8.3", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -92,7 +92,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.lock b/apps/maple-research/frontend/src-tauri/Cargo.lock index 9b89fe512..faa29bc76 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.lock +++ b/apps/maple-research/frontend/src-tauri/Cargo.lock @@ -184,13 +184,28 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "alloc-no-stdlib", + "alloc-no-stdlib 2.0.4", +] + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib 3.0.0", ] [[package]] @@ -890,47 +905,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] -name = "borsh" -version = "1.5.7" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ - "borsh-derive", - "cfg_aliases", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", + "brotli-decompressor 5.0.0", ] [[package]] -name = "borsh-derive" -version = "1.5.7" +name = "brotli" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" dependencies = [ - "once_cell", - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.108", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", + "brotli-decompressor 6.0.1", ] [[package]] -name = "brotli" -version = "8.0.2" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", ] [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", ] [[package]] @@ -965,17 +978,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byte-unit" -version = "5.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" -dependencies = [ - "rust_decimal", - "serde", - "utf8-width", -] - [[package]] name = "bytecount" version = "0.6.9" @@ -1092,12 +1094,13 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.22.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" dependencies = [ + "semver", "serde", - "toml 0.9.8", + "toml 1.1.8+spec-1.1.0", ] [[package]] @@ -1129,9 +1132,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" dependencies = [ "byteorder", "fnv", @@ -1140,13 +1143,13 @@ dependencies = [ [[package]] name = "cfb" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" dependencies = [ - "byteorder", "fnv", "uuid", + "web-time", ] [[package]] @@ -1370,7 +1373,7 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ - "brotli", + "brotli 8.0.2", "compression-core", "flate2", "memchr", @@ -1683,9 +1686,9 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" dependencies = [ "cssparser-macros", "dtoa-short", @@ -1696,29 +1699,19 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" dependencies = [ "quote", - "syn 2.0.108", + "syn 3.0.3", ] [[package]] name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "ctr" @@ -2042,6 +2035,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -2051,7 +2053,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2120,9 +2122,9 @@ dependencies = [ [[package]] name = "dom_query" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" dependencies = [ "bit-set", "cssparser", @@ -2163,21 +2165,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -2376,7 +2363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3200,7 +3187,7 @@ dependencies = [ "blake3", "chrono", "clap", - "dirs", + "dirs 6.0.0", "etcetera", "fs-err", "fs2", @@ -3217,7 +3204,7 @@ dependencies = [ "ignore", "image 0.24.9", "include_dir", - "indexmap 2.12.0", + "indexmap 2.14.2", "indoc", "jsonschema", "jsonwebtoken", @@ -3475,7 +3462,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -3619,9 +3606,9 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", "markup5ever", @@ -3762,7 +3749,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4045,12 +4032,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -4066,11 +4053,11 @@ dependencies = [ [[package]] name = "infer" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" dependencies = [ - "cfb 0.7.3", + "cfb 0.14.0", ] [[package]] @@ -4322,21 +4309,21 @@ dependencies = [ [[package]] name = "json-patch" -version = "3.0.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "jsonptr" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" dependencies = [ "serde", "serde_json", @@ -4413,13 +4400,12 @@ dependencies = [ [[package]] name = "keyboard-types" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" dependencies = [ "bitflags 2.10.0", "serde", - "unicode-segmentation", ] [[package]] @@ -4596,9 +4582,6 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -dependencies = [ - "value-bag", -] [[package]] name = "loop9" @@ -4680,7 +4663,7 @@ dependencies = [ "tokio-util", "tower-http 0.6.8", "webpki-roots", - "windows 0.62.2", + "windows", "zip", ] @@ -4746,9 +4729,9 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", "tendril", @@ -4870,9 +4853,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisign-verify" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" +checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" [[package]] name = "miniz_oxide" @@ -4907,9 +4890,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" dependencies = [ "crossbeam-channel", "dpi", @@ -4923,7 +4906,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5070,7 +5053,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5186,7 +5169,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.108", @@ -5258,6 +5241,7 @@ dependencies = [ "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -5776,7 +5760,7 @@ dependencies = [ "aes 0.9.1", "base64 0.22.1", "bitflags 2.10.0", - "brotli", + "brotli 8.0.2", "byteorder", "bytes", "cbc", @@ -6020,7 +6004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", - "indexmap 2.12.0", + "indexmap 2.14.2", "quick-xml 0.41.0", "serde", "time", @@ -6250,11 +6234,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.12.0", + "indexmap 2.14.2", "nix 0.31.3", "tokio", "tracing", - "windows 0.62.2", + "windows", ] [[package]] @@ -6937,23 +6921,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rust_decimal" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.6", - "rand 0.9.4", - "serde", - "serde_json", - "wasm-bindgen", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -7011,7 +6978,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7070,7 +7037,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7258,9 +7225,9 @@ dependencies = [ [[package]] name = "selectors" -version = "0.36.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" dependencies = [ "bitflags 2.10.0", "cssparser", @@ -7277,9 +7244,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -7344,7 +7311,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "itoa", "memchr", "ryu", @@ -7385,9 +7352,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -7415,7 +7382,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.14.2", "schemars 0.9.0", "schemars 1.0.5", "serde_core", @@ -7442,7 +7409,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "itoa", "ryu", "serde", @@ -7555,7 +7522,7 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -7694,7 +7661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7797,7 +7764,7 @@ dependencies = [ "futures-util", "hashbrown 0.16.0", "hashlink", - "indexmap 2.12.0", + "indexmap 2.14.2", "log", "memchr", "percent-encoding", @@ -7964,6 +7931,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_vcruntime" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7589a1859b09232522bb9edf076ca7fcf14ded96685d3fa629db73023ffbb7" + [[package]] name = "streaming-iterator" version = "0.1.9" @@ -8085,9 +8058,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swift-rs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9" dependencies = [ "base64 0.21.7", "serde", @@ -8219,7 +8192,7 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.2" +version = "0.37.1" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", @@ -8237,29 +8210,29 @@ dependencies = [ "libc", "log", "ndk", + "ndk-context", "ndk-sys", "objc2 0.6.4", "objc2-app-kit", "objc2-foundation 0.3.2", "objc2-ui-kit", - "once_cell", "parking_lot", "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", "url", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", "windows-version", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" dependencies = [ "proc-macro2", "quote", @@ -8285,14 +8258,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.2" +version = "2.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" +checksum = "f316751027fc2644221d1a448564d519efb07067fda584879d9cda966391d212" dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 7.0.0", "dunce", "embed_plist", "getrandom 0.3.4", @@ -8331,18 +8304,18 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.61.3", + "windows", ] [[package]] name = "tauri-build" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" +checksum = "641dd5fd297c99ab92717b0428cca5b9c6f960b2ac96ca131b3eebc9d70b8ddc" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 7.0.0", "glob", "heck 0.5.0", "json-patch", @@ -8350,6 +8323,8 @@ dependencies = [ "semver", "serde", "serde_json", + "static_vcruntime", + "strsim", "tauri-utils", "tauri-winres", "walkdir", @@ -8357,16 +8332,16 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" +checksum = "a6585c5b536c00f039d12ce70a9755172d5423bbcfd3969863baa70314c3e02f" dependencies = [ - "base64 0.22.1", - "brotli", + "base64 0.23.1", + "brotli 9.0.0", "ico", "json-patch", "plist", - "png 0.17.16", + "png 0.18.1", "proc-macro2", "quote", "semver", @@ -8384,9 +8359,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" +checksum = "e2e83a0cfa1bae376e8fbbdc0b1692707aed50cd6072ce85cae4789af5d6c19f" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -8398,9 +8373,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.2" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" +checksum = "67fb21a77e8f94e8beb3325e583102488659365af0a3948b7ea244c5b779c291" dependencies = [ "anyhow", "glob", @@ -8435,9 +8410,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.7.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +checksum = "b7461aaacf1901915ea7b9f5cd74f8224a351ed96af0a62c07fce934003cb34b" dependencies = [ "log", "raw-window-handle", @@ -8453,9 +8428,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +checksum = "5d58eb1d4984e535f47a642ffec09a2da6f5c18a12b5902d12126cb7626d628b" dependencies = [ "anyhow", "dunce", @@ -8471,7 +8446,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.8+spec-1.1.0", "url", ] @@ -8488,12 +8463,11 @@ dependencies = [ [[package]] name = "tauri-plugin-log" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" +checksum = "d83980f6765feb86cf2224959246be28b0488e14a23204a719f7f9deecfc64c9" dependencies = [ "android_logger", - "byte-unit", "fern", "log", "objc2 0.6.4", @@ -8510,9 +8484,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.5.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +checksum = "53810be1b4fed22bb6ac3f4cab5a2b80d9391d1af436bda145607532bc0bb219" dependencies = [ "dunce", "glob", @@ -8526,15 +8500,15 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows 0.61.3", + "windows", "zbus", ] [[package]] name = "tauri-plugin-os" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" +checksum = "4bd69d272ab772b30462779de12a40f837616831c794c71531bea91474e1061f" dependencies = [ "gethostname", "log", @@ -8578,16 +8552,17 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.10.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" +checksum = "7259989d4fe11b2bf51f7d671642249b1837c4ae2da1430dc713ae38f98ceeec" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 7.0.0", "flate2", "futures-util", "http", "infer", + "libc", "log", "minisign-verify", "osakit", @@ -8605,14 +8580,14 @@ dependencies = [ "time", "tokio", "url", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "tauri-runtime" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" +checksum = "7c55766d99cea69055904d1735b93c2049302b2e09effd462120deb5581d78e9" dependencies = [ "cookie", "dpi", @@ -8630,14 +8605,14 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.3", + "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +checksum = "8d61892587aea2a85245bcaec98c6fe46268874c0a0deae5535fb71ca7b93eae" dependencies = [ "gtk", "http", @@ -8655,18 +8630,18 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.3", + "windows", "wry", ] [[package]] name = "tauri-utils" -version = "2.9.2" +version = "2.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" +checksum = "362ab21cd4cc713f2a02b1a9b5688198b1541cfb6907c4281e9190e0f0ef4aa3" dependencies = [ "anyhow", - "brotli", + "brotli 9.0.0", "cargo_metadata", "ctor", "dom_query", @@ -8690,7 +8665,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 0.9.8", "url", "urlpattern", "uuid", @@ -8717,7 +8692,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -9011,9 +8986,9 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", + "serde_spanned 1.1.2", "toml_datetime 0.7.3", "toml_parser", "toml_writer", @@ -9022,14 +8997,14 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned 1.1.2", + "toml_datetime 1.1.2+spec-1.1.0", "toml_parser", "toml_writer", "winnow 1.0.3", @@ -9055,9 +9030,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] @@ -9068,7 +9043,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -9079,7 +9054,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -9092,7 +9067,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.7.3", "toml_parser", "winnow 0.7.13", @@ -9100,18 +9075,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -9293,12 +9268,12 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.23.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 7.0.0", "libappindicator", "muda", "objc2 0.6.4", @@ -9310,7 +9285,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9484,47 +9459,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -9670,13 +9604,13 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] @@ -9686,12 +9620,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -9758,12 +9686,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" - [[package]] name = "vcpkg" version = "0.2.15" @@ -10035,23 +9957,21 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows", + "windows-core", ] [[package]] name = "webview2-com-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", @@ -10060,13 +9980,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" dependencies = [ "thiserror 2.0.18", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", ] [[package]] @@ -10128,7 +10048,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -10139,51 +10059,30 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "raw-window-handle", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "windows-version", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -10192,20 +10091,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -10221,26 +10107,15 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", -] - [[package]] name = "windows-future" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-threading", ] [[package]] @@ -10277,23 +10152,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link 0.2.1", ] @@ -10437,15 +10302,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" @@ -10661,7 +10517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" dependencies = [ "font-types", - "indexmap 2.12.0", + "indexmap 2.14.2", "kurbo", "log", "read-fonts", @@ -10675,15 +10531,15 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wry" -version = "0.55.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0" dependencies = [ "base64 0.22.1", "block2 0.6.2", "cookie", "crossbeam-channel", - "dirs", + "dirs 7.0.0", "dom_query", "dpi", "dunce", @@ -10711,8 +10567,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", "windows-version", "x11-dl", ] @@ -10993,7 +10849,7 @@ checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" dependencies = [ "crc32fast", "flate2", - "indexmap 2.12.0", + "indexmap 2.14.2", "memchr", "nix 0.31.3", "typed-path", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index f58550c53..7da870c2a 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2.6.2", features = [] } +tauri-build = { version = "2.7.3", features = [] } plist = "1" serde_json = "1.0" @@ -23,23 +23,25 @@ serde_json = "1.0" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.11.2", features = [] } -tauri-plugin-log = "2.8.0" +tauri = { version = "2.12.4", features = [] } +tauri-plugin-log = "2.10.1" # Maple publishes raw Windows installers and Linux AppImages, so it does not # need the updater's legacy ZIP / AppImage.tar.gz handlers or their ZIP dependency. -tauri-plugin-updater = { version = "2.10.1", default-features = false, features = ["rustls-tls"] } -tauri-plugin = "2.6.2" +tauri-plugin-updater = { version = "2.14.0", default-features = false, features = ["rustls-tls"] } +tauri-plugin = "2.7.2" # Keep Android deep links on the last known-working plugin behavior. # tauri-plugin-deep-link 2.4.9 added Android-side config filtering that drops # our existing cloud.opensecret.maple:// OAuth callback before Rust receives it. tauri-plugin-deep-link = "=2.4.7" +# single-instance 2.4.1+ needs deep-link >= 2.4.8, so it stays on the newest release +# that resolves with the deep-link pin above. tauri-plugin-single-instance = { version = "=2.4.0", features = ["deep-link"] } -tauri-plugin-opener = "2.5.4" -tauri-plugin-os = "2.3.2" +tauri-plugin-opener = "2.7.2" +tauri-plugin-os = "2.4.0" tauri-plugin-sign-in-with-apple = "1.0.2" tokio = { version = "1.0", features = ["io-std", "io-util", "net", "process", "sync", "rt-multi-thread", "macros", "time"] } once_cell = "1.18.0" -tauri-plugin-fs = "2.5.1" +tauri-plugin-fs = "2.7.1" anyhow = "1.0" pdf_oxide = { version = "=0.3.74", git = "https://github.com/OpenSecretCloud/pdf_oxide.git", rev = "2bc9246782fe874a97e57e348acc7a9eaa27ca4d", features = ["ocr-ort", "rendering"] } # Keep the pre-1.0 Word parser exact-pinned. office_oxide is already in the @@ -73,7 +75,7 @@ axum = "0.8" tower-http = { version = "0.6", features = ["cors"] } async-trait = "0.1" rmcp = { version = "=3.1.2", default-features = false, features = ["client", "transport-streamable-http-client-reqwest"] } -tauri-plugin-dialog = "2.7.1" +tauri-plugin-dialog = "2.8.3" tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } agent-client-protocol = { version = "=2.0.0", default-features = false, features = ["unstable_elicitation", "unstable_end_turn_token_usage"] } chrono = { version = "0.4", default-features = false, features = ["std"] } @@ -117,7 +119,7 @@ ciborium = "0.2" # The embedded proxy requires `maple-sdk` from the registry; resolve it to the same # in-tree source as the dependency above (docs/sdk-publishing.md). maple-sdk = { path = "../../../../sdk/rust" } -# Local patch for tao 0.35.2 Android intent crashes: +# Local patch for tao 0.37.1 Android intent crashes: # https://github.com/tauri-apps/tao/issues/1217 # # Tao's Android intent handler currently unwraps several JNI calls that can @@ -126,4 +128,4 @@ maple-sdk = { path = "../../../../sdk/rust" } # this once Tauri resolves to a Tao release containing the upstream fix. # The vendored library uses this application's Cargo.lock; its upstream # standalone lockfile is intentionally omitted. -tao = { path = "patches/tao-0.35.2" } +tao = { path = "patches/tao-0.37.1" } diff --git a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index 0d4bd191c..df3acc569 100644 --- a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ #Tue May 10 19:22:52 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml deleted file mode 100644 index 3f0674d4e..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml +++ /dev/null @@ -1,406 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -rust-version = "1.74" -name = "tao" -version = "0.35.2" -authors = [ - "Tauri Programme within The Commons Conservancy", - "The winit contributors", -] -build = false -include = [ - "/README.md", - "src/**/*.rs", - "examples/**/*.rs", - "LICENSE*", -] -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "Cross-platform window manager library." -documentation = "https://docs.rs/tao" -readme = "README.md" -keywords = ["windowing"] -categories = ["gui"] -license = "Apache-2.0" -repository = "https://github.com/tauri-apps/tao" - -[package.metadata.docs.rs] -features = [ - "rwh_04", - "rwh_05", - "rwh_06", - "serde", - "x11", - "dbus", -] -default-target = "x86_64-unknown-linux-gnu" -targets = [ - "i686-pc-windows-msvc", - "x86_64-pc-windows-msvc", - "i686-unknown-linux-gnu", - "x86_64-unknown-linux-gnu", - "x86_64-apple-darwin", -] - -[features] -dbus = ["dep:dbus"] -default = [ - "rwh_06", - "x11", - "dbus", -] -rwh_04 = ["dep:rwh_04"] -rwh_05 = ["dep:rwh_05"] -rwh_06 = ["dep:rwh_06"] -serde = [ - "dep:serde", - "dpi/serde", -] -x11 = [ - "dep:gdkx11-sys", - "dep:x11-dl", -] - -[lib] -name = "tao" -path = "src/lib.rs" - -[[example]] -name = "control_flow" -path = "examples/control_flow.rs" - -[[example]] -name = "cursor" -path = "examples/cursor.rs" - -[[example]] -name = "cursor_grab" -path = "examples/cursor_grab.rs" - -[[example]] -name = "custom_events" -path = "examples/custom_events.rs" - -[[example]] -name = "decorations" -path = "examples/decorations.rs" - -[[example]] -name = "drag_window" -path = "examples/drag_window.rs" - -[[example]] -name = "fullscreen" -path = "examples/fullscreen.rs" - -[[example]] -name = "handling_close" -path = "examples/handling_close.rs" - -[[example]] -name = "min_max_size" -path = "examples/min_max_size.rs" - -[[example]] -name = "minimize" -path = "examples/minimize.rs" - -[[example]] -name = "monitor_list" -path = "examples/monitor_list.rs" - -[[example]] -name = "mouse_wheel" -path = "examples/mouse_wheel.rs" - -[[example]] -name = "multithreaded" -path = "examples/multithreaded.rs" - -[[example]] -name = "multiwindow" -path = "examples/multiwindow.rs" - -[[example]] -name = "overlay" -path = "examples/overlay.rs" - -[[example]] -name = "parentwindow" -path = "examples/parentwindow.rs" - -[[example]] -name = "progress_bar" -path = "examples/progress_bar.rs" - -[[example]] -name = "reopen_event" -path = "examples/reopen_event.rs" - -[[example]] -name = "request_redraw" -path = "examples/request_redraw.rs" - -[[example]] -name = "request_redraw_threaded" -path = "examples/request_redraw_threaded.rs" - -[[example]] -name = "resizable" -path = "examples/resizable.rs" - -[[example]] -name = "set_ime_position" -path = "examples/set_ime_position.rs" - -[[example]] -name = "theme" -path = "examples/theme.rs" - -[[example]] -name = "timer" -path = "examples/timer.rs" - -[[example]] -name = "transparent" -path = "examples/transparent.rs" - -[[example]] -name = "video_modes" -path = "examples/video_modes.rs" - -[[example]] -name = "window" -path = "examples/window.rs" - -[[example]] -name = "window_debug" -path = "examples/window_debug.rs" - -[[example]] -name = "window_icon" -path = "examples/window_icon.rs" - -[[example]] -name = "window_run_return" -path = "examples/window_run_return.rs" - -[dependencies.bitflags] -version = "2" - -[dependencies.crossbeam-channel] -version = "0.5" - -[dependencies.dpi] -version = "0.1" - -[dependencies.libc] -version = "0.2" - -[dependencies.log] -version = "0.4" - -[dependencies.once_cell] -version = "1" - -[dependencies.rwh_04] -version = "0.4" -optional = true -package = "raw-window-handle" - -[dependencies.rwh_05] -version = "0.5" -features = ["std"] -optional = true -package = "raw-window-handle" - -[dependencies.rwh_06] -version = "0.6" -features = ["std"] -optional = true -package = "raw-window-handle" - -[dependencies.serde] -version = "1" -features = ["serde_derive"] -optional = true - -[dependencies.url] -version = "2" - -[dev-dependencies.env_logger] -version = "0.11" - -[dev-dependencies.image] -version = "0.25" - -[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.block2] -version = "0.6" - -[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies.objc2] -version = "0.6" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.dbus] -version = "0.9" -optional = true - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.dlopen2] -version = "0.8.0" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gdkwayland-sys] -version = "0.18.0" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gdkx11-sys] -version = "0.18" -optional = true - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.gtk] -version = "0.18" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.parking_lot] -version = "0.12" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies.x11-dl] -version = "2.21" -optional = true - -[target.'cfg(target_os = "android")'.dependencies.jni] -version = "0.21" - -[target.'cfg(target_os = "android")'.dependencies.ndk] -version = "0.9" - -[target.'cfg(target_os = "android")'.dependencies.ndk-sys] -version = "0.6" - -[target.'cfg(target_os = "android")'.dependencies.percent-encoding] -version = "2.3" - -[target.'cfg(target_os = "android")'.dependencies.tao-macros] -version = "0.1.0" - -[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] -version = "0.3" -features = ["NSProcessInfo"] -default-features = false - -[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit] -version = "0.3" -features = [ - "UIResponder", - "UIScene", - "UISceneOptions", - "UIOpenURLContext", -] - -[target.'cfg(target_os = "macos")'.dependencies.core-foundation] -version = "0.10" - -[target.'cfg(target_os = "macos")'.dependencies.core-graphics] -version = "0.25" - -[target.'cfg(target_os = "macos")'.dependencies.dispatch2] -version = "0.3" - -[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit] -version = "0.3" -features = [ - "std", - "objc2-core-foundation", - "NSApplication", - "NSButton", - "NSColor", - "NSControl", - "NSEvent", - "NSGraphics", - "NSImage", - "NSOpenGLView", - "NSPasteboard", - "NSResponder", - "NSRunningApplication", - "NSScreen", - "NSView", - "NSWindow", - "NSUserActivity", -] -default-features = false - -[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] -version = "0.3" -features = [ - "std", - "NSArray", - "NSAttributedString", - "NSAutoreleasePool", - "NSDate", - "NSDictionary", - "NSEnumerator", - "NSGeometry", - "NSObjCRuntime", - "NSRange", - "NSString", - "NSThread", - "NSURL", -] -default-features = false - -[target.'cfg(target_os = "windows")'.dependencies.parking_lot] -version = "0.12" - -[target.'cfg(target_os = "windows")'.dependencies.unicode-segmentation] -version = "1.11" - -[target.'cfg(target_os = "windows")'.dependencies.windows] -version = "0.61" -features = [ - "Win32_Devices_HumanInterfaceDevice", - "Win32_Foundation", - "Win32_Globalization", - "Win32_Graphics_Dwm", - "Win32_Graphics_Gdi", - "Win32_System_Com", - "Win32_System_Com_StructuredStorage", - "Win32_System_DataExchange", - "Win32_System_Diagnostics_Debug", - "Win32_System_LibraryLoader", - "Win32_System_Memory", - "Win32_System_Ole", - "Win32_System_Registry", - "Win32_System_SystemServices", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", - "Win32_System_SystemInformation", - "Win32_UI_Accessibility", - "Win32_UI_Controls", - "Win32_UI_HiDpi", - "Win32_UI_Input_Ime", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_UI_Input_Pointer", - "Win32_UI_Input_Touch", - "Win32_UI_Shell", - "Win32_UI_TextServices", - "Win32_UI_WindowsAndMessaging", -] - -[target.'cfg(target_os = "windows")'.dependencies.windows-core] -version = "0.61" - -[target.'cfg(target_os = "windows")'.dependencies.windows-version] -version = "0.1" - -[target.'cfg(target_os = "windows")'.dev-dependencies.softbuffer] -version = "0.4" diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig deleted file mode 100644 index 75c8c138b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig +++ /dev/null @@ -1,172 +0,0 @@ -[package] -name = "tao" -version = "0.35.2" -description = "Cross-platform window manager library." -authors = [ - "Tauri Programme within The Commons Conservancy", - "The winit contributors", -] -edition = "2021" -rust-version = "1.74" -keywords = ["windowing"] -license = "Apache-2.0" -readme = "README.md" -repository = "https://github.com/tauri-apps/tao" -documentation = "https://docs.rs/tao" -categories = ["gui"] -include = ["/README.md", "src/**/*.rs", "examples/**/*.rs", "LICENSE*"] - -[package.metadata.docs.rs] -features = ["rwh_04", "rwh_05", "rwh_06", "serde", "x11", "dbus"] -default-target = "x86_64-unknown-linux-gnu" -targets = [ - "i686-pc-windows-msvc", - "x86_64-pc-windows-msvc", - "i686-unknown-linux-gnu", - "x86_64-unknown-linux-gnu", - "x86_64-apple-darwin", -] - -[features] -default = ["rwh_06", "x11", "dbus"] -serde = ["dep:serde", "dpi/serde"] -rwh_04 = ["dep:rwh_04"] -rwh_05 = ["dep:rwh_05"] -rwh_06 = ["dep:rwh_06"] -x11 = ["dep:gdkx11-sys", "dep:x11-dl"] -dbus = ["dep:dbus"] - -[workspace] -members = ["tao-macros"] - -[dependencies] -libc = "0.2" -log = "0.4" -once_cell = "1" -serde = { version = "1", optional = true, features = ["serde_derive"] } -rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } -rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ - "std", -], optional = true } -rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ - "std", -], optional = true } -bitflags = "2" -crossbeam-channel = "0.5" -url = "2" -dpi = "0.1" - -[dev-dependencies] -image = "0.25" -env_logger = "0.11" - -[target."cfg(target_os = \"windows\")".dev-dependencies] -softbuffer = "0.4" - -[target."cfg(target_os = \"windows\")".dependencies] -parking_lot = "0.12" -unicode-segmentation = "1.11" -windows-version = "0.1" -windows-core = "0.61" - -[target."cfg(target_os = \"windows\")".dependencies.windows] -version = "0.61" -features = [ - "Win32_Devices_HumanInterfaceDevice", - "Win32_Foundation", - "Win32_Globalization", - "Win32_Graphics_Dwm", - "Win32_Graphics_Gdi", - "Win32_System_Com", - "Win32_System_Com_StructuredStorage", - "Win32_System_DataExchange", - "Win32_System_Diagnostics_Debug", - "Win32_System_LibraryLoader", - "Win32_System_Memory", - "Win32_System_Ole", - "Win32_System_Registry", - "Win32_System_SystemServices", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", - "Win32_System_SystemInformation", - "Win32_UI_Accessibility", - "Win32_UI_Controls", - "Win32_UI_HiDpi", - "Win32_UI_Input_Ime", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_UI_Input_Pointer", - "Win32_UI_Input_Touch", - "Win32_UI_Shell", - "Win32_UI_TextServices", - "Win32_UI_WindowsAndMessaging", -] - -[target."cfg(target_os = \"android\")".dependencies] -jni = "0.21" -ndk = "0.9" -ndk-sys = "0.6" -percent-encoding = "2.3" -tao-macros = { version = "0.1.0", path = "./tao-macros" } - -[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] -objc2 = "0.6" -block2 = "0.6" - -[target."cfg(target_os = \"macos\")".dependencies] -objc2-foundation = { version = "0.3", default-features = false, features = [ - "std", - "NSArray", - "NSAttributedString", - "NSAutoreleasePool", - "NSDate", - "NSDictionary", - "NSEnumerator", - "NSGeometry", - "NSObjCRuntime", - "NSRange", - "NSString", - "NSThread", - "NSURL", -] } -objc2-app-kit = { version = "0.3", default-features = false, features = [ - "std", - "objc2-core-foundation", - "NSApplication", - "NSButton", - "NSColor", - "NSControl", - "NSEvent", - "NSGraphics", - "NSImage", - "NSOpenGLView", - "NSPasteboard", - "NSResponder", - "NSRunningApplication", - "NSScreen", - "NSView", - "NSWindow", - "NSUserActivity", -] } -core-foundation = "0.10" -core-graphics = "0.25" -dispatch2 = "0.3" - -[target."cfg(target_os = \"ios\")".dependencies] -objc2-ui-kit = { version = "0.3", features = [ - "UIResponder", - "UIScene", - "UISceneOptions", - "UIOpenURLContext", -] } -objc2-foundation = { version = "0.3", default-features = false, features = [ - "NSProcessInfo", -] } - -[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] -gtk = "0.18" -gdkx11-sys = { version = "0.18", optional = true } -gdkwayland-sys = "0.18.0" -x11-dl = { version = "2.21", optional = true } -parking_lot = "0.12" -dlopen2 = "0.8.0" -dbus = { version = "0.9", optional = true } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE deleted file mode 100644 index ad410e113..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx deleted file mode 100644 index ac886c6d6..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx +++ /dev/null @@ -1,18 +0,0 @@ -SPDXVersion: SPDX-2.1 -DataLicense: CC0-1.0 -PackageName: tao -DataFormat: SPDXRef-1 -PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy -PackageHomePage: https://tauri.app -PackageLicenseDeclared: Apache-2.0 -PackageCopyrightText: 2021-2023, The Tauri Programme in the Commons Conservancy -PackageSummary: Tao is the official, rust-based window manager for Wry. - -PackageComment: The package includes the following libraries; see -Relationship information. - -Created: 2020-05-20T09:00:00Z -PackageDownloadLocation: git://github.com/tauri-apps/tao -PackageDownloadLocation: git+https://github.com/tauri-apps/tao.git -PackageDownloadLocation: git+ssh://github.com/tauri-apps/tao.git -Creator: Person: Daniel Thompson-Yvetot diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md deleted file mode 100644 index a7d504619..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md +++ /dev/null @@ -1,80 +0,0 @@ -TAO - Window Creation Library - -[![](https://img.shields.io/crates/v/tao?style=flat-square)](https://crates.io/crates/tao) -[![](https://img.shields.io/docsrs/tao?style=flat-square)](https://docs.rs/tao/) -[![License](https://img.shields.io/badge/License-Apache%202-green.svg)](https://opencollective.com/tauri) -[![Chat Server](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/SpmNs4S) -[![website](https://img.shields.io/badge/website-tauri.app-purple.svg)](https://tauri.app) -[![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) -[![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/tauri) - -Cross-platform application window creation library in Rust that supports all major platforms like -Windows, macOS, Linux, iOS and Android. Built for you, maintained for Tauri. - -### Cargo Features - -TAO provides the following features, which can be enabled in your `Cargo.toml` file: - -- `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). - -## Platform-specific notes - -### Android - -This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. - -Running on an Android device needs a dynamic system library, add this to Cargo.toml: - -```toml -[[example]] -name = "request_redraw_threaded" -crate-type = ["cdylib"] -``` - -And add this to the example file to add the native activity glue: - -```rust -#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] -fn main() { - ... -} -``` - -And run the application with `cargo apk run --example request_redraw_threaded` - -### Linux - -Gtk and its related libraries are used to build the support of Linux. Be sure to install following packages before building: - -#### Arch Linux / Manjaro: - -```bash -sudo pacman -S gtk3 -``` - -#### Debian / Ubuntu: - -```bash -sudo apt install libgtk-3-dev -``` - -### Acknowledgement - -This is a fork of [winit](https://crates.io/crates/winit) which replaces Linux's port to Gtk. -In the future, we want to make these features more modular as separate crates. So we can switch back to winit and also benefit the whole community. - -## Partners - - - - - - - -
- - CrabNebula - -
- -For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs deleted file mode 100644 index 45148bfd2..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{thread, time}; - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - window::WindowBuilder, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Mode { - Wait, - WaitUntil, - Poll, -} - -const WAIT_TIME: time::Duration = time::Duration::from_millis(100); -const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - - println!("Press '1' to switch to Wait mode."); - println!("Press '2' to switch to WaitUntil mode."); - println!("Press '3' to switch to Poll mode."); - println!("Press 'R' to toggle request_redraw() calls."); - println!("Press 'Esc' to close the window."); - - let event_loop = EventLoop::new(); - let window = WindowBuilder::new() - .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") - .build(&event_loop) - .unwrap(); - - let mut mode = Mode::Wait; - let mut request_redraw = false; - let mut wait_cancelled = false; - let mut close_requested = false; - - event_loop.run(move |event, _, control_flow| { - use tao::event::StartCause; - println!("{event:?}"); - match event { - Event::NewEvents(start_cause) => { - wait_cancelled = match start_cause { - StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, - _ => false, - } - } - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - close_requested = true; - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key, - state: ElementState::Pressed, - .. - }, - .. - } => { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - if Key::Character("1") == logical_key { - mode = Mode::Wait; - println!("\nmode: {mode:?}\n"); - } - if Key::Character("2") == logical_key { - mode = Mode::WaitUntil; - println!("\nmode: {mode:?}\n"); - } - if Key::Character("3") == logical_key { - mode = Mode::Poll; - println!("\nmode: {mode:?}\n"); - } - if Key::Character("r") == logical_key { - request_redraw = !request_redraw; - println!("\nrequest_redraw: {request_redraw}\n"); - } - if Key::Escape == logical_key { - close_requested = true; - } - } - _ => {} - }, - Event::MainEventsCleared => { - if request_redraw && !wait_cancelled && !close_requested { - window.request_redraw(); - } - if close_requested { - *control_flow = ControlFlow::Exit; - } - } - Event::RedrawRequested(_window_id) => {} - Event::RedrawEventsCleared => { - *control_flow = match mode { - Mode::Wait => ControlFlow::Wait, - Mode::WaitUntil => { - if wait_cancelled { - *control_flow - } else { - ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME) - } - } - Mode::Poll => { - thread::sleep(POLL_SLEEP_TIME); - ControlFlow::Poll - } - }; - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs deleted file mode 100644 index 48a91d6a9..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, -}; - -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - window.set_title("A fantastic window!"); - - let mut cursor_idx = 0; - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Pressed, - .. - }, - .. - }, - .. - } => { - println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); - window.set_cursor_icon(CURSORS[cursor_idx]); - if cursor_idx < CURSORS.len() - 1 { - cursor_idx += 1; - } else { - cursor_idx = 0; - } - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => (), - } - }); -} - -const CURSORS: &[CursorIcon] = &[ - CursorIcon::Default, - CursorIcon::Crosshair, - CursorIcon::Hand, - CursorIcon::Arrow, - CursorIcon::Move, - CursorIcon::Text, - CursorIcon::Wait, - CursorIcon::Help, - CursorIcon::Progress, - CursorIcon::NotAllowed, - CursorIcon::ContextMenu, - CursorIcon::Cell, - CursorIcon::VerticalText, - CursorIcon::Alias, - CursorIcon::Copy, - CursorIcon::NoDrop, - CursorIcon::Grab, - CursorIcon::Grabbing, - CursorIcon::AllScroll, - CursorIcon::ZoomIn, - CursorIcon::ZoomOut, - CursorIcon::EResize, - CursorIcon::NResize, - CursorIcon::NeResize, - CursorIcon::NwResize, - CursorIcon::SResize, - CursorIcon::SeResize, - CursorIcon::SwResize, - CursorIcon::WResize, - CursorIcon::EwResize, - CursorIcon::NsResize, - CursorIcon::NeswResize, - CursorIcon::NwseResize, - CursorIcon::ColResize, - CursorIcon::RowResize, -]; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs deleted file mode 100644 index a939d8201..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, ModifiersState}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("Super Cursor Grab'n'Hide Simulator 9000") - .build(&event_loop) - .unwrap(); - - let mut modifiers = ModifiersState::default(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: key, - state: ElementState::Released, - .. - }, - .. - } => { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - match key { - Key::Escape => *control_flow = ControlFlow::Exit, - Key::Character(ch) => match ch.to_lowercase().as_str() { - "g" => window.set_cursor_grab(!modifiers.shift_key()).unwrap(), - "h" => window.set_cursor_visible(modifiers.shift_key()), - _ => (), - }, - _ => (), - } - } - WindowEvent::ModifiersChanged(m) => modifiers = m, - _ => (), - }, - Event::DeviceEvent { event, .. } => match event { - DeviceEvent::MouseMotion { delta, .. } => println!("mouse moved: {delta:?}"), - DeviceEvent::Button { button, state, .. } => match state { - ElementState::Pressed => println!("mouse button {button} pressed"), - ElementState::Released => println!("mouse button {button} released"), - _ => (), - }, - _ => (), - }, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs deleted file mode 100644 index f94d00113..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoopBuilder}, - window::WindowBuilder, - }; - - #[derive(Debug, Clone, Copy)] - enum CustomEvent { - Timer, - } - - let event_loop = EventLoopBuilder::::with_user_event().build(); - - let _window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - // `EventLoopProxy` allows you to dispatch custom events to the main Tao event - // loop from any thread. - let event_loop_proxy = event_loop.create_proxy(); - - std::thread::spawn(move || { - // Wake up the `event_loop` once every second and dispatch a custom event - // from a different thread. - loop { - std::thread::sleep(std::time::Duration::from_secs(1)); - event_loop_proxy.send_event(CustomEvent::Timer).ok(); - } - }); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::UserEvent(event) => println!("user event: {event:?}"), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs deleted file mode 100644 index d9025e2b4..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - dpi::LogicalSize, - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::KeyCode, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let mut decorations = true; - - let window = WindowBuilder::new() - .with_title("Hit space to toggle decorations.") - .with_inner_size(LogicalSize::new(400.0, 200.0)) - .with_decorations(decorations) - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - event: - KeyEvent { - physical_key: KeyCode::Space, - state: ElementState::Released, - .. - }, - .. - } => { - decorations = !decorations; - println!("Decorations: {decorations}"); - window.set_decorations(decorations); - } - _ => (), - }, - _ => (), - }; - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs deleted file mode 100644 index 895c360d3..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - window::{Window, WindowBuilder, WindowId}, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window_1 = WindowBuilder::new().build(&event_loop).unwrap(); - let window_2 = WindowBuilder::new().build(&event_loop).unwrap(); - - let mut switched = false; - let mut entered_id = window_2.id(); - - event_loop.run(move |event, _, control_flow| match event { - Event::NewEvents(StartCause::Init) => { - eprintln!("Switch which window is to be dragged by pressing \"x\".") - } - Event::WindowEvent { - event, window_id, .. - } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - .. - } => { - let window = if (window_id == window_1.id() && switched) - || (window_id == window_2.id() && !switched) - { - &window_2 - } else { - &window_1 - }; - - window.drag_window().unwrap() - } - WindowEvent::CursorEntered { .. } => { - entered_id = window_id; - name_windows(entered_id, switched, &window_1, &window_2) - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Released, - logical_key: Key::Character("x"), - .. - }, - .. - } => { - switched = !switched; - name_windows(entered_id, switched, &window_1, &window_2); - println!("Switched!") - } - _ => (), - }, - _ => (), - }); -} - -fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) { - let (drag_target, other) = - if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { - (&window_2, &window_1) - } else { - (&window_1, &window_2) - }; - drag_target.set_title("drag target"); - other.set_title("tao window"); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs deleted file mode 100644 index c5d8bfacd..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::io::{stdin, stdout, Write}; - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - monitor::{MonitorHandle, VideoMode}, - window::{Fullscreen, WindowBuilder}, -}; -#[allow(clippy::single_match)] -#[allow(clippy::ok_expect)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless, (3) borderless on current monitor: "); - stdout().flush().unwrap(); - - let mut num = String::new(); - stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().ok().expect("Please enter a number"); - - let fullscreen = Some(match num { - 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), - 2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))), - 3 => Fullscreen::Borderless(None), - _ => panic!("Please enter a valid number"), - }); - - let mut decorations = true; - - let window = WindowBuilder::new() - .with_title("Hello world!") - .with_fullscreen(fullscreen.clone()) - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key, - state: ElementState::Pressed, - .. - }, - .. - } => { - if Key::Escape == logical_key { - *control_flow = ControlFlow::Exit - } - - if Key::Character("f") == logical_key { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - window.set_fullscreen(fullscreen.clone()); - } - } - - if Key::Character("s") == logical_key { - println!("window.fullscreen {:?}", window.fullscreen()); - } - if Key::Character("m") == logical_key { - let is_maximized = window.is_maximized(); - window.set_maximized(!is_maximized); - } - if Key::Character("d") == logical_key { - decorations = !decorations; - window.set_decorations(decorations); - } - } - _ => (), - }, - _ => {} - } - }); -} - -// Enumerate monitors and prompt user to choose one -fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { - for (num, monitor) in event_loop.available_monitors().enumerate() { - println!("Monitor #{}: {:?}", num, monitor.name()); - } - - print!("Please write the number of the monitor to use: "); - stdout().flush().unwrap(); - - let mut num = String::new(); - stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().expect("Please enter a number"); - let monitor = event_loop - .available_monitors() - .nth(num) - .expect("Please enter a valid ID"); - - println!("Using {:?}", monitor.name()); - - monitor -} - -fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { - for (i, video_mode) in monitor.video_modes().enumerate() { - println!("Video mode #{i}: {video_mode}"); - } - - print!("Please write the number of the video mode to use: "); - stdout().flush().unwrap(); - - let mut num = String::new(); - stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().expect("Please enter a number"); - let video_mode = monitor - .video_modes() - .nth(num) - .expect("Please enter a valid ID"); - - println!("Using {video_mode}"); - - video_mode -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs deleted file mode 100644 index 278a646fc..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let _window = WindowBuilder::new() - .with_title("Your faithful window") - .build(&event_loop) - .unwrap(); - - let mut close_requested = false; - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CloseRequested => { - // `CloseRequested` is sent when the close button on the window is pressed (or - // through whatever other mechanisms the window manager provides for closing a - // window). If you don't handle this event, the close button won't actually do - // anything. - - // A common thing to do here is prompt the user if they have unsaved work. - // Creating a proper dialog box for that is far beyond the scope of this - // example, so here we'll just respond to the Y and N keys. - println!("Are you ready to bid your window farewell? [Y/N]"); - close_requested = true; - - // In applications where you can safely close the window without further - // action from the user, this is generally where you'd handle cleanup before - // closing the window. How to close the window is detailed in the handler for - // the Y key. - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Character(char), - state: ElementState::Released, - .. - }, - .. - } => { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - match char { - "y" => { - if close_requested { - // This is where you'll want to do any cleanup you need. - println!("Buh-bye!"); - - // For a single-window application like this, you'd normally just - // break out of the event loop here. If you wanted to keep running the - // event loop (i.e. if it's a multi-window application), you need to - // drop the window. That closes it, and results in `Destroyed` being - // sent. - *control_flow = ControlFlow::Exit; - } - } - "n" => { - if close_requested { - println!("Your window will continue to stay by your side."); - close_requested = false; - } - } - _ => (), - } - } - _ => (), - } - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs deleted file mode 100644 index 6ad631faa..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - dpi::LogicalUnit, - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - window::{WindowBuilder, WindowSizeConstraints}, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let min_width = 400.0; - let max_width = 800.0; - let min_height = 200.0; - let max_height = 400.0; - let mut size_constraints = WindowSizeConstraints::default(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - eprintln!("constraint keys:"); - eprintln!(" (E) Toggle the min width"); - eprintln!(" (F) Toggle the max width"); - eprintln!(" (P) Toggle the min height"); - eprintln!(" (V) Toggle the max height"); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Character(key_str), - state: ElementState::Released, - .. - }, - .. - }, - .. - } => match key_str { - "e" => { - size_constraints.min_width = size_constraints - .min_width - .is_none() - .then_some(LogicalUnit::new(min_width).into()); - window.set_inner_size_constraints(size_constraints); - } - "f" => { - size_constraints.max_width = size_constraints - .max_width - .is_none() - .then_some(LogicalUnit::new(max_width).into()); - window.set_inner_size_constraints(size_constraints); - } - "p" => { - size_constraints.min_height = size_constraints - .min_height - .is_none() - .then_some(LogicalUnit::new(min_height).into()); - window.set_inner_size_constraints(size_constraints); - } - "v" => { - size_constraints.max_height = size_constraints - .max_height - .is_none() - .then_some(LogicalUnit::new(max_height).into()); - window.set_inner_size_constraints(size_constraints); - } - _ => {} - }, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs deleted file mode 100644 index 76ceeb7b5..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -extern crate tao; - -use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::Key, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - - // Keyboard input event to handle minimize via a hotkey - Event::WindowEvent { - event: WindowEvent::KeyboardInput { event, .. }, - window_id, - .. - } if window_id == window.id() && Key::Character("m") == event.logical_key => { - // Pressing the 'm' key will minimize the window - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - window.set_minimized(true); - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs deleted file mode 100644 index 02ea917d6..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{event_loop::EventLoop, window::WindowBuilder}; - -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - dbg!(window.available_monitors().collect::>()); - dbg!(window.primary_monitor()); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs deleted file mode 100644 index 8997a5e6f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{DeviceEvent, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::collapsible_match)] -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("Mouse Wheel events") - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => (), - }, - Event::DeviceEvent { event, .. } => match event { - DeviceEvent::MouseWheel { delta, .. } => match delta { - tao::event::MouseScrollDelta::LineDelta(x, y) => { - println!("mouse wheel Line Delta: ({x},{y})"); - let pixels_per_line = 120.0; - let mut pos = window.outer_position().unwrap(); - pos.x -= (x * pixels_per_line) as i32; - pos.y -= (y * pixels_per_line) as i32; - window.set_outer_position(pos) - } - tao::event::MouseScrollDelta::PixelDelta(p) => { - println!("mouse wheel Pixel Delta: ({},{})", p.x, p.y); - let mut pos = window.outer_position().unwrap(); - pos.x -= p.x as i32; - pos.y -= p.y as i32; - window.set_outer_position(pos) - } - _ => (), - }, - _ => (), - }, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs deleted file mode 100644 index 7dfe99632..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[allow(clippy::single_match)] -#[allow(clippy::iter_nth)] -fn main() { - use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; - - use tao::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, ModifiersState}, - window::{CursorIcon, Fullscreen, WindowBuilder}, - }; - - const WINDOW_COUNT: usize = 3; - const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); - - env_logger::init(); - let event_loop = EventLoop::new(); - let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); - for _ in 0..WINDOW_COUNT { - let window = WindowBuilder::new() - .with_inner_size(WINDOW_SIZE) - .build(&event_loop) - .unwrap(); - - let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect(); - let mut video_mode_id = 0usize; - - let (tx, rx) = mpsc::channel(); - window_senders.insert(window.id(), tx); - let mut modifiers = ModifiersState::default(); - thread::spawn(move || { - while let Ok(event) = rx.recv() { - match event { - WindowEvent::Moved { .. } => { - // We need to update our chosen video mode if the window - // was moved to an another monitor, so that the window - // appears on this monitor instead when we go fullscreen - let previous_video_mode = video_modes.iter().nth(video_mode_id).cloned(); - video_modes = window.current_monitor().unwrap().video_modes().collect(); - video_mode_id = video_mode_id.min(video_modes.len()); - let video_mode = video_modes.iter().nth(video_mode_id); - - // Different monitors may support different video modes, - // and the index we chose previously may now point to a - // completely different video mode, so notify the user - if video_mode != previous_video_mode.as_ref() { - println!( - "Window moved to another monitor, picked video mode: {}", - video_modes.iter().nth(video_mode_id).unwrap() - ); - } - } - WindowEvent::ModifiersChanged(mod_state) => { - modifiers = mod_state; - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Released, - logical_key: key, - .. - }, - .. - } => { - use Key::{ArrowLeft, ArrowRight, Character}; - window.set_title(&format!("{key:?}")); - let state = !modifiers.shift_key(); - match &key { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - Character(string) => match string.to_lowercase().as_str() { - "a" => window.set_always_on_top(state), - "c" => window.set_cursor_icon(match state { - true => CursorIcon::Progress, - false => CursorIcon::Default, - }), - "d" => window.set_decorations(!state), - "f" => window.set_fullscreen(match (state, modifiers.alt_key()) { - (true, false) => Some(Fullscreen::Borderless(None)), - (true, true) => Some(Fullscreen::Exclusive( - video_modes.iter().nth(video_mode_id).unwrap().clone(), - )), - (false, _) => None, - }), - "g" => window.set_cursor_grab(state).unwrap(), - "h" => window.set_cursor_visible(!state), - "i" => { - println!("Info:"); - println!("-> outer_position : {:?}", window.outer_position()); - println!("-> inner_position : {:?}", window.inner_position()); - println!("-> outer_size : {:?}", window.outer_size()); - println!("-> inner_size : {:?}", window.inner_size()); - println!("-> fullscreen : {:?}", window.fullscreen()); - } - "l" => window.set_min_inner_size(match state { - true => Some(WINDOW_SIZE), - false => None, - }), - "m" => window.set_maximized(state), - "p" => window.set_outer_position({ - let mut position = window.outer_position().unwrap(); - let sign = if state { 1 } else { -1 }; - position.x += 10 * sign; - position.y += 10 * sign; - position - }), - "q" => window.request_redraw(), - "r" => window.set_resizable(state), - "s" => window.set_inner_size(match state { - true => PhysicalSize::new(WINDOW_SIZE.width + 100, WINDOW_SIZE.height + 100), - false => WINDOW_SIZE, - }), - "w" => { - if let Size::Physical(size) = WINDOW_SIZE.into() { - window - .set_cursor_position(Position::Physical(PhysicalPosition::new( - size.width as i32 / 2, - size.height as i32 / 2, - ))) - .unwrap() - } - } - "z" => { - window.set_visible(false); - thread::sleep(Duration::from_secs(1)); - window.set_visible(true); - } - _ => (), - }, - ArrowRight | ArrowLeft => { - video_mode_id = match &key { - ArrowLeft => video_mode_id.saturating_sub(1), - ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1), - _ => unreachable!(), - }; - println!( - "Picking video mode: {}", - video_modes.iter().nth(video_mode_id).unwrap() - ); - } - _ => (), - } - } - _ => (), - } - } - }); - } - event_loop.run(move |event, _event_loop, control_flow| { - *control_flow = match !window_senders.is_empty() { - true => ControlFlow::Wait, - false => ControlFlow::Exit, - }; - match event { - Event::WindowEvent { - event, window_id, .. - } => match event { - WindowEvent::CloseRequested - | WindowEvent::Destroyed - | WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Released, - logical_key: Key::Escape, - .. - }, - .. - } => { - window_senders.remove(&window_id); - } - _ => { - if let Some(tx) = window_senders.get(&window_id) { - if let Some(event) = event.to_static() { - tx.send(event).unwrap(); - } - } - } - }, - _ => (), - } - }) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs deleted file mode 100644 index 32973057a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::Window, -}; - -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let mut windows = HashMap::new(); - for _ in 0..3 { - let window = Window::new(&event_loop).unwrap(); - windows.insert(window.id(), window); - } - - event_loop.run(move |event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; - - if let Event::WindowEvent { - event, window_id, .. - } = event - { - match event { - WindowEvent::CloseRequested => { - println!("Window {window_id:?} has received the signal to close"); - - // This drops the window, causing it to close. - windows.remove(&window_id); - - if windows.is_empty() { - *control_flow = ControlFlow::Exit; - } - } - WindowEvent::KeyboardInput { - event: KeyEvent { - state: ElementState::Pressed, - .. - }, - .. - } => { - let window = Window::new(event_loop).unwrap(); - windows.insert(window.id(), window); - } - _ => (), - } - } - }) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs deleted file mode 100644 index 03f290c61..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, ModifiersState}, - window::WindowBuilder, -}; - -#[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -use tao::platform::unix::WindowExtUnix; - -#[cfg(target_os = "macos")] -use tao::platform::macos::WindowExtMacOS; - -#[cfg(target_os = "ios")] -use tao::platform::ios::WindowExtIOS; - -#[cfg(windows)] -use tao::{ - dpi::PhysicalSize, platform::windows::IconExtWindows, platform::windows::WindowExtWindows, - window::Icon, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - let mut modifiers = ModifiersState::default(); - - eprintln!("Key mappings:"); - #[cfg(windows)] - eprintln!(" [any key]: Show the Overlay Icon"); - #[cfg(not(windows))] - eprintln!(" [1-5]: Show a Badge count"); - eprintln!(" Ctrl+1: Clear"); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - Event::WindowEvent { event, .. } => match event { - WindowEvent::ModifiersChanged(new_state) => { - modifiers = new_state; - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Character(key_str), - state: ElementState::Released, - .. - }, - .. - } => { - let _count = match key_str { - "1" => 1, - "2" => 2, - "3" => 3, - "4" => 4, - "5" => 5, - _ => 20, - }; - - if modifiers.is_empty() { - #[cfg(windows)] - { - let mut path = std::env::current_dir().unwrap(); - path.push("./examples/icon.ico"); - let icon = Icon::from_path(path, Some(PhysicalSize::new(32, 32))).unwrap(); - - window.set_overlay_icon(Some(&icon)); - } - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - window.set_badge_count(Some(_count), None); - - #[cfg(target_os = "macos")] - window.set_badge_label(_count.to_string().into()); - - #[cfg(target_os = "ios")] - window.set_badge_count(_count); - } else if modifiers.control_key() && key_str == "1" { - #[cfg(windows)] - window.set_overlay_icon(None); - - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] - window.set_badge_count(None, None); - - #[cfg(target_os = "macos")] - window.set_badge_label(None); - - #[cfg(target_os = "ios")] - window.set_badge_count(0); - } - } - _ => {} - }, - _ => {} - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs deleted file mode 100644 index 542565105..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] -fn main() { - use std::collections::HashMap; - #[cfg(target_os = "macos")] - use tao::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; - #[cfg(target_os = "linux")] - use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}; - #[cfg(target_os = "windows")] - use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows}; - use tao::{ - dpi::LogicalSize, - event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - env_logger::init(); - let event_loop = EventLoop::new(); - let mut windows = HashMap::new(); - let main_window = WindowBuilder::new().build(&event_loop).unwrap(); - - #[cfg(target_os = "macos")] - let parent_window = main_window.ns_window(); - #[cfg(target_os = "windows")] - let parent_window = main_window.hwnd(); - #[cfg(target_os = "linux")] - let parent_window = main_window.gtk_window(); - - let child_window_builder = WindowBuilder::new().with_inner_size(LogicalSize::new(200, 200)); - - #[cfg(any(target_os = "windows", target_os = "macos"))] - let child_window_builder = child_window_builder.with_parent_window(parent_window); - - #[cfg(target_os = "linux")] - let child_window_builder = child_window_builder.with_transient_for(parent_window); - - let child_window = child_window_builder.build(&event_loop).unwrap(); - - windows.insert(child_window.id(), child_window); - windows.insert(main_window.id(), main_window); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::NewEvents(StartCause::Init) => println!("TAO application started!"), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - window_id, - .. - } => { - println!("Window {window_id:?} has received the signal to close"); - // This drop the window, causing it to close. - windows.remove(&window_id); - if windows.is_empty() { - *control_flow = ControlFlow::Exit; - } - } - _ => (), - }; - }) -} - -#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] -fn main() { - println!("This platform doesn't have the parent window support."); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs deleted file mode 100644 index c526a74cc..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::{Key, ModifiersState}, - window::{ProgressBarState, ProgressState, WindowBuilder}, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - let mut modifiers = ModifiersState::default(); - - eprintln!("Key mappings:"); - eprintln!(" [1-5]: Set progress to [0%, 25%, 50%, 75%, 100%]"); - eprintln!(" Ctrl+1: Set state to None"); - eprintln!(" Ctrl+2: Set state to Normal"); - eprintln!(" Ctrl+3: Set state to Indeterminate"); - eprintln!(" Ctrl+4: Set state to Paused"); - eprintln!(" Ctrl+5: Set state to Error"); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - Event::WindowEvent { event, .. } => match event { - WindowEvent::ModifiersChanged(new_state) => { - modifiers = new_state; - } - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Character(key_str), - state: ElementState::Released, - .. - }, - .. - } => { - if modifiers.is_empty() { - let mut progress: u64 = 0; - match key_str { - "1" => progress = 0, - "2" => progress = 25, - "3" => progress = 50, - "4" => progress = 75, - "5" => progress = 100, - _ => {} - } - - window.set_progress_bar(ProgressBarState { - progress: Some(progress), - state: Some(ProgressState::Normal), - desktop_filename: None, - }); - } else if modifiers.control_key() { - let mut state = ProgressState::None; - match key_str { - "1" => state = ProgressState::None, - "2" => state = ProgressState::Normal, - "3" => state = ProgressState::Indeterminate, - "4" => state = ProgressState::Paused, - "5" => state = ProgressState::Error, - _ => {} - } - - window.set_progress_bar(ProgressBarState { - progress: None, - state: Some(state), - desktop_filename: None, - }); - } - } - _ => {} - }, - _ => {} - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs deleted file mode 100644 index 5b14ef4a2..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::Window, -}; - -#[allow(clippy::single_match)] -fn main() { - let event_loop = EventLoop::new(); - - let mut window = Some(Window::new(&event_loop).unwrap()); - - event_loop.run(move |event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - // drop the window - window = None; - } - Event::Reopen { - has_visible_windows, - .. - } => { - println!("on reopen, has visible windows: {has_visible_windows}"); - if !has_visible_windows { - window = Some(Window::new(event_loop).unwrap()) - } - } - Event::MainEventsCleared => { - if let Some(w) = &window { - w.request_redraw(); - } - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs deleted file mode 100644 index f3f563c6f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{ElementState, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - println!("{event:?}"); - - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::MouseInput { - state: ElementState::Released, - .. - } => { - window.request_redraw(); - } - _ => (), - }, - Event::RedrawRequested(_) => { - println!("\nredrawing!\n"); - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs deleted file mode 100644 index 069d65d52..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{thread, time}; - -use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -#[allow(clippy::collapsible_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - thread::spawn(move || loop { - thread::sleep(time::Duration::from_secs(1)); - window.request_redraw(); - }); - - event_loop.run(move |event, _, control_flow| { - println!("{event:?}"); - - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => (), - }, - Event::RedrawRequested(_) => { - println!("\nredrawing!\n"); - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs deleted file mode 100644 index e0d8c16f2..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - dpi::LogicalSize, - event::{ElementState, Event, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - keyboard::KeyCode, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let mut resizable = false; - - let window = WindowBuilder::new() - .with_title("Hit space to toggle resizability.") - .with_inner_size(LogicalSize::new(400.0, 200.0)) - .with_resizable(resizable) - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - event: - KeyEvent { - physical_key: KeyCode::Space, - state: ElementState::Released, - .. - }, - .. - } => { - resizable = !resizable; - println!("Resizable: {resizable}"); - window.set_resizable(resizable); - } - _ => (), - }, - _ => (), - }; - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs deleted file mode 100644 index 2310f5c5a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - dpi::PhysicalPosition, - event::{ElementState, Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new().build(&event_loop).unwrap(); - window.set_title("A fantastic window!"); - - println!("Ime position will system default"); - println!("Click to set ime position to cursor's"); - - let mut cursor_position = PhysicalPosition::new(0.0, 0.0); - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { - event: WindowEvent::CursorMoved { position, .. }, - .. - } => { - cursor_position = position; - } - Event::WindowEvent { - event: - WindowEvent::MouseInput { - state: ElementState::Released, - .. - }, - .. - } => { - println!( - "Setting ime position to {}, {}", - cursor_position.x, cursor_position.y - ); - window.set_ime_position(cursor_position); - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs deleted file mode 100644 index c0e3284b9..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{event::KeyEvent, keyboard::KeyCode}; - -fn main() { - use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::{Theme, WindowBuilder}, - }; - - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - // .with_theme(Some(tao::window::Theme::Light)) - .build(&event_loop) - .unwrap(); - - println!("Initial theme: {:?}", window.theme()); - println!("Press D for Dark Mode"); - println!("Press L for Light Mode"); - println!("Press A for Auto Mode"); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - if let Event::WindowEvent { event, .. } = event { - match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::KeyboardInput { - event: KeyEvent { physical_key, .. }, - .. - } => match physical_key { - KeyCode::KeyD => window.set_theme(Some(Theme::Dark)), - KeyCode::KeyL => window.set_theme(Some(Theme::Light)), - KeyCode::KeyA => window.set_theme(None), - _ => {} - }, - WindowEvent::ThemeChanged(theme) => { - println!("Theme is changed: {theme:?}") - } - _ => (), - } - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs deleted file mode 100644 index eb3ce6f38..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::time::{Duration, Instant}; - -use tao::{ - event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let _window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - let timer_length = Duration::new(1, 0); - - event_loop.run(move |event, _, control_flow| { - println!("{event:?}"); - - match event { - Event::NewEvents(StartCause::Init) => { - *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length) - } - Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { - *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length); - println!("\nTimer\n"); - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs deleted file mode 100644 index 22c99c9a1..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(windows)] -use std::{num::NonZeroU32, rc::Rc}; - -use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_decorations(false) - .with_transparent(true) - .build(&event_loop) - .unwrap(); - - #[cfg(windows)] - let (window, _context, mut surface) = { - let window = Rc::new(window); - let context = softbuffer::Context::new(window.clone()).unwrap(); - let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); - (window, context, surface) - }; - - window.set_title("A fantastic window!"); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - println!("{event:?}"); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - - #[cfg(windows)] - Event::RedrawRequested(_) => { - let (width, height) = { - let size = window.inner_size(); - (size.width, size.height) - }; - surface - .resize( - NonZeroU32::new(width).unwrap(), - NonZeroU32::new(height).unwrap(), - ) - .unwrap(); - - let mut buffer = surface.buffer_mut().unwrap(); - buffer.fill(0); - buffer.present().unwrap(); - } - - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs deleted file mode 100644 index d40b3f6e3..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::event_loop::EventLoop; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - let monitor = match event_loop.primary_monitor() { - Some(monitor) => monitor, - None => { - println!("No primary monitor detected."); - return; - } - }; - - println!("Listing available video modes:"); - - for mode in monitor.video_modes() { - println!("{mode}"); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs deleted file mode 100644 index c4804fc4c..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -#[allow(clippy::single_match)] -fn main() { - let event_loop = EventLoop::new(); - - let mut window = Some( - WindowBuilder::new() - .with_title("A fantastic window!") - .with_inner_size(tao::dpi::LogicalSize::new(300.0, 300.0)) - .with_min_inner_size(tao::dpi::LogicalSize::new(200.0, 200.0)) - .build(&event_loop) - .unwrap(), - ); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - println!("{event:?}"); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - window_id: _, - .. - } => { - // drop the window to fire the `Destroyed` event - window = None; - } - Event::WindowEvent { - event: WindowEvent::Destroyed, - window_id: _, - .. - } => { - *control_flow = ControlFlow::Exit; - } - Event::MainEventsCleared => { - if let Some(w) = &window { - w.request_redraw(); - } - } - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs deleted file mode 100644 index 1bb04c478..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -// This example is used by developers to test various window functions. - -use tao::{ - dpi::{LogicalSize, PhysicalSize}, - event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, - event_loop::{ControlFlow, DeviceEventFilter, EventLoop}, - keyboard::{Key, KeyCode}, - window::{Fullscreen, WindowBuilder}, -}; - -#[allow(clippy::single_match)] -#[allow(clippy::collapsible_match)] -fn main() { - env_logger::init(); - let event_loop = EventLoop::new(); - event_loop.set_device_event_filter(DeviceEventFilter::Never); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .with_inner_size(LogicalSize::new(100.0, 100.0)) - .build(&event_loop) - .unwrap(); - - eprintln!("debugging keys:"); - eprintln!(" (E) Enter exclusive fullscreen"); - eprintln!(" (F) Toggle borderless fullscreen"); - eprintln!(" (P) Toggle borderless fullscreen on system's preferred monitor"); - eprintln!(" (V) Toggle visibility"); - eprintln!(" (T) Toggle always on top"); - eprintln!(" (B) Toggle always on bottom"); - eprintln!(" (C) Toggle content protection"); - eprintln!(" (R) Toggle resizable"); - eprintln!(" (M) Toggle minimized"); - eprintln!(" (X) Toggle maximized"); - eprintln!(" (Q) Quit event loop"); - eprintln!(" (Shift + M) Toggle minimizable"); - eprintln!(" (Shift + X) Toggle maximizable"); - eprintln!(" (Shift + Q) Toggle closable"); - - let mut always_on_bottom = false; - let mut always_on_top = false; - let mut visible = true; - let mut content_protection = false; - let mut resizable = false; - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - // This used to use the virtual key, but the new API - // only provides the `physical_key` (`Code`). - Event::DeviceEvent { - event: - DeviceEvent::Key(RawKeyEvent { - physical_key, - state: ElementState::Released, - .. - }), - .. - } => match physical_key { - KeyCode::KeyM => { - if window.is_minimized() { - window.set_minimized(false); - window.set_focus() - } - } - KeyCode::KeyV => { - if !visible { - visible = !visible; - window.set_visible(visible); - } - } - _ => (), - }, - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - event: - KeyEvent { - logical_key: Key::Character(key_str), - state: ElementState::Released, - .. - }, - .. - }, - .. - } => match key_str { - // WARNING: Consider using `key_without_modifers()` if available on your platform. - // See the `key_binding` example - "e" => { - fn area(size: PhysicalSize) -> u32 { - size.width * size.height - } - - let monitor = window.current_monitor().unwrap(); - if let Some(mode) = monitor - .video_modes() - .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) - { - window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); - } else { - eprintln!("no video modes available"); - } - } - "f" => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - let monitor = window.current_monitor(); - window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); - } - } - "p" => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - window.set_fullscreen(Some(Fullscreen::Borderless(None))); - } - } - "r" => { - resizable = !resizable; - window.set_resizable(resizable); - println!("Resizable: {resizable}"); - } - "m" => { - window.set_minimized(!window.is_minimized()); - } - "q" => { - *control_flow = ControlFlow::Exit; - } - "v" => { - visible = !visible; - window.set_visible(visible); - } - "x" => { - window.set_maximized(!window.is_maximized()); - } - "t" => { - always_on_top = !always_on_top; - window.set_always_on_top(always_on_top); - } - "b" => { - always_on_bottom = !always_on_bottom; - window.set_always_on_bottom(always_on_bottom); - } - "c" => { - content_protection = !content_protection; - window.set_content_protection(content_protection); - } - "M" => { - let minimizable = !window.is_minimizable(); - window.set_minimizable(minimizable); - } - "X" => { - let maximizable = !window.is_maximizable(); - window.set_maximizable(maximizable); - } - "Q" => { - let closable = !window.is_closable(); - window.set_closable(closable); - } - _ => (), - }, - Event::WindowEvent { - event: WindowEvent::CloseRequested, - window_id, - .. - } if window_id == window.id() => *control_flow = ControlFlow::Exit, - _ => (), - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs deleted file mode 100644 index bc5c4c610..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -extern crate image; -use std::path::Path; - -use tao::{ - event::Event, - event_loop::{ControlFlow, EventLoop}, - window::{Icon, WindowBuilder}, -}; - -#[allow(clippy::single_match)] -fn main() { - env_logger::init(); - - // You'll have to choose an icon size at your own discretion. On Linux, the icon should be - // provided in whatever size it was naturally drawn; that is, don’t scale the image before passing - // it to Tao. But on Windows, you will have to account for screen scaling. Here we use 32px, - // since it seems to work well enough in most cases. Be careful about going too high, or - // you'll be bitten by the low-quality downscaling built into the WM. - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png"); - - let icon = load_icon(Path::new(path)); - - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("An iconic window!") - // At present, this only does anything on Windows and Linux, so if you want to save load - // time, you can put icon loading behind a function that returns `None` on other platforms. - .with_window_icon(Some(icon)) - .build(&event_loop) - .unwrap(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - if let Event::WindowEvent { event, .. } = event { - use tao::event::WindowEvent::*; - match event { - CloseRequested => *control_flow = ControlFlow::Exit, - DroppedFile(path) => { - window.set_window_icon(Some(load_icon(&path))); - } - _ => (), - } - } - }); -} - -fn load_icon(path: &Path) -> Icon { - let (icon_rgba, icon_width, icon_height) = { - // alternatively, you can embed the icon in the binary through `include_bytes!` macro and use `image::load_from_memory` - let image = image::open(path) - .expect("Failed to open icon path") - .into_rgba8(); - let (width, height) = image.dimensions(); - let rgba = image.into_raw(); - (rgba, width, height) - }; - Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs deleted file mode 100644 index c4754bb63..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -// Limit this example to only compatible platforms. -#[cfg(not(target_os = "ios"))] -#[allow(clippy::single_match)] -fn main() { - use std::{thread::sleep, time::Duration}; - - use tao::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - platform::run_return::EventLoopExtRunReturn, - window::WindowBuilder, - }; - let mut event_loop = EventLoop::new(); - - env_logger::init(); - let _window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - let mut quit = false; - - while !quit { - event_loop.run_return(|event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - if let Event::WindowEvent { event, .. } = &event { - // Print only Window events to reduce noise - println!("{event:?}"); - } - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - quit = true; - } - Event::MainEventsCleared => { - *control_flow = ControlFlow::Exit; - } - _ => (), - } - }); - - // Sleep for 1/60 second to simulate rendering - println!("rendering"); - sleep(Duration::from_millis(16)); - } -} - -#[cfg(target_os = "ios")] -fn main() { - println!("This platform doesn't support run_return."); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs deleted file mode 100644 index 3bc21cedc..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! The `Error` struct and associated types. -use std::{error, fmt}; - -use crate::platform_impl; - -/// An error whose cause it outside Tao's control. -#[non_exhaustive] -#[derive(Debug)] -pub enum ExternalError { - /// The operation is not supported by the backend. - NotSupported(NotSupportedError), - /// The OS cannot perform the operation. - Os(OsError), -} - -/// The error type for when the requested operation is not supported by the backend. -#[derive(Clone)] -pub struct NotSupportedError { - _marker: (), -} - -/// The error type for when the OS cannot perform the requested operation. -#[derive(Debug)] -pub struct OsError { - line: u32, - file: &'static str, - error: platform_impl::OsError, -} - -impl NotSupportedError { - #[inline] - #[allow(dead_code)] - pub(crate) fn new() -> NotSupportedError { - NotSupportedError { _marker: () } - } -} - -impl OsError { - #[allow(dead_code)] - pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError { - OsError { line, file, error } - } -} - -#[allow(unused_macros)] -macro_rules! os_error { - ($error:expr) => {{ - crate::error::OsError::new(line!(), file!(), $error) - }}; -} - -impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.pad(&format!( - "os error at {}:{}: {}", - self.file, self.line, self.error - )) - } -} - -impl fmt::Display for ExternalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - ExternalError::NotSupported(e) => e.fmt(f), - ExternalError::Os(e) => e.fmt(f), - } - } -} - -impl fmt::Debug for NotSupportedError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.debug_struct("NotSupportedError").finish() - } -} - -impl fmt::Display for NotSupportedError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.pad("the requested operation is not supported by Tao") - } -} - -impl error::Error for OsError {} -impl error::Error for ExternalError {} -impl error::Error for NotSupportedError {} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs deleted file mode 100644 index d72af30ba..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs +++ /dev/null @@ -1,964 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! The `Event` enum and assorted supporting types. -//! -//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get -//! processed and used to modify the program state. For more details, see the root-level documentation. -//! -//! Some of these events represent different "parts" of a traditional event-handling loop. You could -//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this: -//! -//! ```rust,ignore -//! let mut control_flow = ControlFlow::Poll; -//! let mut start_cause = StartCause::Init; -//! -//! while control_flow != ControlFlow::Exit { -//! event_handler(NewEvents(start_cause), ..., &mut control_flow); -//! -//! for e in (window events, user events, device events) { -//! event_handler(e, ..., &mut control_flow); -//! } -//! event_handler(MainEventsCleared, ..., &mut control_flow); -//! -//! for w in (redraw windows) { -//! event_handler(RedrawRequested(w), ..., &mut control_flow); -//! } -//! event_handler(RedrawEventsCleared, ..., &mut control_flow); -//! -//! start_cause = wait_if_necessary(control_flow); -//! } -//! -//! event_handler(LoopDestroyed, ..., &mut control_flow); -//! ``` -//! -//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully -//! describes what happens in what order. -//! -//! [event_loop_run]: crate::event_loop::EventLoop::run -use std::{path::PathBuf, time::Instant}; - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - keyboard::{self, ModifiersState}, - platform_impl, - window::{Theme, WindowId}, -}; - -/// Describes a generic event. -/// -/// See the module-level docs for more information on the event loop manages each event. -#[non_exhaustive] -#[derive(Debug, PartialEq)] -pub enum Event<'a, T: 'static> { - /// Emitted when new events arrive from the OS to be processed. - /// - /// This event type is useful as a place to put code that should be done before you start - /// processing events, such as updating frame timing information for benchmarking or checking - /// the [`StartCause`][crate::event::StartCause] to see if a timer set by - /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. - NewEvents(StartCause), - - /// Emitted when the OS sends an event to a tao window. - #[non_exhaustive] - WindowEvent { - window_id: WindowId, - event: WindowEvent<'a>, - }, - - /// Emitted when the OS sends an event to a device. - #[non_exhaustive] - DeviceEvent { - device_id: DeviceId, - event: DeviceEvent, - }, - - /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) - UserEvent(T), - - /// Emitted when the application has been suspended. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onPause` method of the Activity. - /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Suspended, - - /// Emitted when the application has been resumed. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. - /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Resumed, - - /// Emitted when all of the event loop's input events have been processed and redraw processing - /// is about to begin. - /// - /// This event is useful as a place to put your code that should be run after all - /// state-changing events have been handled and you want to do stuff (updating state, performing - /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws - /// graphics when something changes, it's usually better to do it in response to - /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted - /// immediately after this event. Programs that draw graphics continuously, like most games, - /// can render here unconditionally for simplicity. - MainEventsCleared, - - /// Emitted after `MainEventsCleared` when a window should be redrawn. - /// - /// This gets triggered in two scenarios: - /// - The OS has performed an operation that's invalidated the window's contents (such as - /// resizing the window). - /// - The application has explicitly requested a redraw via - /// [`Window::request_redraw`](crate::window::Window::request_redraw). - /// - /// During each iteration of the event loop, Tao will aggregate duplicate redraw requests - /// into a single event, to help avoid duplicating rendering work. - /// - /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless - /// something changes, like most non-game GUIs. - /// - /// ## Platform-specific - /// - /// - **Linux: This is triggered by `draw` signal of the gtk window. It can be used to detect if - /// the window is requested to redraw. But widgets it contains are usually not tied to its signal. - /// So if you really want to draw each component, please consider using `connect_draw` method - /// from [`WidgetExt`] directly.** - /// - /// [`WidgetExt`]: https://gtk-rs.org/gtk3-rs/stable/latest/docs/gtk/prelude/trait.WidgetExt.html - RedrawRequested(WindowId), - - /// Emitted after all `RedrawRequested` events have been processed and control flow is about to - /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted - /// immediately after `MainEventsCleared`. - /// - /// This event is useful for doing any cleanup or bookkeeping work after all the rendering - /// tasks have been completed. - RedrawEventsCleared, - - /// Emitted when the event loop is being shut down. - /// - /// This is irreversable - if this event is emitted, it is guaranteed to be the last event that - /// gets emitted. You generally want to treat this as an "do on quit" event. - LoopDestroyed, - - /// Emitted when the app is open by external resources, like opening a file or deeplink. - Opened { urls: Vec }, - - /// ## Platform-specific - /// - /// - **macOS**: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen with return value same as hasVisibleWindows - /// - **Other**: Unsupported. - #[non_exhaustive] - Reopen { has_visible_windows: bool }, - - /// Emitted when a scene is requested by the system. - /// - /// This event is emitted when a scene is requested by the system. - /// Scenes created by `Window::new` are not emitted with this event. - /// It is also not emitted for the main scene. - #[cfg(target_os = "ios")] - SceneRequested { - /// Scene that was requested by the system. - scene: objc2::rc::Retained, - /// Options that were used to request the scene. - /// - /// This lets you determine why the scene was requested. - options: objc2::rc::Retained, - }, -} - -impl Clone for Event<'static, T> { - fn clone(&self) -> Self { - use self::Event::*; - match self { - WindowEvent { window_id, event } => WindowEvent { - window_id: *window_id, - event: event.clone(), - }, - UserEvent(event) => UserEvent(event.clone()), - DeviceEvent { device_id, event } => DeviceEvent { - device_id: *device_id, - event: event.clone(), - }, - NewEvents(cause) => NewEvents(*cause), - MainEventsCleared => MainEventsCleared, - RedrawRequested(wid) => RedrawRequested(*wid), - RedrawEventsCleared => RedrawEventsCleared, - LoopDestroyed => LoopDestroyed, - Suspended => Suspended, - Resumed => Resumed, - Opened { urls } => Opened { urls: urls.clone() }, - Reopen { - has_visible_windows, - } => Reopen { - has_visible_windows: *has_visible_windows, - }, - #[cfg(target_os = "ios")] - SceneRequested { scene, options } => SceneRequested { - scene: scene.clone(), - options: options.clone(), - }, - } - } -} - -impl<'a, T> Event<'a, T> { - pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { - use self::Event::*; - match self { - UserEvent(_) => Err(self), - WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), - DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), - NewEvents(cause) => Ok(NewEvents(cause)), - MainEventsCleared => Ok(MainEventsCleared), - RedrawRequested(wid) => Ok(RedrawRequested(wid)), - RedrawEventsCleared => Ok(RedrawEventsCleared), - LoopDestroyed => Ok(LoopDestroyed), - Suspended => Ok(Suspended), - Resumed => Ok(Resumed), - Opened { urls } => Ok(Opened { urls }), - Reopen { - has_visible_windows, - } => Ok(Reopen { - has_visible_windows, - }), - #[cfg(target_os = "ios")] - SceneRequested { scene, options } => Ok(SceneRequested { scene, options }), - } - } - - /// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime. - /// Otherwise, return `None`. - pub fn to_static(self) -> Option> { - use self::Event::*; - match self { - WindowEvent { window_id, event } => event - .to_static() - .map(|event| WindowEvent { window_id, event }), - UserEvent(event) => Some(UserEvent(event)), - DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), - NewEvents(cause) => Some(NewEvents(cause)), - MainEventsCleared => Some(MainEventsCleared), - RedrawRequested(wid) => Some(RedrawRequested(wid)), - RedrawEventsCleared => Some(RedrawEventsCleared), - LoopDestroyed => Some(LoopDestroyed), - Suspended => Some(Suspended), - Resumed => Some(Resumed), - Opened { urls } => Some(Opened { urls }), - Reopen { - has_visible_windows, - } => Some(Reopen { - has_visible_windows, - }), - #[cfg(target_os = "ios")] - SceneRequested { scene, options } => Some(SceneRequested { scene, options }), - } - } -} - -/// Describes the reason the event loop is resuming. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[non_exhaustive] -pub enum StartCause { - /// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the - /// moment the timeout was requested and the requested resume time. The actual resume time is - /// guaranteed to be equal to or after the requested resume time. - #[non_exhaustive] - ResumeTimeReached { - start: Instant, - requested_resume: Instant, - }, - - /// Sent if the OS has new events to send to the window, after a wait was requested. Contains - /// the moment the wait was requested and the resume time, if requested. - #[non_exhaustive] - WaitCancelled { - start: Instant, - requested_resume: Option, - }, - - /// Sent if the event loop is being resumed after the loop's control flow was set to - /// `ControlFlow::Poll`. - Poll, - - /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. - Init, -} - -/// Describes an event from a `Window`. -#[non_exhaustive] -#[derive(Debug, PartialEq)] -pub enum WindowEvent<'a> { - /// The size of the window has changed. Contains the client area's new dimensions. - Resized(PhysicalSize), - - /// The position of the window has changed. Contains the window's new position. - /// - /// ## Platform-specific - /// - /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global cordinate system. - Moved(PhysicalPosition), - - /// The window has been requested to close. - CloseRequested, - - /// The window has been destroyed. - /// - /// ## Platform-specific - /// - /// - **Windows / Linux:** Only fired if the [`crate::window::Window`] is dropped. - /// - **macOS:** Fired if the [`crate::window::Window`] is dropped or the dock `Quit` item is clicked. - Destroyed, - - /// A file has been dropped into the window. - /// - /// When the user drops multiple files at once, this event will be emitted for each file - /// separately. - DroppedFile(PathBuf), - - /// A file is being hovered over the window. - /// - /// When the user hovers multiple files at once, this event will be emitted for each file - /// separately. - HoveredFile(PathBuf), - - /// A file was hovered, but has exited the window. - /// - /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were - /// hovered. - HoveredFileCancelled, - - /// The window received a unicode character. - ReceivedImeText(String), - - /// The window gained or lost focus. - /// - /// The parameter is true if the window has gained focus, and false if it has lost focus. - Focused(bool), - - /// An event from the keyboard has been received. - /// - /// ## Platform-specific - /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, - /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key - /// events which are not marked as `is_synthetic`. - #[non_exhaustive] - KeyboardInput { - device_id: DeviceId, - event: KeyEvent, - - /// If `true`, the event was generated synthetically by tao - /// in one of the following circumstances: - /// - /// * Synthetic key press events are generated for all keys pressed - /// when a window gains focus. Likewise, synthetic key release events - /// are generated for all keys pressed when a window goes out of focus. - /// ***Currently, this is only functional on Linux and Windows*** - /// - /// Otherwise, this value is always `false`. - is_synthetic: bool, - }, - - /// The keyboard modifiers have changed. - ModifiersChanged(ModifiersState), - - /// The cursor has moved on the window. - CursorMoved { - device_id: DeviceId, - - /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is - /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor - /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. - position: PhysicalPosition, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, - }, - - /// The cursor has entered the window. - CursorEntered { device_id: DeviceId }, - - /// The cursor has left the window. - CursorLeft { device_id: DeviceId }, - - /// A mouse wheel movement or touchpad scroll occurred. - MouseWheel { - device_id: DeviceId, - delta: MouseScrollDelta, - phase: TouchPhase, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, - }, - - /// An mouse button press has been received. - MouseInput { - device_id: DeviceId, - state: ElementState, - button: MouseButton, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, - }, - - /// Touchpad pressure event. - /// - /// At the moment, only supported on Apple forcetouch-capable macbooks. - /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad - /// is being pressed) and stage (integer representing the click level). - TouchpadPressure { - device_id: DeviceId, - pressure: f32, - stage: i64, - }, - - /// Motion on some analog axis. May report data redundant to other, more specific events. - AxisMotion { - device_id: DeviceId, - axis: AxisId, - value: f64, - }, - - /// Touch event has been received - Touch(Touch), - - /// The window's scale factor has changed. - /// - /// The following user actions can cause DPI changes: - /// - /// * Changing the display's resolution. - /// * Changing the display's scale factor (e.g. in Control Panel on Windows). - /// * Moving the window to a display with a different scale factor. - /// - /// After this event callback has been processed, the window will be resized to whatever value - /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested - /// by the OS, but it can be changed to any value. - /// - /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. - ScaleFactorChanged { - scale_factor: f64, - new_inner_size: &'a mut PhysicalSize, - }, - - /// The system window theme has changed. - /// - /// Applications might wish to react to this to change the theme of the content of the window - /// when the system changes the window theme. - /// - /// ## Platform-specific - /// - /// - **Android / iOS:** Unsupported - ThemeChanged(Theme), - - /// The window decorations has been clicked. - /// - /// ## Platform-specific - /// - /// - **Linux / macOS / Android / iOS:** Unsupported - DecorationsClick, -} - -impl Clone for WindowEvent<'static> { - fn clone(&self) -> Self { - use self::WindowEvent::*; - match self { - Resized(size) => Resized(*size), - Moved(pos) => Moved(*pos), - CloseRequested => CloseRequested, - Destroyed => Destroyed, - DroppedFile(file) => DroppedFile(file.clone()), - HoveredFile(file) => HoveredFile(file.clone()), - HoveredFileCancelled => HoveredFileCancelled, - ReceivedImeText(c) => ReceivedImeText(c.clone()), - Focused(f) => Focused(*f), - KeyboardInput { - device_id, - event, - is_synthetic, - } => KeyboardInput { - device_id: *device_id, - event: event.clone(), - is_synthetic: *is_synthetic, - }, - - ModifiersChanged(modifiers) => ModifiersChanged(*modifiers), - #[allow(deprecated)] - CursorMoved { - device_id, - position, - modifiers, - } => CursorMoved { - device_id: *device_id, - position: *position, - modifiers: *modifiers, - }, - CursorEntered { device_id } => CursorEntered { - device_id: *device_id, - }, - CursorLeft { device_id } => CursorLeft { - device_id: *device_id, - }, - #[allow(deprecated)] - MouseWheel { - device_id, - delta, - phase, - modifiers, - } => MouseWheel { - device_id: *device_id, - delta: *delta, - phase: *phase, - modifiers: *modifiers, - }, - #[allow(deprecated)] - MouseInput { - device_id, - state, - button, - modifiers, - } => MouseInput { - device_id: *device_id, - state: *state, - button: *button, - modifiers: *modifiers, - }, - TouchpadPressure { - device_id, - pressure, - stage, - } => TouchpadPressure { - device_id: *device_id, - pressure: *pressure, - stage: *stage, - }, - AxisMotion { - device_id, - axis, - value, - } => AxisMotion { - device_id: *device_id, - axis: *axis, - value: *value, - }, - Touch(touch) => Touch(*touch), - ThemeChanged(theme) => ThemeChanged(*theme), - ScaleFactorChanged { .. } => { - unreachable!("Static event can't be about scale factor changing") - } - DecorationsClick => DecorationsClick, - } - } -} - -impl<'a> WindowEvent<'a> { - pub fn to_static(self) -> Option> { - use self::WindowEvent::*; - match self { - Resized(size) => Some(Resized(size)), - Moved(position) => Some(Moved(position)), - CloseRequested => Some(CloseRequested), - Destroyed => Some(Destroyed), - DroppedFile(file) => Some(DroppedFile(file)), - HoveredFile(file) => Some(HoveredFile(file)), - HoveredFileCancelled => Some(HoveredFileCancelled), - ReceivedImeText(c) => Some(ReceivedImeText(c)), - Focused(focused) => Some(Focused(focused)), - KeyboardInput { - device_id, - event, - is_synthetic, - } => Some(KeyboardInput { - device_id, - event, - is_synthetic, - }), - ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), - #[allow(deprecated)] - CursorMoved { - device_id, - position, - modifiers, - } => Some(CursorMoved { - device_id, - position, - modifiers, - }), - CursorEntered { device_id } => Some(CursorEntered { device_id }), - CursorLeft { device_id } => Some(CursorLeft { device_id }), - #[allow(deprecated)] - MouseWheel { - device_id, - delta, - phase, - modifiers, - } => Some(MouseWheel { - device_id, - delta, - phase, - modifiers, - }), - #[allow(deprecated)] - MouseInput { - device_id, - state, - button, - modifiers, - } => Some(MouseInput { - device_id, - state, - button, - modifiers, - }), - TouchpadPressure { - device_id, - pressure, - stage, - } => Some(TouchpadPressure { - device_id, - pressure, - stage, - }), - AxisMotion { - device_id, - axis, - value, - } => Some(AxisMotion { - device_id, - axis, - value, - }), - Touch(touch) => Some(Touch(touch)), - ThemeChanged(theme) => Some(ThemeChanged(theme)), - ScaleFactorChanged { .. } => None, - DecorationsClick => Some(DecorationsClick), - } - } -} - -/// Identifier of an input device. -/// -/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which -/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or -/// physical. Virtual devices typically aggregate inputs from multiple physical devices. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(pub(crate) platform_impl::DeviceId); - -impl DeviceId { - /// # Safety - /// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return - /// value of this function is that it will always be equal to itself and to future values returned - /// by this function. No other guarantees are made. This may be equal to a real `DeviceId`. - /// - /// **Passing this into a tao function will result in undefined behavior.** - pub unsafe fn dummy() -> Self { - DeviceId(platform_impl::DeviceId::dummy()) - } -} - -/// Represents raw hardware events that are not associated with any particular window. -/// -/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person -/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because -/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs -/// may not match. -/// -/// Note that these events are delivered regardless of input focus. -#[non_exhaustive] -#[derive(Clone, Debug, PartialEq)] -pub enum DeviceEvent { - Added, - Removed, - - /// Change in physical position of a pointing device. - /// - /// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`. - #[non_exhaustive] - MouseMotion { - /// (x, y) change in position in unspecified units. - /// - /// Different devices may use different units. - delta: (f64, f64), - }, - - /// Physical scroll event - #[non_exhaustive] - MouseWheel { - delta: MouseScrollDelta, - }, - - /// Motion on some analog axis. This event will be reported for all arbitrary input devices - /// that tao supports on this platform, including mouse devices. If the device is a mouse - /// device then this will be reported alongside the MouseMotion event. - #[non_exhaustive] - Motion { - axis: AxisId, - value: f64, - }, - - #[non_exhaustive] - Button { - button: ButtonId, - state: ElementState, - }, - - Key(RawKeyEvent), - - #[non_exhaustive] - Text { - codepoint: char, - }, -} - -/// Describes a keyboard input as a raw device event. -/// -/// Note that holding down a key may produce repeated `RawKeyEvent`s. The -/// operating system doesn't provide information whether such an event is a -/// repeat or the initial keypress. An application may emulate this by, for -/// example keeping a Map/Set of pressed keys and determining whether a keypress -/// corresponds to an already pressed key. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct RawKeyEvent { - pub physical_key: keyboard::KeyCode, - pub state: ElementState, -} - -/// Describes a keyboard input targeting a window. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct KeyEvent { - /// Represents the position of a key independent of the currently active layout. - /// - /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). - /// The most prevalent use case for this is games. For example the default keys for the player - /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys - /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" - /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) - /// - /// Note that `Fn` and `FnLock` key events are not guaranteed to be emitted by `tao`. These - /// keys are usually handled at the hardware or OS level. - pub physical_key: keyboard::KeyCode, - - /// This value is affected by all modifiers except Ctrl. - /// - /// This has two use cases: - /// - Allows querying whether the current input is a Dead key. - /// - Allows handling key-bindings on platforms which don't - /// support `key_without_modifiers`. - /// - /// ## Platform-specific - /// - **Web:** Dead keys might be reported as the real key instead - /// of `Dead` depending on the browser/OS. - pub logical_key: keyboard::Key<'static>, - - /// Contains the text produced by this keypress. - /// - /// In most cases this is identical to the content - /// of the `Character` variant of `logical_key`. - /// However, on Windows when a dead key was pressed earlier - /// but cannot be combined with the character from this - /// keypress, the produced text will consist of two characters: - /// the dead-key-character followed by the character resulting - /// from this keypress. - /// - /// An additional difference from `logical_key` is that - /// this field stores the text representation of any key - /// that has such a representation. For example when - /// `logical_key` is `Key::Enter`, this field is `Some("\r")`. - /// - /// This is `None` if the current keypress cannot - /// be interpreted as text. - /// - /// See also: `text_with_all_modifiers()` - pub text: Option<&'static str>, - - pub location: keyboard::KeyLocation, - pub state: ElementState, - pub repeat: bool, - - pub(crate) platform_specific: platform_impl::KeyEventExtra, -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -impl KeyEvent { - /// Identical to `KeyEvent::text` but this is affected by Ctrl. - /// - /// For example, pressing Ctrl+a produces `Some("\x01")`. - pub fn text_with_all_modifiers(&self) -> Option<&str> { - self.platform_specific.text_with_all_modifiers - } - - /// This value ignores all modifiers including, - /// but not limited to Shift, Caps Lock, - /// and Ctrl. In most cases this means that the - /// unicode character in the resulting string is lowercase. - /// - /// This is useful for key-bindings / shortcut key combinations. - /// - /// In case `logical_key` reports `Dead`, this will still report the - /// key as `Character` according to the current keyboard layout. This value - /// cannot be `Dead`. - pub fn key_without_modifiers(&self) -> keyboard::Key<'static> { - self.platform_specific.key_without_modifiers.clone() - } -} - -#[cfg(any(target_os = "android", target_os = "ios"))] -impl KeyEvent { - /// Identical to `KeyEvent::text`. - pub fn text_with_all_modifiers(&self) -> Option<&str> { - self.text - } - - /// Identical to `KeyEvent::logical_key`. - pub fn key_without_modifiers(&self) -> keyboard::Key<'static> { - self.logical_key.clone() - } -} - -/// Describes touch-screen input state. -#[non_exhaustive] -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TouchPhase { - Started, - Moved, - Ended, - Cancelled, -} - -/// Represents a touch event -/// -/// Every time the user touches the screen, a new `Start` event with an unique -/// identifier for the finger is generated. When the finger is lifted, an `End` -/// event is generated with the same finger id. -/// -/// After a `Start` event has been emitted, there may be zero or more `Move` -/// events when the finger is moved or the touch pressure changes. -/// -/// The finger id may be reused by the system after an `End` event. The user -/// should assume that a new `Start` event received with the same id has nothing -/// to do with the old finger and is a new finger. -/// -/// A `Cancelled` event is emitted when the system has canceled tracking this -/// touch, such as when the window loses focus, or on iOS if the user moves the -/// device against their face. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Touch { - pub device_id: DeviceId, - pub phase: TouchPhase, - pub location: PhysicalPosition, - /// Describes how hard the screen was pressed. May be `None` if the platform - /// does not support pressure sensitivity. - /// - /// ## Platform-specific - /// - /// - Only available on **iOS** 9.0+ and **Windows** 8+. - pub force: Option, - /// Unique identifier of a finger. - pub id: u64, -} - -/// Describes the force of a touch event -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Force { - /// On iOS, the force is calibrated so that the same number corresponds to - /// roughly the same amount of pressure on the screen regardless of the - /// device. - #[non_exhaustive] - Calibrated { - /// The force of the touch, where a value of 1.0 represents the force of - /// an average touch (predetermined by the system, not user-specific). - /// - /// The force reported by Apple Pencil is measured along the axis of the - /// pencil. If you want a force perpendicular to the device, you need to - /// calculate this value using the `altitude_angle` value. - force: f64, - /// The maximum possible force for a touch. - /// - /// The value of this field is sufficiently high to provide a wide - /// dynamic range for values of the `force` field. - max_possible_force: f64, - /// The altitude (in radians) of the stylus. - /// - /// A value of 0 radians indicates that the stylus is parallel to the - /// surface. The value of this property is Pi/2 when the stylus is - /// perpendicular to the surface. - altitude_angle: Option, - }, - /// If the platform reports the force as normalized, we have no way of - /// knowing how much pressure 1.0 corresponds to - we know it's the maximum - /// amount of force, but as to how much force, you might either have to - /// press really really hard, or not hard at all, depending on the device. - Normalized(f64), -} - -impl Force { - /// Returns the force normalized to the range between 0.0 and 1.0 inclusive. - /// Instead of normalizing the force, you should prefer to handle - /// `Force::Calibrated` so that the amount of force the user has to apply is - /// consistent across devices. - pub fn normalized(&self) -> f64 { - match self { - Force::Calibrated { - force, - max_possible_force, - altitude_angle, - } => { - let force = match altitude_angle { - Some(altitude_angle) => force / altitude_angle.sin(), - None => *force, - }; - force / max_possible_force - } - Force::Normalized(force) => *force, - } - } -} - -/// Identifier for a specific analog axis on some device. -pub type AxisId = u32; - -/// Identifier for a specific button on some device. -pub type ButtonId = u32; - -/// Describes the input state of a key. -#[non_exhaustive] -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum ElementState { - Pressed, - Released, -} - -/// Describes a button of a mouse controller. -#[non_exhaustive] -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum MouseButton { - Left, - Right, - Middle, - Other(u16), -} - -/// Describes a difference in the mouse scroll wheel state. -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum MouseScrollDelta { - /// Amount in lines or rows to scroll in the horizontal - /// and vertical directions. - /// - /// Positive values indicate movement forward - /// (away from the user) or rightwards. - LineDelta(f32, f32), - /// Amount in pixels to scroll in the horizontal and - /// vertical direction. - /// - /// Scroll events are expressed as a PixelDelta if - /// supported by the device (eg. a touchpad) and - /// platform. - PixelDelta(PhysicalPosition), -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs deleted file mode 100644 index 6e3406d45..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! The `EventLoop` struct and assorted supporting types, including `ControlFlow`. -//! -//! If you want to send custom events to the event loop, use [`EventLoop::create_proxy()`][create_proxy] -//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method. -//! -//! See the root-level documentation for information on how to create and use an event loop to -//! handle events. -//! -//! [create_proxy]: crate::event_loop::EventLoop::create_proxy -//! [event_loop_proxy]: crate::event_loop::EventLoopProxy -//! [send_event]: crate::event_loop::EventLoopProxy::send_event -use std::{error, fmt, marker::PhantomData, ops::Deref, time::Instant}; - -use crate::{ - dpi::PhysicalPosition, - error::ExternalError, - event::Event, - monitor::MonitorHandle, - platform_impl, - window::{ProgressBarState, Theme}, -}; - -/// Provides a way to retrieve events from the system and from the windows that were registered to -/// the events loop. -/// -/// An `EventLoop` can be seen more or less as a "context". Calling `EventLoop::new()` -/// initializes everything that will be required to create windows. -/// -/// To wake up an `EventLoop` from a another thread, see the `EventLoopProxy` docs. -/// -/// Note that the `EventLoop` cannot be shared across threads (due to platform-dependant logic -/// forbidding it), as such it is neither `Send` nor `Sync`. If you need cross-thread access, the -/// `Window` created from this `EventLoop` _can_ be sent to an other thread, and the -/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread. -/// -pub struct EventLoop { - pub(crate) event_loop: platform_impl::EventLoop, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync -} - -/// Target that associates windows with an `EventLoop`. -/// -/// This type exists to allow you to create new windows while Tao executes -/// your callback. `EventLoop` will coerce into this type (`impl Deref for -/// EventLoop`), so functions that take this as a parameter can also take -/// `&EventLoop`. -#[derive(Clone)] -pub struct EventLoopWindowTarget { - pub(crate) p: platform_impl::EventLoopWindowTarget, - pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync -} - -impl fmt::Debug for EventLoop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("EventLoop { .. }") - } -} - -impl fmt::Debug for EventLoopWindowTarget { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("EventLoopWindowTarget { .. }") - } -} - -/// Object that allows building the event loop. -/// -/// This is used to make specifying options that affect the whole application -/// easier. But note that constructing multiple event loops is not supported. -#[derive(Default)] -pub struct EventLoopBuilder { - pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes, - _p: PhantomData, -} -impl EventLoopBuilder<()> { - /// Start building a new event loop. - #[inline] - pub fn new() -> Self { - Self::with_user_event() - } -} -impl EventLoopBuilder { - /// Start building a new event loop, with the given type as the user event - /// type. - #[inline] - pub fn with_user_event() -> Self { - Self { - platform_specific: Default::default(), - _p: PhantomData, - } - } - /// Builds a new event loop. - /// - /// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.*** - /// Attempting to create the event loop on a different thread will panic. This restriction isn't - /// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when - /// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed - /// in the relevant `platform` module if the target platform supports creating an event loop on - /// any thread. - /// - /// Usage will result in display backend initialisation, this can be controlled on linux - /// using an environment variable `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`. - /// If it is not set, winit will try to connect to a wayland connection, and if it fails will - /// fallback on x11. If this variable is set with any other value, winit will panic. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. - #[inline] - pub fn build(&mut self) -> EventLoop { - EventLoop { - #[cfg_attr( - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ), - allow(clippy::unnecessary_mut_passed) - )] - event_loop: platform_impl::EventLoop::new(&mut self.platform_specific), - _marker: PhantomData, - } - } -} - -/// Set by the user callback given to the `EventLoop::run` method. -/// -/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared] -/// is emitted. Defaults to `Poll`. -/// -/// ## Persistency -/// Almost every change is persistent between multiple calls to the event loop closure within a -/// given run loop. The only exception to this is `ExitWithCode` which, once set, cannot be unset. -/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will -/// reset the control flow to `Poll`. -/// -/// [events_cleared]: crate::event::Event::RedrawEventsCleared -#[non_exhaustive] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ControlFlow { - /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. - Poll, - /// When the current loop iteration finishes, suspend the thread until another event arrives. - Wait, - /// When the current loop iteration finishes, suspend the thread until either another event - /// arrives or the given time is reached. - WaitUntil(Instant), - /// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set, - /// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will - /// result in the `control_flow` parameter being reset to `ExitWithCode`. - /// - /// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this - /// with exit code 0. - /// - /// ## Platform-specific - /// - /// - **Android / iOS / WASM**: The supplied exit code is unused. - /// - **Unix**: On most Unix-like platforms, only the 8 least significant bits will be used, - /// which can cause surprises with negative exit values (`-42` would end up as `214`). See - /// [`std::process::exit`]. - /// - /// [`Exit`]: ControlFlow::Exit - ExitWithCode(i32), -} - -impl ControlFlow { - /// Alias for [`ExitWithCode`]`(0)`. - /// - /// [`ExitWithCode`]: ControlFlow::ExitWithCode - #[allow(non_upper_case_globals)] - pub const Exit: Self = Self::ExitWithCode(0); -} - -impl Default for ControlFlow { - #[inline(always)] - fn default() -> ControlFlow { - ControlFlow::Poll - } -} - -impl EventLoop<()> { - /// Alias for [`EventLoopBuilder::new().build()`]. - /// - /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build - #[inline] - pub fn new() -> EventLoop<()> { - EventLoopBuilder::new().build() - } -} - -impl Default for EventLoop<()> { - fn default() -> Self { - Self::new() - } -} - -impl EventLoop { - /// Hijacks the calling thread and initializes the tao event loop with the provided - /// closure. Since the closure is `'static`, it must be a `move` closure if it needs to - /// access any data from the calling context. - /// - /// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the - /// event loop's behavior. - /// - /// Any values not passed to this function will *not* be dropped. - /// - /// ## Platform-specific - /// - /// - **Unix**: The program terminates with exit code 1 if the display server - /// disconnects. - /// - /// [`ControlFlow`]: crate::event_loop::ControlFlow - #[inline] - pub fn run(self, event_handler: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), - { - self.event_loop.run(event_handler) - } - - /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - event_loop_proxy: self.event_loop.create_proxy(), - } - } -} - -impl Deref for EventLoop { - type Target = EventLoopWindowTarget; - fn deref(&self) -> &EventLoopWindowTarget { - self.event_loop.window_target() - } -} - -impl EventLoopWindowTarget { - /// Returns the list of all the monitors available on the system. - #[inline] - pub fn available_monitors(&self) -> impl Iterator { - self - .p - .available_monitors() - .into_iter() - .map(|inner| MonitorHandle { inner }) - } - - /// Returns the primary monitor of the system. - /// - /// Returns `None` if it can't identify any monitor as a primary one. - #[inline] - pub fn primary_monitor(&self) -> Option { - self.p.primary_monitor() - } - - /// Returns the monitor that contains the given point. - /// - /// ## Platform-specific: - /// - /// - **Android / iOS:** Unsupported. - #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - self - .p - .monitor_from_point(x, y) - .map(|inner| MonitorHandle { inner }) - } - - /// Change [`DeviceEvent`] filter mode. - /// - /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, tao - /// will ignore them by default for unfocused windows. This method allows changing - /// this filter at runtime to explicitly capture them again. - /// - /// ## Platform-specific - /// - /// - **Linux / macOS / iOS / Android:** Unsupported. - /// - /// [`DeviceEvent`]: crate::event::DeviceEvent - pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) { - #[cfg(target_os = "windows")] - self.p.set_device_event_filter(_filter); - } - - /// Returns the current cursor position - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`. - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - self.p.cursor_position() - } - - /// Sets the progress bar state - /// - /// ## Platform-specific - /// - /// - **Windows:** Unsupported. Use the Progress Bar Function Available in Window (Windows can have different progress bars for different window) - /// - **Linux:** Only supported desktop environments with `libunity` (e.g. GNOME). - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_progress_bar(&self, _progress: ProgressBarState) { - #[cfg(any(target_os = "linux", target_os = "macos"))] - self.p.set_progress_bar(_progress) - } - - /// Sets the theme for the application. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_theme(&self, _theme: Option) { - #[cfg(any( - windows, - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", - ))] - self.p.set_theme(_theme) - } -} - -#[cfg(feature = "rwh_05")] -unsafe impl rwh_05::HasRawDisplayHandle for EventLoop { - fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { - rwh_05::HasRawDisplayHandle::raw_display_handle(&**self) - } -} - -#[cfg(feature = "rwh_06")] -impl rwh_06::HasDisplayHandle for EventLoop { - fn display_handle(&self) -> Result, rwh_06::HandleError> { - rwh_06::HasDisplayHandle::display_handle(&**self) - } -} - -#[cfg(feature = "rwh_05")] -unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget { - fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { - self.p.raw_display_handle_rwh_05() - } -} - -#[cfg(feature = "rwh_06")] -impl rwh_06::HasDisplayHandle for EventLoopWindowTarget { - fn display_handle(&self) -> Result, rwh_06::HandleError> { - let raw = self.p.raw_display_handle_rwh_06()?; - // SAFETY: The display will never be deallocated while the event loop is alive. - Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) - } -} - -/// Used to send custom events to `EventLoop`. -pub struct EventLoopProxy { - event_loop_proxy: platform_impl::EventLoopProxy, -} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - Self { - event_loop_proxy: self.event_loop_proxy.clone(), - } - } -} - -impl EventLoopProxy { - /// Send an event to the `EventLoop` from which this proxy was created. This emits a - /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this - /// function. - /// - /// Returns an `Err` if the associated `EventLoop` no longer exists. - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.event_loop_proxy.send_event(event) - } -} - -impl fmt::Debug for EventLoopProxy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("EventLoopProxy { .. }") - } -} - -/// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that -/// no longer exists. Contains the original event given to `send_event`. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EventLoopClosed(pub T); - -impl fmt::Display for EventLoopClosed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Tried to wake up a closed `EventLoop`") - } -} - -impl error::Error for EventLoopClosed {} - -/// Fiter controlling the propagation of device events. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub enum DeviceEventFilter { - /// Always filter out device events. - Always, - /// Filter out device events while the window is not focused. - #[default] - Unfocused, - /// Report all device events regardless of window focus. - Never, -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs deleted file mode 100644 index 8827e77bf..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::platform_impl::PlatformIcon; -use std::{error::Error, fmt, io, mem}; - -#[repr(C)] -#[derive(Debug)] -pub(crate) struct Pixel { - pub(crate) r: u8, - pub(crate) g: u8, - pub(crate) b: u8, - pub(crate) a: u8, -} - -pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); - -#[non_exhaustive] -#[derive(Debug)] -/// An error produced when using `Icon::from_rgba` with invalid arguments. -pub enum BadIcon { - /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be - /// safely interpreted as 32bpp RGBA pixels. - #[non_exhaustive] - ByteCountNotDivisibleBy4 { byte_count: usize }, - /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. - /// At least one of your arguments is incorrect. - #[non_exhaustive] - DimensionsVsPixelCount { - width: u32, - height: u32, - width_x_height: usize, - pixel_count: usize, - }, - /// Produced when the provided icon width or height is equal to zero. - #[non_exhaustive] - DimensionsZero { width: u32, height: u32 }, - /// Produced when the provided icon width or height is equal to zero. - #[non_exhaustive] - DimensionsMultiplyOverflow { width: u32, height: u32 }, - /// Produced when underlying OS functionality failed to create the icon - OsError(io::Error), -} - -impl fmt::Display for BadIcon { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f, - "The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", - ), - BadIcon::DimensionsVsPixelCount { - width, - height, - width_x_height, - pixel_count, - } => write!(f, - "The specified dimensions ({width:?}x{height:?}) don't match the number of pixels supplied by the `rgba` argument ({pixel_count:?}). For those dimensions, the expected pixel count is {width_x_height:?}.", - ), - BadIcon::DimensionsZero { - width, - height, - } => write!(f, - "The specified dimensions ({width:?}x{height:?}) must be greater than zero." - ), - BadIcon::DimensionsMultiplyOverflow { - width, - height, - } => write!(f, - "The specified dimensions multiplication has overflowed ({width:?}x{height:?})." - ), - BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"), - } - } -} - -impl Error for BadIcon { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(self) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct RgbaIcon { - pub(crate) rgba: Vec, - pub(crate) width: u32, - pub(crate) height: u32, -} - -/// For platforms which don't have window icons (e.g. web) -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct NoIcon; - -#[allow(dead_code)] // These are not used on every platform -mod constructors { - use super::*; - - impl RgbaIcon { - /// Creates an `Icon` from 32bpp RGBA data. - /// - /// The length of `rgba` must be divisible by 4, and `width * height` must equal - /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - if width == 0 || height == 0 { - return Err(BadIcon::DimensionsZero { width, height }); - } - - if rgba.len() % PIXEL_SIZE != 0 { - return Err(BadIcon::ByteCountNotDivisibleBy4 { - byte_count: rgba.len(), - }); - } - let width_usize = width as usize; - let height_usize = height as usize; - let width_x_height = match width_usize.checked_mul(height_usize) { - Some(v) => v, - None => return Err(BadIcon::DimensionsMultiplyOverflow { width, height }), - }; - - let pixel_count = rgba.len() / PIXEL_SIZE; - if pixel_count != width_x_height { - Err(BadIcon::DimensionsVsPixelCount { - width, - height, - width_x_height, - pixel_count, - }) - } else { - Ok(RgbaIcon { - rgba, - width, - height, - }) - } - } - } - - impl NoIcon { - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - // Create the rgba icon anyway to validate the input - let _ = RgbaIcon::from_rgba(rgba, width, height)?; - Ok(NoIcon) - } - } -} - -/// An icon used for the window titlebar, taskbar, etc. -#[derive(Clone)] -pub struct Icon { - pub(crate) inner: PlatformIcon, -} - -impl fmt::Debug for Icon { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt::Debug::fmt(&self.inner, formatter) - } -} - -impl Icon { - /// Creates an `Icon` from 32bpp RGBA data. - /// - /// The length of `rgba` must be divisible by 4, and `width * height` must equal - /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - Ok(Icon { - inner: PlatformIcon::from_rgba(rgba, width, height)?, - }) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs deleted file mode 100644 index ac50d42c5..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs +++ /dev/null @@ -1,1596 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! **UNSTABLE** -- Types related to the keyboard. - -// This file contains a substantial portion of the UI Events Specification by the W3C. In -// particular, the variant names within `Key` and `KeyCode` and their documentation are modified -// versions of contents of the aforementioned specification. -// -// The original documents are: -// -// ### For `Key` -// UI Events KeyboardEvent key Values -// https://www.w3.org/TR/2017/CR-uievents-key-20170601/ -// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). -// -// ### For `KeyCode` -// UI Events KeyboardEvent code Values -// https://www.w3.org/TR/2017/CR-uievents-code-20170601/ -// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). -// -// These documents were used under the terms of the following license. This W3C license as well as -// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the -// documentation attached to their variants. - -// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- -// -// License -// -// By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, -// and will comply with the following terms and conditions. -// -// Permission to copy, modify, and distribute this work, with or without modification, for any -// purpose and without fee or royalty is hereby granted, provided that you include the following on -// ALL copies of the work or portions thereof, including modifications: -// -// - The full text of this NOTICE in a location viewable to users of the redistributed or derivative -// work. -// - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none -// exist, the W3C Software and Document Short Notice should be included. -// - Notice of any changes or modifications, through a copyright statement on the new code or -// document such as "This software or document includes material copied from or derived from -// [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." -// -// Disclaimers -// -// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR -// ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD -// PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. -// -// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES -// ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. -// -// The name and trademarks of copyright holders may NOT be used in advertising or publicity -// pertaining to the work without specific, written prior permission. Title to copyright in this -// work will at all times remain with copyright holders. -// -// --------- END OF W3C LICENSE -------------------------------------------------------------------- - -// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- -// -// tao: https://github.com/tauri-apps/tao -// -// Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European -// Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights -// Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will -// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. -// -// [1] http://www.w3.org/Consortium/Legal/copyright-software -// -// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- - -use std::{fmt, str::FromStr}; - -use crate::{ - error::OsError, - platform_impl::{ - keycode_from_scancode as platform_keycode_from_scancode, - keycode_to_scancode as platform_keycode_to_scancode, - }, -}; - -impl ModifiersState { - /// Returns `true` if the shift key is pressed. - pub fn shift_key(&self) -> bool { - self.intersects(Self::SHIFT) - } - /// Returns `true` if the control key is pressed. - pub fn control_key(&self) -> bool { - self.intersects(Self::CONTROL) - } - /// Returns `true` if the alt key is pressed. - pub fn alt_key(&self) -> bool { - self.intersects(Self::ALT) - } - /// Returns `true` if the super key is pressed. - pub fn super_key(&self) -> bool { - self.intersects(Self::SUPER) - } -} - -bitflags! { - /// Represents the current state of the keyboard modifiers - /// - /// Each flag represents a modifier and is set if this modifier is active. - #[derive(Clone, Copy, Default, Debug, PartialEq)] - pub struct ModifiersState: u32 { - // left and right modifiers are currently commented out, but we should be able to support - // them in a future release - /// The "shift" key. - const SHIFT = 0b100 << 0; - // const LSHIFT = 0b010 << 0; - // const RSHIFT = 0b001 << 0; - /// The "control" key. - const CONTROL = 0b100 << 3; - // const LCTRL = 0b010 << 3; - // const RCTRL = 0b001 << 3; - /// The "alt" key. - const ALT = 0b100 << 6; - // const LALT = 0b010 << 6; - // const RALT = 0b001 << 6; - /// This is the "windows" key on PC and "command" key on Mac. - const SUPER = 0b100 << 9; - // const LSUPER = 0b010 << 9; - // const RSUPER = 0b001 << 9; - } -} - -#[cfg(feature = "serde")] -mod modifiers_serde { - use super::ModifiersState; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - #[derive(Default, Serialize, Deserialize)] - #[serde(default)] - #[serde(rename = "ModifiersState")] - pub struct ModifiersStateSerialize { - pub shift_key: bool, - pub control_key: bool, - pub alt_key: bool, - pub super_key: bool, - } - - impl Serialize for ModifiersState { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = ModifiersStateSerialize { - shift_key: self.shift_key(), - control_key: self.control_key(), - alt_key: self.alt_key(), - super_key: self.super_key(), - }; - s.serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for ModifiersState { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let ModifiersStateSerialize { - shift_key, - control_key, - alt_key, - super_key, - } = ModifiersStateSerialize::deserialize(deserializer)?; - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, shift_key); - m.set(ModifiersState::CONTROL, control_key); - m.set(ModifiersState::ALT, alt_key); - m.set(ModifiersState::SUPER, super_key); - Ok(m) - } - } -} - -/// Contains the platform-native physical key identifier (aka scancode) -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum NativeKeyCode { - Unidentified, - Windows(u16), - MacOS(u16), - Gtk(u16), - - /// This is the android "key code" of the event as returned by - /// `KeyEvent.getKeyCode()` - Android(i32), -} - -/// Represents the code of a physical key. -/// -/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few -/// exceptions: -/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and -/// "SuperRight" here. -/// - The key that the specification calls "Super" is reported as `Unidentified` here. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. -/// -/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum KeyCode { - /// This variant is used when the key cannot be translated to any - /// other variant. - /// - /// The native scancode is provided (if available) in order - /// to allow the user to specify keybindings for keys which - /// are not defined by this API. - Unidentified(NativeKeyCode), - /// ` on a US keyboard. This is also called a backtick or grave. - /// This is the 半角/全角/漢字 - /// (hankaku/zenkaku/kanji) key on Japanese keyboards - Backquote, - /// Used for both the US \\ (on the 101-key layout) and also for the key - /// located between the " and Enter keys on row C of the 102-, - /// 104- and 106-key layouts. - /// Labeled # on a UK (102) keyboard. - Backslash, - /// [ on a US keyboard. - BracketLeft, - /// ] on a US keyboard. - BracketRight, - /// , on a US keyboard. - Comma, - /// 0 on a US keyboard. - Digit0, - /// 1 on a US keyboard. - Digit1, - /// 2 on a US keyboard. - Digit2, - /// 3 on a US keyboard. - Digit3, - /// 4 on a US keyboard. - Digit4, - /// 5 on a US keyboard. - Digit5, - /// 6 on a US keyboard. - Digit6, - /// 7 on a US keyboard. - Digit7, - /// 8 on a US keyboard. - Digit8, - /// 9 on a US keyboard. - Digit9, - /// = on a US keyboard. - Equal, - /// Located between the left Shift and Z keys. - /// Labeled \\ on a UK keyboard. - IntlBackslash, - /// Located between the / and right Shift keys. - /// Labeled \\ (ro) on a Japanese keyboard. - IntlRo, - /// Located between the = and Backspace keys. - /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a - /// Russian keyboard. - IntlYen, - /// a on a US keyboard. - /// Labeled q on an AZERTY (e.g., French) keyboard. - KeyA, - /// b on a US keyboard. - KeyB, - /// c on a US keyboard. - KeyC, - /// d on a US keyboard. - KeyD, - /// e on a US keyboard. - KeyE, - /// f on a US keyboard. - KeyF, - /// g on a US keyboard. - KeyG, - /// h on a US keyboard. - KeyH, - /// i on a US keyboard. - KeyI, - /// j on a US keyboard. - KeyJ, - /// k on a US keyboard. - KeyK, - /// l on a US keyboard. - KeyL, - /// m on a US keyboard. - KeyM, - /// n on a US keyboard. - KeyN, - /// o on a US keyboard. - KeyO, - /// p on a US keyboard. - KeyP, - /// q on a US keyboard. - /// Labeled a on an AZERTY (e.g., French) keyboard. - KeyQ, - /// r on a US keyboard. - KeyR, - /// s on a US keyboard. - KeyS, - /// t on a US keyboard. - KeyT, - /// u on a US keyboard. - KeyU, - /// v on a US keyboard. - KeyV, - /// w on a US keyboard. - /// Labeled z on an AZERTY (e.g., French) keyboard. - KeyW, - /// x on a US keyboard. - KeyX, - /// y on a US keyboard. - /// Labeled z on a QWERTZ (e.g., German) keyboard. - KeyY, - /// z on a US keyboard. - /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a - /// QWERTZ (e.g., German) keyboard. - KeyZ, - /// - on a US keyboard. - Minus, - /// Shift+= on a US keyboard. - Plus, - /// . on a US keyboard. - Period, - /// ' on a US keyboard. - Quote, - /// ; on a US keyboard. - Semicolon, - /// / on a US keyboard. - Slash, - /// Alt, Option, or ⌥. - AltLeft, - /// Alt, Option, or ⌥. - /// This is labeled AltGr on many keyboard layouts. - AltRight, - /// Backspace or ⌫. - /// Labeled Delete on Apple keyboards. - Backspace, - /// CapsLock or ⇪ - CapsLock, - /// The application context menu key, which is typically found between the right - /// Super key and the right Control key. - ContextMenu, - /// Control or ⌃ - ControlLeft, - /// Control or ⌃ - ControlRight, - /// Enter or ↵. Labeled Return on Apple keyboards. - Enter, - /// The Windows, ⌘, Command, or other OS symbol key. - SuperLeft, - /// The Windows, ⌘, Command, or other OS symbol key. - SuperRight, - /// Shift or ⇧ - ShiftLeft, - /// Shift or ⇧ - ShiftRight, - /// (space) - Space, - /// Tab or ⇥ - Tab, - /// Japanese: 変 (henkan) - Convert, - /// Japanese: カタカナ/ひらがな/ローマ字 (katakana/hiragana/romaji) - KanaMode, - /// Korean: HangulMode 한/영 (han/yeong) - /// - /// Japanese (Mac keyboard): か (kana) - Lang1, - /// Korean: Hanja 한 (hanja) - /// - /// Japanese (Mac keyboard): 英 (eisu) - Lang2, - /// Japanese (word-processing keyboard): Katakana - Lang3, - /// Japanese (word-processing keyboard): Hiragana - Lang4, - /// Japanese (word-processing keyboard): Zenkaku/Hankaku - Lang5, - /// Japanese: 無変換 (muhenkan) - NonConvert, - /// ⌦. The forward delete key. - /// Note that on Apple keyboards, the key labelled Delete on the main part of - /// the keyboard is encoded as [`Backspace`]. - /// - /// [`Backspace`]: Self::Backspace - Delete, - /// Page Down, End, or ↘ - End, - /// Help. Not present on standard PC keyboards. - Help, - /// Home or ↖ - Home, - /// Insert or Ins. Not present on Apple keyboards. - Insert, - /// Page Down, PgDn, or ⇟ - PageDown, - /// Page Up, PgUp, or ⇞ - PageUp, - /// ↓ - ArrowDown, - /// ← - ArrowLeft, - /// → - ArrowRight, - /// ↑ - ArrowUp, - /// On the Mac, this is used for the numpad Clear key. - NumLock, - /// 0 Ins on a keyboard. 0 on a phone or remote control - Numpad0, - /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote control - Numpad1, - /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control - Numpad2, - /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control - Numpad3, - /// 4 ← on a keyboard. 4 GHI on a phone or remote control - Numpad4, - /// 5 on a keyboard. 5 JKL on a phone or remote control - Numpad5, - /// 6 → on a keyboard. 6 MNO on a phone or remote control - Numpad6, - /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone - /// or remote control - Numpad7, - /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control - Numpad8, - /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone - /// or remote control - Numpad9, - /// + - NumpadAdd, - /// Found on the Microsoft Natural Keyboard. - NumpadBackspace, - /// C or A (All Clear). Also for use with numpads that have a - /// Clear key that is separate from the NumLock key. On the Mac, the - /// numpad Clear key is encoded as [`NumLock`]. - /// - /// [`NumLock`]: Self::NumLock - NumpadClear, - /// C (Clear Entry) - NumpadClearEntry, - /// , (thousands separator). For locales where the thousands separator - /// is a "." (e.g., Brazil), this key may generate a .. - NumpadComma, - /// . Del. For locales where the decimal separator is "," (e.g., - /// Brazil), this key may generate a ,. - NumpadDecimal, - /// / - NumpadDivide, - NumpadEnter, - /// = - NumpadEqual, - /// # on a phone or remote control device. This key is typically found - /// below the 9 key and to the right of the 0 key. - NumpadHash, - /// M Add current entry to the value stored in memory. - NumpadMemoryAdd, - /// M Clear the value stored in memory. - NumpadMemoryClear, - /// M Replace the current entry with the value stored in memory. - NumpadMemoryRecall, - /// M Replace the value stored in memory with the current entry. - NumpadMemoryStore, - /// M Subtract current entry from the value stored in memory. - NumpadMemorySubtract, - /// * on a keyboard. For use with numpads that provide mathematical - /// operations (+, - * and /). - /// - /// Use `NumpadStar` for the * key on phones and remote controls. - NumpadMultiply, - /// ( Found on the Microsoft Natural Keyboard. - NumpadParenLeft, - /// ) Found on the Microsoft Natural Keyboard. - NumpadParenRight, - /// * on a phone or remote control device. - /// - /// This key is typically found below the 7 key and to the left of - /// the 0 key. - /// - /// Use "NumpadMultiply" for the * key on - /// numeric keypads. - NumpadStar, - /// - - NumpadSubtract, - /// Esc or ⎋ - Escape, - /// Fn This is typically a hardware key that does not generate a separate code. - Fn, - /// FLock or FnLock. Function Lock key. Found on the Microsoft - /// Natural Keyboard. - FnLock, - /// PrtScr SysRq or Print Screen - PrintScreen, - /// Scroll Lock - ScrollLock, - /// Pause Break - Pause, - /// Some laptops place this key to the left of the ↑ key. - /// - /// This also the "back" button (triangle) on Android. - BrowserBack, - BrowserFavorites, - /// Some laptops place this key to the right of the ↑ key. - BrowserForward, - /// The "home" button on Android. - BrowserHome, - BrowserRefresh, - BrowserSearch, - BrowserStop, - /// Eject or ⏏. This key is placed in the function section on some Apple - /// keyboards. - Eject, - /// Sometimes labelled My Computer on the keyboard - LaunchApp1, - /// Sometimes labelled Calculator on the keyboard - LaunchApp2, - LaunchMail, - MediaPlayPause, - MediaSelect, - MediaStop, - MediaTrackNext, - MediaTrackPrevious, - /// This key is placed in the function section on some Apple keyboards, replacing the - /// Eject key. - Power, - Sleep, - AudioVolumeDown, - AudioVolumeMute, - AudioVolumeUp, - WakeUp, - Hyper, - Turbo, - Abort, - Resume, - Suspend, - /// Found on Sun’s USB keyboard. - Again, - /// Found on Sun’s USB keyboard. - Copy, - /// Found on Sun’s USB keyboard. - Cut, - /// Found on Sun’s USB keyboard. - Find, - /// Found on Sun’s USB keyboard. - Open, - /// Found on Sun’s USB keyboard. - Paste, - /// Found on Sun’s USB keyboard. - Props, - /// Found on Sun’s USB keyboard. - Select, - /// Found on Sun’s USB keyboard. - Undo, - /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. - Hiragana, - /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. - Katakana, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F1, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F2, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F3, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F4, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F5, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F6, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F7, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F8, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F9, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F10, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F11, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F12, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F13, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F14, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F15, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F16, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F17, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F18, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F19, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F20, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F21, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F22, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F23, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F24, - /// General-purpose function key. - F25, - /// General-purpose function key. - F26, - /// General-purpose function key. - F27, - /// General-purpose function key. - F28, - /// General-purpose function key. - F29, - /// General-purpose function key. - F30, - /// General-purpose function key. - F31, - /// General-purpose function key. - F32, - /// General-purpose function key. - F33, - /// General-purpose function key. - F34, - /// General-purpose function key. - F35, -} - -impl KeyCode { - /// Return platform specific scancode. - pub fn to_scancode(self) -> Option { - platform_keycode_to_scancode(self) - } - /// Return `KeyCode` from platform scancode. - pub fn from_scancode(scancode: u32) -> KeyCode { - platform_keycode_from_scancode(scancode) - } -} - -impl FromStr for KeyCode { - type Err = OsError; - fn from_str(accelerator_string: &str) -> Result { - let keycode = match accelerator_string.to_uppercase().as_str() { - "`" | "BACKQUOTE" => KeyCode::Backquote, - "BACKSLASH" => KeyCode::Backslash, - "[" | "BRACKETLEFT" => KeyCode::BracketLeft, - "]" | "BRACKETRIGHT" => KeyCode::BracketRight, - "," | "COMMA" => KeyCode::Comma, - "0" => KeyCode::Digit0, - "1" => KeyCode::Digit1, - "2" => KeyCode::Digit2, - "3" => KeyCode::Digit3, - "4" => KeyCode::Digit4, - "5" => KeyCode::Digit5, - "6" => KeyCode::Digit6, - "7" => KeyCode::Digit7, - "8" => KeyCode::Digit8, - "9" => KeyCode::Digit9, - "NUM0" | "NUMPAD0" => KeyCode::Numpad0, - "NUM1" | "NUMPAD1" => KeyCode::Numpad1, - "NUM2" | "NUMPAD2" => KeyCode::Numpad2, - "NUM3" | "NUMPAD3" => KeyCode::Numpad3, - "NUM4" | "NUMPAD4" => KeyCode::Numpad4, - "NUM5" | "NUMPAD5" => KeyCode::Numpad5, - "NUM6" | "NUMPAD6" => KeyCode::Numpad6, - "NUM7" | "NUMPAD7" => KeyCode::Numpad7, - "NUM8" | "NUMPAD8" => KeyCode::Numpad8, - "NUM9" | "NUMPAD9" => KeyCode::Numpad9, - "=" => KeyCode::Equal, - "-" => KeyCode::Minus, - "PLUS" => KeyCode::Plus, - "." | "PERIOD" => KeyCode::Period, - "'" | "QUOTE" => KeyCode::Quote, - "\\" => KeyCode::IntlBackslash, - "A" => KeyCode::KeyA, - "B" => KeyCode::KeyB, - "C" => KeyCode::KeyC, - "D" => KeyCode::KeyD, - "E" => KeyCode::KeyE, - "F" => KeyCode::KeyF, - "G" => KeyCode::KeyG, - "H" => KeyCode::KeyH, - "I" => KeyCode::KeyI, - "J" => KeyCode::KeyJ, - "K" => KeyCode::KeyK, - "L" => KeyCode::KeyL, - "M" => KeyCode::KeyM, - "N" => KeyCode::KeyN, - "O" => KeyCode::KeyO, - "P" => KeyCode::KeyP, - "Q" => KeyCode::KeyQ, - "R" => KeyCode::KeyR, - "S" => KeyCode::KeyS, - "T" => KeyCode::KeyT, - "U" => KeyCode::KeyU, - "V" => KeyCode::KeyV, - "W" => KeyCode::KeyW, - "X" => KeyCode::KeyX, - "Y" => KeyCode::KeyY, - "Z" => KeyCode::KeyZ, - - ";" | "SEMICOLON" => KeyCode::Semicolon, - "/" | "SLASH" => KeyCode::Slash, - "BACKSPACE" => KeyCode::Backspace, - "CAPSLOCK" => KeyCode::CapsLock, - "CONTEXTMENU" => KeyCode::ContextMenu, - "ENTER" => KeyCode::Enter, - "SPACE" => KeyCode::Space, - "TAB" => KeyCode::Tab, - "CONVERT" => KeyCode::Convert, - - "DELETE" => KeyCode::Delete, - "END" => KeyCode::End, - "HELP" => KeyCode::Help, - "HOME" => KeyCode::Home, - "PAGEDOWN" => KeyCode::PageDown, - "PAGEUP" => KeyCode::PageUp, - - "DOWN" | "ARROWDOWN" => KeyCode::ArrowDown, - "UP" | "ARROWUP" => KeyCode::ArrowUp, - "LEFT" | "ARROWLEFT" => KeyCode::ArrowLeft, - "RIGHT" | "ARROWRIGHT" => KeyCode::ArrowRight, - - "NUMLOCK" => KeyCode::NumLock, - "NUMADD" | "NUMPADADD" => KeyCode::NumpadAdd, - "NUMBACKSPACE" | "NUMPADBACKSPACE" => KeyCode::NumpadBackspace, - "NUMCLEAR" | "NUMPADCLEAR" => KeyCode::NumpadClear, - "NUMCOMMA" | "NUMPADCOMMA" => KeyCode::NumpadComma, - "NUMDIVIDE" | "NUMPADDIVIDE" => KeyCode::NumpadDivide, - "NUMSUBSTRACT" | "NUMPADSUBSTRACT" => KeyCode::NumpadSubtract, - "NUMENTER" | "NUMPADENTER" => KeyCode::NumpadEnter, - - "ESC" | "ESCAPE" => KeyCode::Escape, - "FN" => KeyCode::Fn, - "FNLOCK" => KeyCode::FnLock, - "PRINTSCREEN" => KeyCode::PrintScreen, - "SCROLLLOCK" => KeyCode::ScrollLock, - - "PAUSE" => KeyCode::Pause, - - "VOLUMEMUTE" => KeyCode::AudioVolumeMute, - "VOLUMEDOWN" => KeyCode::AudioVolumeDown, - "VOLUMEUP" => KeyCode::AudioVolumeUp, - "MEDIANEXTTRACK" => KeyCode::MediaTrackNext, - "MEDIAPREVIOUSTRACK" => KeyCode::MediaTrackPrevious, - "MEDIAPLAYPAUSE" => KeyCode::MediaPlayPause, - "LAUNCHMAIL" => KeyCode::LaunchMail, - - "SUSPEND" => KeyCode::Suspend, - "F1" => KeyCode::F1, - "F2" => KeyCode::F2, - "F3" => KeyCode::F3, - "F4" => KeyCode::F4, - "F5" => KeyCode::F5, - "F6" => KeyCode::F6, - "F7" => KeyCode::F7, - "F8" => KeyCode::F8, - "F9" => KeyCode::F9, - "F10" => KeyCode::F10, - "F11" => KeyCode::F11, - "F12" => KeyCode::F12, - "F13" => KeyCode::F13, - "F14" => KeyCode::F14, - "F15" => KeyCode::F15, - "F16" => KeyCode::F16, - "F17" => KeyCode::F17, - "F18" => KeyCode::F18, - "F19" => KeyCode::F19, - "F20" => KeyCode::F20, - "F21" => KeyCode::F21, - "F22" => KeyCode::F22, - "F23" => KeyCode::F23, - "F24" => KeyCode::F24, - "F25" => KeyCode::F25, - "F26" => KeyCode::F26, - "F27" => KeyCode::F27, - "F28" => KeyCode::F28, - "F29" => KeyCode::F29, - "F30" => KeyCode::F30, - "F31" => KeyCode::F31, - "F32" => KeyCode::F32, - "F33" => KeyCode::F33, - "F34" => KeyCode::F34, - "F35" => KeyCode::F35, - _ => KeyCode::Unidentified(NativeKeyCode::Unidentified), - }; - - Ok(keycode) - } -} - -impl fmt::Display for KeyCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - &KeyCode::Unidentified(_) => write!(f, "{:?}", "Unidentified"), - val => write!(f, "{val:?}"), - } - } -} - -/// Key represents the meaning of a keypress. -/// -/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few -/// exceptions: -/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's -/// another key which the specification calls `Super`. That does not exist here.) -/// - The `Space` variant here, can be identified by the character it generates in the -/// specificaiton. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. -/// - The `Dead` variant here, can specify the character which is inserted when pressing the -/// dead-key twice. -/// -/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum Key<'a> { - /// A key string that corresponds to the character typed by the user, taking into account the - /// user’s current locale setting, and any system-level keyboard mapping overrides that are in - /// effect. - Character(&'a str), - - /// This variant is used when the key cannot be translated to any other variant. - /// - /// The native scancode is provided (if available) in order to allow the user to specify - /// keybindings for keys which are not defined by this API. - Unidentified(NativeKeyCode), - - /// Contains the text representation of the dead-key when available. - /// - /// ## Platform-specific - /// - **Web:** Always contains `None` - Dead(Option), - - /// The `Alt` (Alternative) key. - /// - /// This key enables the alternate modifier function for interpreting concurrent or subsequent - /// keyboard input. This key value is also used for the Apple Option key. - Alt, - /// The Alternate Graphics (AltGr or AltGraph) key. - /// - /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the - /// level 2 modifier). - AltGraph, - /// The `Caps Lock` (Capital) key. - /// - /// Toggle capital character lock function for interpreting subsequent keyboard input event. - CapsLock, - /// The `Control` or `Ctrl` key. - /// - /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard - /// input. - Control, - /// The Function switch `Fn` key. Activating this key simultaneously with another key changes - /// that key’s value to an alternate character or function. This key is often handled directly - /// in the keyboard hardware and does not usually generate key events. - Fn, - /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the - /// keyboard to changes some keys' values to an alternate character or function. This key is - /// often handled directly in the keyboard hardware and does not usually generate key events. - FnLock, - /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting - /// subsequent keyboard input. - NumLock, - /// Toggle between scrolling and cursor movement modes. - ScrollLock, - /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard - /// input. - Shift, - /// The Symbol modifier key (used on some virtual keyboards). - Symbol, - SymbolLock, - Hyper, - /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard - /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. - /// - /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. - Super, - /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key - /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for - /// the Android `KEYCODE_DPAD_CENTER`. - Enter, - /// The Horizontal Tabulation `Tab` key. - Tab, - /// Used in text to insert a space between words. Usually located below the character keys. - Space, - /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) - ArrowDown, - /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) - ArrowLeft, - /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) - ArrowRight, - /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) - ArrowUp, - /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). - End, - /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). - /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. - /// - /// [`GoHome`]: Self::GoHome - Home, - /// Scroll down or display next page of content. - PageDown, - /// Scroll up or display previous page of content. - PageUp, - /// Used to remove the character to the left of the cursor. This key value is also used for - /// the key labeled `Delete` on MacOS keyboards. - Backspace, - /// Remove the currently selected input. - Clear, - /// Copy the current selection. (`APPCOMMAND_COPY`) - Copy, - /// The Cursor Select key. - CrSel, - /// Cut the current selection. (`APPCOMMAND_CUT`) - Cut, - /// Used to delete the character to the right of the cursor. This key value is also used for the - /// key labeled `Delete` on MacOS keyboards when `Fn` is active. - Delete, - /// The Erase to End of Field key. This key deletes all characters from the current cursor - /// position to the end of the current field. - EraseEof, - /// The Extend Selection (Exsel) key. - ExSel, - /// Toggle between text modes for insertion or overtyping. - /// (`KEYCODE_INSERT`) - Insert, - /// The Paste key. (`APPCOMMAND_PASTE`) - Paste, - /// Redo the last action. (`APPCOMMAND_REDO`) - Redo, - /// Undo the last action. (`APPCOMMAND_UNDO`) - Undo, - /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. - Accept, - /// Redo or repeat an action. - Again, - /// The Attention (Attn) key. - Attn, - Cancel, - /// Show the application’s context menu. - /// This key is commonly found between the right `Super` key and the right `Control` key. - ContextMenu, - /// The `Esc` key. This key was originally used to initiate an escape sequence, but is - /// now more generally used to exit or "escape" the current context, such as closing a dialog - /// or exiting full screen mode. - Escape, - Execute, - /// Open the Find dialog. (`APPCOMMAND_FIND`) - Find, - /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, - /// `KEYCODE_HELP`) - Help, - /// Pause the current state or application (as appropriate). - /// - /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` - /// instead. - Pause, - /// Play or resume the current state or application (as appropriate). - /// - /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` - /// instead. - Play, - /// The properties (Props) key. - Props, - Select, - /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) - ZoomIn, - /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) - ZoomOut, - /// The Brightness Down key. Typically controls the display brightness. - /// (`KEYCODE_BRIGHTNESS_DOWN`) - BrightnessDown, - /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) - BrightnessUp, - /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) - Eject, - LogOff, - /// Toggle power state. (`KEYCODE_POWER`) - /// Note: Note: Some devices might not expose this key to the operating environment. - Power, - /// The `PowerOff` key. Sometime called `PowerDown`. - PowerOff, - /// Initiate print-screen function. - PrintScreen, - /// The Hibernate key. This key saves the current state of the computer to disk so that it can - /// be restored. The computer will then shutdown. - Hibernate, - /// The Standby key. This key turns off the display and places the computer into a low-power - /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. - /// (`KEYCODE_SLEEP`) - Standby, - /// The WakeUp key. (`KEYCODE_WAKEUP`) - WakeUp, - /// Initate the multi-candidate mode. - AllCandidates, - Alphanumeric, - /// Initiate the Code Input mode to allow characters to be entered by - /// their code points. - CodeInput, - /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a - /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to - /// produce a different character. - Compose, - /// Convert the current input method sequence. - Convert, - /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. - FinalMode, - /// Switch to the first character group. (ISO/IEC 9995) - GroupFirst, - /// Switch to the last character group. (ISO/IEC 9995) - GroupLast, - /// Switch to the next character group. (ISO/IEC 9995) - GroupNext, - /// Switch to the previous character group. (ISO/IEC 9995) - GroupPrevious, - /// Toggle between or cycle through input modes of IMEs. - ModeChange, - NextCandidate, - /// Accept current input method sequence without - /// conversion in IMEs. - NonConvert, - PreviousCandidate, - Process, - SingleCandidate, - /// Toggle between Hangul and English modes. - HangulMode, - HanjaMode, - JunjaMode, - /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. - /// (`KEYCODE_EISU`) - Eisu, - /// The (Half-Width) Characters key. - Hankaku, - /// The Hiragana (Japanese Kana characters) key. - Hiragana, - /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) - HiraganaKatakana, - /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from - /// romaji mode). - KanaMode, - /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is - /// typically used to switch to a hiragana keyboard for the purpose of converting input into - /// kanji. (`KEYCODE_KANA`) - KanjiMode, - /// The Katakana (Japanese Kana characters) key. - Katakana, - /// The Roman characters function key. - Romaji, - /// The Zenkaku (Full-Width) Characters key. - Zenkaku, - /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) - ZenkakuHankaku, - /// General purpose virtual function key, as index 1. - Soft1, - /// General purpose virtual function key, as index 2. - Soft2, - /// General purpose virtual function key, as index 3. - Soft3, - /// General purpose virtual function key, as index 4. - Soft4, - /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, - /// `KEYCODE_CHANNEL_DOWN`) - ChannelDown, - /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, - /// `KEYCODE_CHANNEL_UP`) - ChannelUp, - /// Close the current document or message (Note: This doesn’t close the application). - /// (`APPCOMMAND_CLOSE`) - Close, - /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) - MailForward, - /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) - MailReply, - /// Send the current message. (`APPCOMMAND_SEND_MAIL`) - MailSend, - /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) - MediaClose, - /// Initiate or continue forward playback at faster than normal speed, or increase speed if - /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) - MediaFastForward, - /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) - /// - /// Note: Media controller devices should use this value rather than `"Pause"` for their pause - /// keys. - MediaPause, - /// Initiate or continue media playback at normal speed, if not currently playing at normal - /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) - MediaPlay, - /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, - /// `KEYCODE_MEDIA_PLAY_PAUSE`) - MediaPlayPause, - /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, - /// `KEYCODE_MEDIA_RECORD`) - MediaRecord, - /// Initiate or continue reverse playback at faster than normal speed, or increase speed if - /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) - MediaRewind, - /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. - /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) - MediaStop, - /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) - MediaTrackNext, - /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, - /// `KEYCODE_MEDIA_PREVIOUS`) - MediaTrackPrevious, - /// Open a new document or message. (`APPCOMMAND_NEW`) - New, - /// Open an existing document or message. (`APPCOMMAND_OPEN`) - Open, - /// Print the current document or message. (`APPCOMMAND_PRINT`) - Print, - /// Save the current document or message. (`APPCOMMAND_SAVE`) - Save, - /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) - SpellCheck, - /// The `11` key found on media numpads that - /// have buttons from `1` ... `12`. - Key11, - /// The `12` key found on media numpads that - /// have buttons from `1` ... `12`. - Key12, - /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) - AudioBalanceLeft, - /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) - AudioBalanceRight, - /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, - /// `VK_BASS_BOOST_DOWN`) - AudioBassBoostDown, - /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) - AudioBassBoostToggle, - /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, - /// `VK_BASS_BOOST_UP`) - AudioBassBoostUp, - /// Adjust audio fader towards front. (`VK_FADER_FRONT`) - AudioFaderFront, - /// Adjust audio fader towards rear. (`VK_FADER_REAR`) - AudioFaderRear, - /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) - AudioSurroundModeNext, - /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) - AudioTrebleDown, - /// Increase treble. (`APPCOMMAND_TREBLE_UP`) - AudioTrebleUp, - /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) - AudioVolumeDown, - /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) - AudioVolumeUp, - /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, - /// `KEYCODE_VOLUME_MUTE`) - AudioVolumeMute, - /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) - MicrophoneToggle, - /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) - MicrophoneVolumeDown, - /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) - MicrophoneVolumeUp, - /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) - MicrophoneVolumeMute, - /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) - SpeechCorrectionList, - /// Toggle between dictation mode and command/control mode. - /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) - SpeechInputToggle, - /// The first generic "LaunchApplication" key. This is commonly associated with launching "My - /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) - LaunchApplication1, - /// The second generic "LaunchApplication" key. This is commonly associated with launching - /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, - /// `KEYCODE_CALCULATOR`) - LaunchApplication2, - /// The "Calendar" key. (`KEYCODE_CALENDAR`) - LaunchCalendar, - /// The "Contacts" key. (`KEYCODE_CONTACTS`) - LaunchContacts, - /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) - LaunchMail, - /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) - LaunchMediaPlayer, - LaunchMusicPlayer, - LaunchPhone, - LaunchScreenSaver, - LaunchSpreadsheet, - LaunchWebBrowser, - LaunchWebCam, - LaunchWordProcessor, - /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) - BrowserBack, - /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) - BrowserFavorites, - /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) - BrowserForward, - /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) - BrowserHome, - /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) - BrowserRefresh, - /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) - BrowserSearch, - /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) - BrowserStop, - /// The Application switch key, which provides a list of recent apps to switch between. - /// (`KEYCODE_APP_SWITCH`) - AppSwitch, - /// The Call key. (`KEYCODE_CALL`) - Call, - /// The Camera key. (`KEYCODE_CAMERA`) - Camera, - /// The Camera focus key. (`KEYCODE_FOCUS`) - CameraFocus, - /// The End Call key. (`KEYCODE_ENDCALL`) - EndCall, - /// The Back key. (`KEYCODE_BACK`) - GoBack, - /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) - GoHome, - /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) - HeadsetHook, - LastNumberRedial, - /// The Notification key. (`KEYCODE_NOTIFICATION`) - Notification, - /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) - MannerMode, - VoiceDial, - /// Switch to viewing TV. (`KEYCODE_TV`) - TV, - /// TV 3D Mode. (`KEYCODE_3D_MODE`) - TV3DMode, - /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) - TVAntennaCable, - /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) - TVAudioDescription, - /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) - TVAudioDescriptionMixDown, - /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) - TVAudioDescriptionMixUp, - /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) - TVContentsMenu, - /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) - TVDataService, - /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) - TVInput, - /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) - TVInputComponent1, - /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) - TVInputComponent2, - /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) - TVInputComposite1, - /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) - TVInputComposite2, - /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) - TVInputHDMI1, - /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) - TVInputHDMI2, - /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) - TVInputHDMI3, - /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) - TVInputHDMI4, - /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) - TVInputVGA1, - /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) - TVMediaContext, - /// Toggle network. (`KEYCODE_TV_NETWORK`) - TVNetwork, - /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) - TVNumberEntry, - /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) - TVPower, - /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) - TVRadioService, - /// Satellite. (`KEYCODE_TV_SATELLITE`) - TVSatellite, - /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) - TVSatelliteBS, - /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) - TVSatelliteCS, - /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) - TVSatelliteToggle, - /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) - TVTerrestrialAnalog, - /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) - TVTerrestrialDigital, - /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) - TVTimer, - /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) - AVRInput, - /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) - AVRPower, - /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, - /// `KEYCODE_PROG_RED`) - ColorF0Red, - /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, - /// `KEYCODE_PROG_GREEN`) - ColorF1Green, - /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, - /// `KEYCODE_PROG_YELLOW`) - ColorF2Yellow, - /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, - /// `KEYCODE_PROG_BLUE`) - ColorF3Blue, - /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) - ColorF4Grey, - /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) - ColorF5Brown, - /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) - ClosedCaptionToggle, - /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) - Dimmer, - /// Swap video sources. (`VK_DISPLAY_SWAP`) - DisplaySwap, - /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) - DVR, - /// Exit the current application. (`VK_EXIT`) - Exit, - /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) - FavoriteClear0, - /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) - FavoriteClear1, - /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) - FavoriteClear2, - /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) - FavoriteClear3, - /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) - FavoriteRecall0, - /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) - FavoriteRecall1, - /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) - FavoriteRecall2, - /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) - FavoriteRecall3, - /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) - FavoriteStore0, - /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) - FavoriteStore1, - /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) - FavoriteStore2, - /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) - FavoriteStore3, - /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) - Guide, - /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) - GuideNextDay, - /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) - GuidePreviousDay, - /// Toggle display of information about currently selected context or media. (`VK_INFO`, - /// `KEYCODE_INFO`) - Info, - /// Toggle instant replay. (`VK_INSTANT_REPLAY`) - InstantReplay, - /// Launch linked content, if available and appropriate. (`VK_LINK`) - Link, - /// List the current program. (`VK_LIST`) - ListProgram, - /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) - LiveContent, - /// Lock or unlock current content or program. (`VK_LOCK`) - Lock, - /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) - /// - /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, - /// which is encoded as `"ContextMenu"`. - MediaApps, - /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) - MediaAudioTrack, - /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) - MediaLast, - /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) - MediaSkipBackward, - /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) - MediaSkipForward, - /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) - MediaStepBackward, - /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) - MediaStepForward, - /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) - MediaTopMenu, - /// Navigate in. (`KEYCODE_NAVIGATE_IN`) - NavigateIn, - /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) - NavigateNext, - /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) - NavigateOut, - /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) - NavigatePrevious, - /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) - NextFavoriteChannel, - /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) - NextUserProfile, - /// Access on-demand content or programs. (`VK_ON_DEMAND`) - OnDemand, - /// Pairing key to pair devices. (`KEYCODE_PAIRING`) - Pairing, - /// Move picture-in-picture window down. (`VK_PINP_DOWN`) - PinPDown, - /// Move picture-in-picture window. (`VK_PINP_MOVE`) - PinPMove, - /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) - PinPToggle, - /// Move picture-in-picture window up. (`VK_PINP_UP`) - PinPUp, - /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) - PlaySpeedDown, - /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) - PlaySpeedReset, - /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) - PlaySpeedUp, - /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) - RandomToggle, - /// Not a physical key, but this key code is sent when the remote control battery is low. - /// (`VK_RC_LOW_BATTERY`) - RcLowBattery, - /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) - RecordSpeedNext, - /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). - /// (`VK_RF_BYPASS`) - RfBypass, - /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) - ScanChannelsToggle, - /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) - ScreenModeNext, - /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) - Settings, - /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) - SplitScreenToggle, - /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) - STBInput, - /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) - STBPower, - /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) - Subtitle, - /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). - Teletext, - /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) - VideoModeNext, - /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) - Wink, - /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, - /// `KEYCODE_TV_ZOOM_MODE`) - ZoomToggle, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F1, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F2, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F3, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F4, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F5, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F6, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F7, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F8, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F9, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F10, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F11, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F12, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F13, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F14, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F15, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F16, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F17, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F18, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F19, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F20, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F21, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F22, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F23, - /// General-purpose function key. - /// Usually found at the top of the keyboard. - F24, - /// General-purpose function key. - F25, - /// General-purpose function key. - F26, - /// General-purpose function key. - F27, - /// General-purpose function key. - F28, - /// General-purpose function key. - F29, - /// General-purpose function key. - F30, - /// General-purpose function key. - F31, - /// General-purpose function key. - F32, - /// General-purpose function key. - F33, - /// General-purpose function key. - F34, - /// General-purpose function key. - F35, -} - -impl<'a> Key<'a> { - pub fn to_text(&self) -> Option<&'a str> { - match self { - Key::Character(ch) => Some(*ch), - Key::Enter => Some("\r"), - Key::Backspace => Some("\x08"), - Key::Tab => Some("\t"), - Key::Space => Some(" "), - Key::Escape => Some("\x1b"), - _ => None, - } - } -} - -impl<'a> From<&'a str> for Key<'a> { - fn from(src: &'a str) -> Key<'a> { - Key::Character(src) - } -} - -/// Represents the location of a physical key. -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum KeyLocation { - Standard, - Left, - Right, - Numpad, -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs deleted file mode 100644 index e157637f5..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! Tao is a cross-platform application window creation and event loop management library. -//! -//! # Building windows -//! -//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the -//! [`EventLoop::new()`] function. -//! -//! ```no_run -//! use tao::event_loop::EventLoop; -//! let event_loop = EventLoop::new(); -//! ``` -//! -//! Once this is done there are two ways to create a [`Window`]: -//! -//! - Calling [`Window::new(&event_loop)`][window_new]. -//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build]. -//! -//! The first method is the simplest, and will give you default values for everything. The second -//! method allows you to customize the way your [`Window`] will look and behave by modifying the -//! fields of the [`WindowBuilder`] object before you create the [`Window`]. -//! -//! # Event handling -//! -//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can -//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the -//! window or a key getting pressed while the window is focused. Devices can generate -//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. -//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a -//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. -//! -//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will -//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and -//! will run until the `control_flow` argument given to the closure is set to -//! [`ControlFlow`]`::`[`ExitWithCode`] (which [`ControlFlow`]`::`[`Exit`] aliases to), at which -//! point [`Event`]`::`[`LoopDestroyed`] is emitted and the entire program terminates. -//! -//! Tao no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop -//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on -//! most other platforms. However, this model can be re-implemented to an extent with -//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why -//! it's discouraged, beyond compatibility reasons. -//! -//! -//! ```no_run -//! use tao::{ -//! event::{Event, WindowEvent}, -//! event_loop::{ControlFlow, EventLoop}, -//! window::WindowBuilder, -//! }; -//! -//! let event_loop = EventLoop::new(); -//! let window = WindowBuilder::new().build(&event_loop).unwrap(); -//! -//! event_loop.run(move |event, _, control_flow| { -//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't -//! // dispatched any events. This is ideal for games and similar applications. -//! *control_flow = ControlFlow::Poll; -//! -//! // ControlFlow::Wait pauses the event loop if no events are available to process. -//! // This is ideal for non-game applications that only update in response to user -//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. -//! *control_flow = ControlFlow::Wait; -//! -//! match event { -//! Event::WindowEvent { -//! event: WindowEvent::CloseRequested, -//! .. -//! } => { -//! println!("The close button was pressed; stopping"); -//! *control_flow = ControlFlow::Exit -//! }, -//! Event::MainEventsCleared => { -//! // Application update code. -//! -//! // Queue a RedrawRequested event. -//! // -//! // You only need to call this if you've determined that you need to redraw, in -//! // applications which do not always need to. Applications that redraw continuously -//! // can just render here instead. -//! window.request_redraw(); -//! }, -//! Event::RedrawRequested(_) => { -//! // Redraw the application. -//! // -//! // It's preferable for applications that do not render continuously to render in -//! // this event rather than in MainEventsCleared, since rendering in here allows -//! // the program to gracefully handle redraws requested by the OS. -//! }, -//! _ => () -//! } -//! }); -//! ``` -//! -//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be -//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`] -//! dispatched the event. -//! -//! # Drawing on the window -//! -//! Tao doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to -//! retrieve the raw handle of the window and display (see the [`platform`] module and/or the -//! [`raw_window_handle`] and [`raw_display_handle`] methods), which in turn allows you to create an -//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. -//! -//! Note that many platforms will display garbage data in the window's client area if the -//! application doesn't render anything to the window by the time the desktop compositor is ready to -//! display the window to the user. If you notice this happening, you should create the window with -//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the -//! window visible only once you're ready to render into it. -//! -//! [`EventLoop`]: event_loop::EventLoop -//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return -//! [`EventLoop::new()`]: event_loop::EventLoop::new -//! [event_loop_run]: event_loop::EventLoop::run -//! [`ControlFlow`]: event_loop::ControlFlow -//! [`Exit`]: event_loop::ControlFlow::Exit -//! [`ExitWithCode`]: event_loop::ControlFlow::ExitWithCode -//! [`Window`]: window::Window -//! [`WindowId`]: window::WindowId -//! [`WindowBuilder`]: window::WindowBuilder -//! [window_new]: window::Window::new -//! [window_builder_new]: window::WindowBuilder::new -//! [window_builder_build]: window::WindowBuilder::build -//! [window_id_fn]: window::Window::id -//! [`Event`]: event::Event -//! [`WindowEvent`]: event::WindowEvent -//! [`DeviceEvent`]: event::DeviceEvent -//! [`UserEvent`]: event::Event::UserEvent -//! [`LoopDestroyed`]: event::Event::LoopDestroyed -//! [`platform`]: platform -//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle -//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle -#![allow( - clippy::match_str_case_mismatch, - clippy::upper_case_acronyms, - clippy::from_over_into, - clippy::option_map_unit_fn, - clippy::needless_lifetimes, - clippy::type_complexity, - clippy::identity_op, - clippy::wrong_self_convention, - clippy::non_send_fields_in_send_ty -)] -#![deny(rustdoc::broken_intra_doc_links)] - -use dpi::PixelUnit; -#[cfg(feature = "rwh_04")] -pub use rwh_04; -#[cfg(feature = "rwh_05")] -pub use rwh_05; -#[cfg(feature = "rwh_06")] -pub use rwh_06; - -#[allow(unused_imports)] -#[macro_use] -extern crate log; -#[cfg(feature = "serde")] -#[macro_use] -extern crate serde; -#[macro_use] -extern crate bitflags; -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[macro_use(class, msg_send, sel)] -extern crate objc2; - -pub use dpi; - -#[macro_use] -pub mod error; -pub mod event; -pub mod event_loop; -mod icon; -pub mod keyboard; -pub mod monitor; -mod platform_impl; - -pub mod window; - -pub mod platform; - -pub(crate) fn extract_width_height(size: dpi::Size) -> (PixelUnit, PixelUnit) { - match size { - dpi::Size::Physical(size) => ( - PixelUnit::Physical(size.width.into()), - PixelUnit::Physical(size.height.into()), - ), - dpi::Size::Logical(size) => ( - PixelUnit::Logical(size.width.into()), - PixelUnit::Logical(size.height.into()), - ), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs deleted file mode 100644 index 7e5404f57..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! Types useful for interacting with a user's monitors. -//! -//! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] -//! type. This is retrieved from one of the following methods, which return an iterator of -//! [`MonitorHandle`][monitor_handle]: -//! - [`EventLoopWindowTarget::available_monitors`][loop_get] -//! - [`Window::available_monitors`][window_get]. -//! -//! [monitor_handle]: crate::monitor::MonitorHandle -//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors -//! [window_get]: crate::window::Window::available_monitors -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - platform_impl, -}; - -/// Describes a fullscreen video mode of a monitor. -/// -/// Can be acquired with: -/// - [`MonitorHandle::video_modes`][monitor_get]. -/// -/// [monitor_get]: crate::monitor::MonitorHandle::video_modes -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct VideoMode { - pub(crate) video_mode: platform_impl::VideoMode, -} - -impl std::fmt::Debug for VideoMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.video_mode.fmt(f) - } -} - -impl PartialOrd for VideoMode { - fn partial_cmp(&self, other: &VideoMode) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VideoMode { - fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { - // TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32` - // to `u32` there - let size: (u32, u32) = self.size().into(); - let other_size: (u32, u32) = other.size().into(); - self.monitor().cmp(&other.monitor()).then( - size - .cmp(&other_size) - .then( - self - .refresh_rate() - .cmp(&other.refresh_rate()) - .then(self.bit_depth().cmp(&other.bit_depth())), - ) - .reverse(), - ) - } -} - -impl VideoMode { - /// Returns the resolution of this video mode. - #[inline] - pub fn size(&self) -> PhysicalSize { - self.video_mode.size() - } - - /// Returns the bit depth of this video mode, as in how many bits you have - /// available per color. This is generally 24 bits or 32 bits on modern - /// systems, depending on whether the alpha channel is counted or not. - /// - /// ## Platform-specific - /// - **iOS:** Always returns 32. - #[inline] - pub fn bit_depth(&self) -> u16 { - self.video_mode.bit_depth() - } - - /// Returns the refresh rate of this video mode. **Note**: the returned - /// refresh rate is an integer approximation, and you shouldn't rely on this - /// value to be exact. - #[inline] - pub fn refresh_rate(&self) -> u16 { - self.video_mode.refresh_rate() - } - - /// Returns the monitor that this video mode is valid for. Each monitor has - /// a separate set of valid video modes. - #[inline] - pub fn monitor(&self) -> MonitorHandle { - self.video_mode.monitor() - } -} - -impl std::fmt::Display for VideoMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}x{} @ {} Hz ({} bpp)", - self.size().width, - self.size().height, - self.refresh_rate(), - self.bit_depth() - ) - } -} - -/// Handle to a monitor. -/// -/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. -/// -/// [`Window`]: crate::window::Window -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct MonitorHandle { - pub(crate) inner: platform_impl::MonitorHandle, -} - -impl MonitorHandle { - /// Returns a human-readable name of the monitor. - /// - /// Returns `None` if the monitor doesn't exist anymore. - #[inline] - pub fn name(&self) -> Option { - self.inner.name() - } - - /// Returns the monitor's resolution. - #[inline] - pub fn size(&self) -> PhysicalSize { - self.inner.size() - } - - /// Returns the top-left corner position of the monitor relative to the larger full - /// screen area. - #[inline] - pub fn position(&self) -> PhysicalPosition { - self.inner.position() - } - - /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. - /// - /// See the [`dpi`](crate::dpi) module for more information. - /// - /// ## Platform-specific - /// - /// - **Android:** Always returns 1.0. - #[inline] - pub fn scale_factor(&self) -> f64 { - self.inner.scale_factor() - } - - /// Returns all fullscreen video modes supported by this monitor. - /// - /// ## Platform-specific - /// - **Linux:** Unsupported. This will always return empty iterator. - #[inline] - pub fn video_modes(&self) -> impl Iterator { - self.inner.video_modes() - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs deleted file mode 100644 index 7d4456cb8..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "android")] - -pub mod prelude { - pub use crate::platform_impl::ndk_glue::*; - pub use tao_macros::{android_fn, generate_package_name}; -} -use crate::{ - event_loop::{EventLoop, EventLoopWindowTarget}, - platform_impl::ndk_glue::Rect, - window::{Window, WindowBuilder}, -}; -use ndk::configuration::Configuration; - -/// Additional methods on `EventLoop` that are specific to Android. -pub trait EventLoopExtAndroid {} - -impl EventLoopExtAndroid for EventLoop {} - -/// Additional methods on `EventLoopWindowTarget` that are specific to Android. -pub trait EventLoopWindowTargetExtAndroid {} - -/// Additional methods on `Window` that are specific to Android. -pub trait WindowExtAndroid { - fn content_rect(&self) -> Rect; - - fn config(&self) -> Configuration; - - fn activity_name(&self) -> String; -} - -impl WindowExtAndroid for Window { - fn content_rect(&self) -> Rect { - self.window.content_rect() - } - - fn config(&self) -> Configuration { - self.window.config() - } - - fn activity_name(&self) -> String { - self.window.activity_name().to_string() - } -} - -impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} - -/// Additional methods on `WindowBuilder` that are specific to Android. -pub trait WindowBuilderExtAndroid { - /// The name of the activity class to create. - fn with_activity_name(self, activity_name: String) -> Self; - - /// The name of the activity class that created this window. - /// - /// This is important to define which stack the activity will be created on. - fn with_created_by_activity_name(self, created_by_activity_name: String) -> Self; -} - -impl WindowBuilderExtAndroid for WindowBuilder { - fn with_activity_name(mut self, activity_name: String) -> Self { - self.platform_specific.activity_name = Some(activity_name); - self - } - - fn with_created_by_activity_name(mut self, created_by_activity_name: String) -> Self { - self.platform_specific.created_by_activity_name = Some(created_by_activity_name); - self - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs deleted file mode 100644 index 536c3a98b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "ios")] - -use std::os::raw::c_void; - -use crate::{ - event_loop::{EventLoop, EventLoopWindowTarget}, - monitor::{MonitorHandle, VideoMode}, - platform_impl::set_badge_count, - window::{Window, WindowBuilder}, -}; - -/// Additional methods on [`EventLoop`] that are specific to iOS. -pub trait EventLoopExtIOS { - /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device. - fn idiom(&self) -> Idiom; -} - -impl EventLoopExtIOS for EventLoop { - fn idiom(&self) -> Idiom { - self.event_loop.idiom() - } -} - -/// Additional methods on [`Window`] that are specific to iOS. -pub trait WindowExtIOS { - /// Returns a pointer to the [`UIWindow`] that is used by this window. - /// - /// The pointer will become invalid when the [`Window`] is destroyed. - /// - /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc - fn ui_window(&self) -> *mut c_void; - - /// Returns a pointer to the [`UIViewController`] that is used by this window. - /// - /// The pointer will become invalid when the [`Window`] is destroyed. - /// - /// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc - fn ui_view_controller(&self) -> *mut c_void; - - /// Returns a pointer to the [`UIView`] that is used by this window. - /// - /// The pointer will become invalid when the [`Window`] is destroyed. - /// - /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc - fn ui_view(&self) -> *mut c_void; - - /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. - /// - /// The default value is device dependent, and it's recommended GLES or Metal applications set - /// this to [`MonitorHandle::scale_factor()`]. - /// - /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc - /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc - fn set_scale_factor(&self, scale_factor: f64); - - /// Sets the valid orientations for the [`Window`]. - /// - /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. - /// - /// This changes the value returned by - /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc), - /// and then calls - /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc). - fn set_valid_orientations(&self, valid_orientations: ValidOrientations); - - /// Sets whether the [`Window`] prefers the home indicator hidden. - /// - /// The default is to prefer showing the home indicator. - /// - /// This changes the value returned by - /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc), - /// and then calls - /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc). - /// - /// This only has an effect on iOS 11.0+. - fn set_prefers_home_indicator_hidden(&self, hidden: bool); - - /// Sets the screen edges for which the system gestures will take a lower priority than the - /// application's touch handling. - /// - /// This changes the value returned by - /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc), - /// and then calls - /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). - /// - /// This only has an effect on iOS 11.0+. - fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); - - /// Sets whether the [`Window`] prefers the status bar hidden. - /// - /// The default is to prefer showing the status bar. - /// - /// This changes the value returned by - /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc), - /// and then calls - /// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc). - fn set_prefers_status_bar_hidden(&self, hidden: bool); - - /// Sets the badge count on iOS launcher. 0 hides the count - fn set_badge_count(&self, count: i32); - - /// Returns the identifier of the UIScene tied to this UIWindow. - fn scene_identifier(&self) -> String; -} - -impl WindowExtIOS for Window { - #[inline] - fn ui_window(&self) -> *mut c_void { - self.window.ui_window() as _ - } - - #[inline] - fn ui_view_controller(&self) -> *mut c_void { - self.window.ui_view_controller() as _ - } - - #[inline] - fn ui_view(&self) -> *mut c_void { - self.window.ui_view() as _ - } - - #[inline] - fn set_scale_factor(&self, scale_factor: f64) { - self.window.set_scale_factor(scale_factor) - } - - #[inline] - fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { - self.window.set_valid_orientations(valid_orientations) - } - - #[inline] - fn set_prefers_home_indicator_hidden(&self, hidden: bool) { - self.window.set_prefers_home_indicator_hidden(hidden) - } - - #[inline] - fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { - self - .window - .set_preferred_screen_edges_deferring_system_gestures(edges) - } - - #[inline] - fn set_prefers_status_bar_hidden(&self, hidden: bool) { - self.window.set_prefers_status_bar_hidden(hidden) - } - - #[inline] - fn set_badge_count(&self, count: i32) { - self.window.set_badge_count(count) - } - - #[inline] - fn scene_identifier(&self) -> String { - self.window.scene_identifier() - } -} - -pub trait EventLoopWindowTargetExtIOS { - /// Sets the badge count on iOS launcher. 0 hides the count - fn set_badge_count(&self, count: i32); -} - -impl EventLoopWindowTargetExtIOS for EventLoopWindowTarget { - fn set_badge_count(&self, count: i32) { - set_badge_count(count) - } -} - -/// Additional methods on [`WindowBuilder`] that are specific to iOS. -pub trait WindowBuilderExtIOS { - /// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided. - /// - /// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc). - /// - /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc - fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; - - /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. - /// - /// The default value is device dependent, and it's recommended GLES or Metal applications set - /// this to [`MonitorHandle::scale_factor()`]. - /// - /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc - /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc - fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder; - - /// Sets the valid orientations for the [`Window`]. - /// - /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. - /// - /// This sets the initial value returned by - /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). - fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder; - - /// Sets whether the [`Window`] prefers the home indicator hidden. - /// - /// The default is to prefer showing the home indicator. - /// - /// This sets the initial value returned by - /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). - /// - /// This only has an effect on iOS 11.0+. - fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder; - - /// Sets the screen edges for which the system gestures will take a lower priority than the - /// application's touch handling. - /// - /// This sets the initial value returned by - /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). - /// - /// This only has an effect on iOS 11.0+. - fn with_preferred_screen_edges_deferring_system_gestures( - self, - edges: ScreenEdge, - ) -> WindowBuilder; - - /// Sets whether the [`Window`] prefers the status bar hidden. - /// - /// The default is to prefer showing the status bar. - /// - /// This sets the initial value returned by - /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). - fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder; - - /// Sets the identifier of the UIScene that is requesting the creation of this new scene, - /// establishing a relationship between the two scenes. - /// - /// By default the system uses the foreground scene. - fn with_requesting_scene_identifier(self, identifier: String) -> WindowBuilder; -} - -impl WindowBuilderExtIOS for WindowBuilder { - #[inline] - fn with_root_view_class(mut self, root_view_class: *const c_void) -> WindowBuilder { - self.platform_specific.root_view_class = unsafe { &*(root_view_class as *const _) }; - self - } - - #[inline] - fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder { - self.platform_specific.scale_factor = Some(scale_factor); - self - } - - #[inline] - fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> WindowBuilder { - self.platform_specific.valid_orientations = valid_orientations; - self - } - - #[inline] - fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder { - self.platform_specific.prefers_home_indicator_hidden = hidden; - self - } - - #[inline] - fn with_preferred_screen_edges_deferring_system_gestures( - mut self, - edges: ScreenEdge, - ) -> WindowBuilder { - self - .platform_specific - .preferred_screen_edges_deferring_system_gestures = edges; - self - } - - #[inline] - fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder { - self.platform_specific.prefers_status_bar_hidden = hidden; - self - } - - #[inline] - fn with_requesting_scene_identifier(mut self, identifier: String) -> WindowBuilder { - self - .platform_specific - .requesting_scene_identifier - .replace(identifier); - self - } -} - -/// Additional methods on [`MonitorHandle`] that are specific to iOS. -pub trait MonitorHandleExtIOS { - /// Returns a pointer to the [`UIScreen`] that is used by this monitor. - /// - /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc - fn ui_screen(&self) -> *mut c_void; - - /// Returns the preferred [`VideoMode`] for this monitor. - /// - /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). - fn preferred_video_mode(&self) -> VideoMode; -} - -impl MonitorHandleExtIOS for MonitorHandle { - #[inline] - fn ui_screen(&self) -> *mut c_void { - self.inner.ui_screen() as _ - } - - #[inline] - fn preferred_video_mode(&self) -> VideoMode { - self.inner.preferred_video_mode() - } -} - -/// Valid orientations for a particular [`Window`]. -#[non_exhaustive] -#[derive(Clone, Copy, Debug)] -pub enum ValidOrientations { - /// Excludes `PortraitUpsideDown` on iphone - LandscapeAndPortrait, - - Landscape, - - /// Excludes `PortraitUpsideDown` on iphone - Portrait, -} - -impl Default for ValidOrientations { - #[inline] - fn default() -> ValidOrientations { - ValidOrientations::LandscapeAndPortrait - } -} - -/// The device [idiom]. -/// -/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc -#[non_exhaustive] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Idiom { - Unspecified, - - /// iPhone and iPod touch. - Phone, - - /// iPad. - Pad, - - /// tvOS and Apple TV. - TV, - CarPlay, -} - -bitflags! { - /// The [edges] of a screen. - /// - /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc - #[derive(Clone, Copy ,Default)] - pub struct ScreenEdge: u8 { - const NONE = 0; - const TOP = 1 << 0; - const LEFT = 1 << 1; - const BOTTOM = 1 << 2; - const RIGHT = 1 << 3; - const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits() - | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits(); - } -} - -pub(crate) fn operating_system_version() -> (isize, isize, isize) { - let process_info = objc2_foundation::NSProcessInfo::processInfo(); - let version = process_info.operatingSystemVersion(); - ( - version.majorVersion, - version.minorVersion, - version.patchVersion, - ) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs deleted file mode 100644 index 2f0603c23..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "linux")] diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs deleted file mode 100644 index 32b8cbbf3..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "macos")] - -use std::os::raw::c_void; - -use objc2_foundation::NSObject; - -use crate::{ - dpi::{LogicalSize, Position}, - event_loop::{EventLoop, EventLoopWindowTarget}, - monitor::MonitorHandle, - platform_impl::{get_aux_state_mut, set_badge_label, set_dock_visibility, Parent}, - window::{Window, WindowBuilder}, -}; - -/// Additional methods on `Window` that are specific to MacOS. -pub trait WindowExtMacOS { - /// Returns a pointer to the cocoa `NSWindow` that is used by this window. - /// - /// The pointer will become invalid when the `Window` is destroyed. - fn ns_window(&self) -> *mut c_void; - - /// Returns a pointer to the cocoa `NSView` that is used by this window. - /// - /// The pointer will become invalid when the `Window` is destroyed. - fn ns_view(&self) -> *mut c_void; - - /// Returns whether or not the window is in simple fullscreen mode. - fn simple_fullscreen(&self) -> bool; - - /// Toggles a fullscreen mode that doesn't require a new macOS space. - /// Returns a boolean indicating whether the transition was successful (this - /// won't work if the window was already in the native fullscreen). - /// - /// This is how fullscreen used to work on macOS in versions before Lion. - /// And allows the user to have a fullscreen window without using another - /// space or taking control over the entire monitor. - fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; - - /// Returns whether or not the window has shadow. - fn has_shadow(&self) -> bool; - - /// Sets whether or not the window has shadow. - fn set_has_shadow(&self, has_shadow: bool); - - /// Set the window traffic light position relative to the upper left corner - fn set_traffic_light_inset>(&self, position: P); - /// Put the window in a state which indicates a file save is required. - /// - /// - fn set_is_document_edited(&self, edited: bool); - - /// Get the window's edit state - fn is_document_edited(&self) -> bool; - - /// Sets whether the system can automatically organize windows into tabs. - /// - /// - fn set_allows_automatic_window_tabbing(&self, enabled: bool); - - /// Returns whether the system can automatically organize windows into tabs. - fn allows_automatic_window_tabbing(&self) -> bool; - - /// Group windows together by using the same tabbing identifier. - /// - /// - fn set_tabbing_identifier(&self, identifier: &str); - - /// Returns the window's tabbing identifier. - fn tabbing_identifier(&self) -> String; - - /// The content view consumes the full size of the window. - /// - /// - fn set_fullsize_content_view(&self, fullsize: bool); - - /// A Boolean value that indicates whether the title bar draws its background. - /// - /// - fn set_titlebar_transparent(&self, transparent: bool); - - /// Sets the badge label on the taskbar - fn set_badge_label(&self, label: Option); -} - -impl WindowExtMacOS for Window { - #[inline] - fn ns_window(&self) -> *mut c_void { - self.window.ns_window() - } - - #[inline] - fn ns_view(&self) -> *mut c_void { - self.window.ns_view() - } - - #[inline] - fn simple_fullscreen(&self) -> bool { - self.window.simple_fullscreen() - } - - #[inline] - fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { - self.window.set_simple_fullscreen(fullscreen) - } - - #[inline] - fn has_shadow(&self) -> bool { - self.window.has_shadow() - } - - #[inline] - fn set_has_shadow(&self, has_shadow: bool) { - self.window.set_has_shadow(has_shadow) - } - - #[inline] - fn set_traffic_light_inset>(&self, position: P) { - self.window.set_traffic_light_inset(position) - } - - #[inline] - fn set_is_document_edited(&self, edited: bool) { - self.window.set_is_document_edited(edited) - } - - #[inline] - fn is_document_edited(&self) -> bool { - self.window.is_document_edited() - } - - #[inline] - fn set_allows_automatic_window_tabbing(&self, enabled: bool) { - self.window.set_allows_automatic_window_tabbing(enabled) - } - - #[inline] - fn allows_automatic_window_tabbing(&self) -> bool { - self.window.allows_automatic_window_tabbing() - } - - #[inline] - fn set_tabbing_identifier(&self, identifier: &str) { - self.window.set_tabbing_identifier(identifier) - } - - #[inline] - fn tabbing_identifier(&self) -> String { - self.window.tabbing_identifier() - } - - #[inline] - fn set_fullsize_content_view(&self, fullsize: bool) { - self.window.set_fullsize_content_view(fullsize); - } - - #[inline] - fn set_titlebar_transparent(&self, transparent: bool) { - self.window.set_titlebar_transparent(transparent); - } - - #[inline] - fn set_badge_label(&self, label: Option) { - self.window.set_badge_label(label); - } -} - -/// Corresponds to `NSApplicationActivationPolicy`. -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub enum ActivationPolicy { - /// Corresponds to `NSApplicationActivationPolicyRegular`. - #[default] - Regular, - /// Corresponds to `NSApplicationActivationPolicyAccessory`. - Accessory, - /// Corresponds to `NSApplicationActivationPolicyProhibited`. - Prohibited, -} - -/// Additional methods on `WindowBuilder` that are specific to MacOS. -/// -/// **Note:** Properties dealing with the titlebar will be overwritten by the `with_decorations` method -/// on the base `WindowBuilder`: -/// -/// - `with_titlebar_transparent` -/// - `with_title_hidden` -/// - `with_titlebar_hidden` -/// - `with_titlebar_buttons_hidden` -/// - `with_fullsize_content_view` -pub trait WindowBuilderExtMacOS { - /// Sets a parent to the window to be created. - fn with_parent_window(self, parent: *mut c_void) -> WindowBuilder; - /// Enables click-and-drag behavior for the entire window, not just the titlebar. - fn with_movable_by_window_background(self, movable_by_window_background: bool) -> WindowBuilder; - /// Makes the titlebar transparent and allows the content to appear behind it. - fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder; - /// Hides the window title. - fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder; - /// Hides the window titlebar. - fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder; - /// Hides the window titlebar buttons. - fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder; - /// Makes the window content appear behind the titlebar. - fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; - /// Build window with `resizeIncrements` property. Values must not be 0. - fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; - fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; - /// Sets whether or not the window has shadow. - fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; - /// Sets the traffic light position to (x, y) relative to the upper left corner - fn with_traffic_light_inset>(self, inset: P) -> WindowBuilder; - /// Sets whether the system can automatically organize windows into tabs. - fn with_automatic_window_tabbing(self, automatic_tabbing: bool) -> WindowBuilder; - /// Defines the window [tabbing identifier]. - /// - /// [tabbing identifier]: - fn with_tabbing_identifier(self, identifier: &str) -> WindowBuilder; -} - -impl WindowBuilderExtMacOS for WindowBuilder { - #[inline] - fn with_parent_window(mut self, parent: *mut c_void) -> WindowBuilder { - self.platform_specific.parent = Parent::ChildOf(parent); - self - } - - #[inline] - fn with_movable_by_window_background( - mut self, - movable_by_window_background: bool, - ) -> WindowBuilder { - self.platform_specific.movable_by_window_background = movable_by_window_background; - self - } - - #[inline] - fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder { - self.platform_specific.titlebar_transparent = titlebar_transparent; - self - } - - #[inline] - fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder { - self.platform_specific.titlebar_hidden = titlebar_hidden; - self - } - - #[inline] - fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder { - self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; - self - } - - #[inline] - fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder { - self.platform_specific.title_hidden = title_hidden; - self - } - - #[inline] - fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder { - self.platform_specific.fullsize_content_view = fullsize_content_view; - self - } - - #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { - self.platform_specific.resize_increments = Some(increments); - self - } - - #[inline] - fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder { - self.platform_specific.disallow_hidpi = disallow_hidpi; - self - } - - #[inline] - fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder { - self.platform_specific.has_shadow = has_shadow; - self - } - - #[inline] - fn with_traffic_light_inset>(mut self, inset: P) -> WindowBuilder { - self.platform_specific.traffic_light_inset = Some(inset.into()); - self - } - - #[inline] - fn with_automatic_window_tabbing(mut self, automatic_tabbing: bool) -> WindowBuilder { - self.platform_specific.automatic_tabbing = automatic_tabbing; - self - } - - #[inline] - fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> WindowBuilder { - self - .platform_specific - .tabbing_identifier - .replace(tabbing_identifier.into()); - self - } -} - -pub trait EventLoopExtMacOS { - /// Sets the activation policy for the application. It is set to - /// `NSApplicationActivationPolicyRegular` by default. - /// - /// This function only takes effect if it's called before calling - /// [`run`](crate::event_loop::EventLoop::run) or - /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return). - /// To set the activation policy after that, use - /// [`EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime`](crate::platform::macos::EventLoopWindowTargetExtMacOS::set_activation_policy_at_runtime). - fn set_activation_policy(&mut self, activation_policy: ActivationPolicy); - - /// Sets the visibility of the application in the dock. - /// - /// This function only takes effect if it's called before calling - /// [`run`](crate::event_loop::EventLoop::run) or - /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return). - fn set_dock_visibility(&mut self, visible: bool); - - /// Used to prevent the application from automatically activating when launched if - /// another application is already active - /// - /// The default behavior is to ignore other applications and activate when launched. - /// - /// This function only takes effect if it's called before calling - /// [`run`](crate::event_loop::EventLoop::run) or - /// [`run_return`](crate::platform::run_return::EventLoopExtRunReturn::run_return) - fn set_activate_ignoring_other_apps(&mut self, ignore: bool); -} - -impl EventLoopExtMacOS for EventLoop { - #[inline] - fn set_activation_policy(&mut self, activation_policy: ActivationPolicy) { - unsafe { - get_aux_state_mut(&**self.event_loop.delegate).activation_policy = activation_policy; - } - } - - #[inline] - fn set_dock_visibility(&mut self, visible: bool) { - unsafe { - get_aux_state_mut(&**self.event_loop.delegate).dock_visibility = visible; - } - } - - #[inline] - fn set_activate_ignoring_other_apps(&mut self, ignore: bool) { - unsafe { - get_aux_state_mut(&**self.event_loop.delegate).activate_ignoring_other_apps = ignore; - } - } -} - -/// Additional methods on `MonitorHandle` that are specific to MacOS. -pub trait MonitorHandleExtMacOS { - /// Returns the identifier of the monitor for Cocoa. - fn native_id(&self) -> u32; - /// Returns a pointer to the NSScreen representing this monitor. - fn ns_screen(&self) -> Option<*mut c_void>; -} - -impl MonitorHandleExtMacOS for MonitorHandle { - #[inline] - fn native_id(&self) -> u32 { - self.inner.native_identifier() - } - - fn ns_screen(&self) -> Option<*mut c_void> { - self - .inner - .ns_screen() - .map(|s| objc2::rc::Retained::into_raw(s) as *mut c_void) - } -} - -/// Additional methods on `EventLoopWindowTarget` that are specific to macOS. -pub trait EventLoopWindowTargetExtMacOS { - /// Hide the entire application. In most applications this is typically triggered with Command-H. - fn hide_application(&self); - /// Show the entire application. - fn show_application(&self); - /// Hide the other applications. In most applications this is typically triggered with Command+Option-H. - fn hide_other_applications(&self); - /// Sets the activation policy for the application. It is set to - /// `NSApplicationActivationPolicyRegular` by default. - /// - /// To set the activation policy before the app starts running, see - /// [`EventLoopExtMacOS::set_activation_policy`](crate::platform::macos::EventLoopExtMacOS::set_activation_policy). - fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy); - - /// Sets the visibility of the application in the dock. - /// - /// To set the dock visibility before the app starts running, see - /// [`EventLoopExtMacOS::set_dock_visibility`](crate::platform::macos::EventLoopExtMacOS::set_dock_visibility). - fn set_dock_visibility(&self, visible: bool); - - /// Sets the badge label on macos dock - fn set_badge_label(&self, label: Option); -} - -impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { - fn hide_application(&self) { - // TODO: Safety. - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; - objc2_app_kit::NSApplication::sharedApplication(mtm).hide(None) - } - - fn show_application(&self) { - // TODO: Safety. - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; - unsafe { objc2_app_kit::NSApplication::sharedApplication(mtm).unhide(None) } - } - - fn hide_other_applications(&self) { - // TODO: Safety. - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; - objc2_app_kit::NSApplication::sharedApplication(mtm).hideOtherApplications(None) - } - - fn set_activation_policy_at_runtime(&self, activation_policy: ActivationPolicy) { - use objc2_app_kit::NSApplicationActivationPolicy; - - let ns_activation_policy = match activation_policy { - ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, - ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, - ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, - }; - - // TODO: Safety. - let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() }; - objc2_app_kit::NSApplication::sharedApplication(mtm).setActivationPolicy(ns_activation_policy); - } - - fn set_dock_visibility(&self, visible: bool) { - let Some(Ok(delegate)) = (unsafe { - // TODO: Safety. - let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); - objc2_app_kit::NSApplication::sharedApplication(mtm) - .delegate() - .map(|delegate| delegate.downcast::()) - }) else { - return; - }; - set_dock_visibility(&delegate, visible); - } - - fn set_badge_label(&self, label: Option) { - set_badge_label(label); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs deleted file mode 100644 index 2ca20b36d..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! Contains traits with platform-specific methods in them. -//! -//! Contains the follow OS-specific modules: -//! -//! - `android` -//! - `ios` -//! - `macos` -//! - `unix` -//! - `linux` -//! - `windows` -//! -//! And the following platform-specific module: -//! -//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) -//! -//! However only the module corresponding to the platform you're compiling to will be available. - -pub mod android; -pub mod ios; -pub mod linux; -pub mod macos; -pub mod run_return; -pub mod unix; -pub mod windows; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs deleted file mode 100644 index e86ae1f6a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(not(target_os = "ios"))] - -use crate::{ - event::Event, - event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, -}; - -/// Additional methods on `EventLoop` to return control flow to the caller. -pub trait EventLoopExtRunReturn { - /// A type provided by the user that can be passed through `Event::UserEvent`. - type UserEvent; - - /// Initializes the `tao` event loop. - /// - /// Unlike `run`, this function accepts non-`'static` (i.e. non-`move`) closures and returns - /// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`. - /// - /// # Caveats - /// Despite its appearance at first glance, this is *not* a perfect replacement for - /// `poll_events`. For example, this function will not return on Windows or macOS while a - /// window is getting resized, resulting in all application logic outside of the - /// `event_handler` closure not running until the resize operation ends. Other OS operations - /// may also result in such freezes. This behavior is caused by fundamental limitations in the - /// underlying OS APIs, which cannot be hidden by `tao` without severe stability repercussions. - /// - /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. - /// - /// ## Platform-specific - /// - /// - **Unix-alikes** (**X11** or **Wayland**): This function returns `1` upon disconnection from - /// the display server. - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow); -} - -impl EventLoopExtRunReturn for EventLoop { - type UserEvent = T; - - fn run_return(&mut self, event_handler: F) -> i32 - where - F: FnMut(Event<'_, Self::UserEvent>, &EventLoopWindowTarget, &mut ControlFlow), - { - self.event_loop.run_return(event_handler) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs deleted file mode 100644 index c96fb9361..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] - -#[cfg(feature = "x11")] -use std::{os::raw::c_int, sync::Arc}; - -// XConnection utilities -#[doc(hidden)] -#[cfg(feature = "x11")] -pub use crate::platform_impl::x11; - -#[cfg(feature = "x11")] -use crate::platform_impl::x11::xdisplay::XError; -pub use crate::platform_impl::EventLoop as UnixEventLoop; -use crate::{ - error::{ExternalError, OsError}, - event_loop::{EventLoopBuilder, EventLoopWindowTarget}, - monitor::MonitorHandle, - platform_impl::{Parent, Window as UnixWindow}, - window::{Window, WindowBuilder}, -}; - -#[cfg(feature = "x11")] -use self::x11::xdisplay::XConnection; - -/// Additional methods on `EventLoop` that are specific to Unix. -pub trait EventLoopBuilderExtUnix { - /// Whether to allow the event loop to be created off of the main thread. - /// - /// By default, the window is only allowed to be created on the main - /// thread, to make platform compatibility easier. - /// - /// # `Window` caveats - /// - /// Note that any `Window` created on the new thread will be destroyed when the thread - /// terminates. Attempting to use a `Window` after its parent thread terminates has - /// unspecified, although explicitly not undefined, behavior. - fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; - - /// Set the gtk application id. - /// - /// If no application ID is given then some features (most notably application uniqueness) will be disabled. - fn with_app_id>(&mut self, id: S) -> &mut Self; -} - -impl EventLoopBuilderExtUnix for EventLoopBuilder { - #[inline] - fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { - self.platform_specific.any_thread = any_thread; - self - } - - fn with_app_id>(&mut self, id: S) -> &mut Self { - self.platform_specific.app_id = Some(id.into()); - self - } -} - -/// Additional methods on `Window` that are specific to Unix. -pub trait WindowExtUnix { - /// Create a new Tao window from an existing GTK window. Generally you should use - /// the non-Linux `WindowBuilder`, this is for those who need lower level window access - /// and know what they're doing. - fn new_from_gtk_window( - event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, - ) -> Result; - - /// Returns the `gtk::ApplicatonWindow` from gtk crate that is used by this window. - fn gtk_window(&self) -> >k::ApplicationWindow; - - /// Returns the vertical `gtk::Box` that is added by default as the sole child of this window. - /// Returns `None` if the default vertical `gtk::Box` creation was disabled by [`WindowBuilderExtUnix::with_default_vbox`]. - fn default_vbox(&self) -> Option<>k::Box>; - - /// Whether to show the window icon in the taskbar or not. - fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>; - - fn set_badge_count(&self, count: Option, desktop_filename: Option); -} - -impl WindowExtUnix for Window { - fn gtk_window(&self) -> >k::ApplicationWindow { - &self.window.window - } - - fn default_vbox(&self) -> Option<>k::Box> { - self.window.default_vbox.as_ref() - } - - fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { - self.window.set_skip_taskbar(skip) - } - - fn new_from_gtk_window( - event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, - ) -> Result { - let window = UnixWindow::new_from_gtk_window(&event_loop_window_target.p, window)?; - Ok(Window { window }) - } - - fn set_badge_count(&self, count: Option, desktop_filename: Option) { - self.window.set_badge_count(count, desktop_filename); - } -} - -pub trait WindowBuilderExtUnix { - /// Whether to create the window icon with the taskbar icon or not. - fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; - /// Set this window as a transient dialog for `parent` - /// - fn with_transient_for(self, parent: &impl gtk::glib::IsA) -> WindowBuilder; - - /// Whether to enable or disable the internal draw for transparent window. - /// - /// When tranparent attribute is enabled, we will call `connect_draw` and draw a transparent background. - /// For anyone who wants to draw the background themselves, set this to `false`. - /// Default is `true`. - fn with_transparent_draw(self, draw: bool) -> WindowBuilder; - - /// Whether to enable or disable the double buffered rendering of the window. - /// - /// Default is `true`. - fn with_double_buffered(self, double_buffered: bool) -> WindowBuilder; - - /// Whether to enable the rgba visual for the window. - /// - /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` - fn with_rgba_visual(self, rgba_visual: bool) -> WindowBuilder; - - /// Wether to set this window as app paintable - /// - /// - /// - /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` - fn with_app_paintable(self, app_paintable: bool) -> WindowBuilder; - - /// Whether to set cursor moved event. Cursor event is suited for native GUI frameworks and - /// games. But it can block gtk's own pipeline occasionally. Turn this off can help Gtk looks - /// smoother. - /// - /// Default is `true`. - fn with_cursor_moved_event(self, cursor_moved: bool) -> WindowBuilder; - - /// Whether to create a vertical `gtk::Box` and add it as the sole child of this window. - /// Created by default. - fn with_default_vbox(self, add: bool) -> WindowBuilder; -} - -impl WindowBuilderExtUnix for WindowBuilder { - fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder { - self.platform_specific.skip_taskbar = skip; - self - } - - fn with_transient_for(mut self, parent: &impl gtk::glib::IsA) -> WindowBuilder { - use gtk::glib::Cast; - self.platform_specific.parent = Parent::ChildOf(parent.clone().upcast()); - self - } - - fn with_transparent_draw(mut self, draw: bool) -> WindowBuilder { - self.platform_specific.auto_transparent = draw; - self - } - - fn with_double_buffered(mut self, double_buffered: bool) -> WindowBuilder { - self.platform_specific.double_buffered = double_buffered; - self - } - - fn with_rgba_visual(mut self, rgba_visual: bool) -> WindowBuilder { - self.platform_specific.rgba_visual = rgba_visual; - self - } - - fn with_app_paintable(mut self, app_paintable: bool) -> WindowBuilder { - self.platform_specific.app_paintable = app_paintable; - self - } - - fn with_cursor_moved_event(mut self, cursor_moved: bool) -> WindowBuilder { - self.platform_specific.cursor_moved = cursor_moved; - self - } - - fn with_default_vbox(mut self, add: bool) -> WindowBuilder { - self.platform_specific.default_vbox = add; - self - } -} - -/// Additional methods on `EventLoopWindowTarget` that are specific to Unix. -pub trait EventLoopWindowTargetExtUnix { - /// True if the `EventLoopWindowTarget` uses Wayland. - fn is_wayland(&self) -> bool; - - /// True if the `EventLoopWindowTarget` uses X11. - #[cfg(feature = "x11")] - fn is_x11(&self) -> bool; - - #[cfg(feature = "x11")] - fn xlib_xconnection(&self) -> Option>; - - // /// Returns a pointer to the `wl_display` object of wayland that is used by this - // /// `EventLoopWindowTarget`. - // /// - // /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). - // /// - // /// The pointer will become invalid when the winit `EventLoop` is destroyed. - // fn wayland_display(&self) -> Option<*mut raw::c_void>; - - /// Returns the gtk application for this event loop. - fn gtk_app(&self) -> >k::Application; - - /// Sets the badge count on the taskbar - fn set_badge_count(&self, count: Option, desktop_filename: Option); -} - -impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { - #[inline] - fn is_wayland(&self) -> bool { - self.p.is_wayland() - } - - #[cfg(feature = "x11")] - #[inline] - fn is_x11(&self) -> bool { - !self.p.is_wayland() - } - - #[cfg(feature = "x11")] - #[inline] - fn xlib_xconnection(&self) -> Option> { - if self.is_x11() { - if let Ok(xconn) = XConnection::new(Some(x_error_callback)) { - Some(Arc::new(xconn)) - } else { - None - } - } else { - None - } - } - - // #[inline] - // fn wayland_display(&self) -> Option<*mut raw::c_void> { - // match self.p { - // LinuxEventLoopWindowTarget::Wayland(ref p) => { - // Some(p.display().get_display_ptr() as *mut _) - // } - // #[cfg(feature = "x11")] - // _ => None, - // } - // } - - #[inline] - fn gtk_app(&self) -> >k::Application { - &self.p.app - } - - #[inline] - fn set_badge_count(&self, count: Option, desktop_filename: Option) { - self.p.set_badge_count(count, desktop_filename); - } -} - -#[cfg(feature = "x11")] -unsafe extern "C" fn x_error_callback( - _display: *mut x11::ffi::Display, - event: *mut x11::ffi::XErrorEvent, -) -> c_int { - let error = XError { - // TODO get the error text as description - description: String::new(), - error_code: (*event).error_code, - request_code: (*event).request_code, - minor_code: (*event).minor_code, - }; - - error!("X11 error: {:#?}", error); - - // Fun fact: this return value is completely ignored. - 0 -} - -/// Additional methods on `MonitorHandle` that are specific to Unix. -pub trait MonitorHandleExtUnix { - /// Returns the gdk handle of the monitor. - fn gdk_monitor(&self) -> >k::gdk::Monitor; -} - -impl MonitorHandleExtUnix for MonitorHandle { - #[inline] - fn gdk_monitor(&self) -> >k::gdk::Monitor { - &self.inner.monitor - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs deleted file mode 100644 index a248b04b3..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright 2014-2021 The tao contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "windows")] - -use std::path::Path; - -use crate::{ - dpi::PhysicalSize, - error::ExternalError, - event::DeviceId, - event_loop::EventLoopBuilder, - monitor::MonitorHandle, - platform_impl::{Parent, WinIcon}, - window::{BadIcon, Icon, Theme, Window, WindowBuilder}, -}; -use windows::Win32::UI::Input::KeyboardAndMouse::*; - -pub type HWND = isize; -pub type HMENU = isize; - -/// Additional methods on `EventLoop` that are specific to Windows. -pub trait EventLoopBuilderExtWindows { - /// Whether to allow the event loop to be created off of the main thread. - /// - /// By default, the window is only allowed to be created on the main - /// thread, to make platform compatibility easier. - /// - /// # `Window` caveats - /// - /// Note that any `Window` created on the new thread will be destroyed when the thread - /// terminates. Attempting to use a `Window` after its parent thread terminates has - /// unspecified, although explicitly not undefined, behavior. - fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; - - /// Whether to enable process-wide DPI awareness. - /// - /// By default, `tao` will attempt to enable process-wide DPI awareness. If - /// that's undesirable, you can disable it with this function. - /// - /// # Example - /// - /// Disable process-wide DPI awareness. - /// - /// ``` - /// use tao::event_loop::EventLoopBuilder; - /// #[cfg(target_os = "windows")] - /// use tao::platform::windows::EventLoopBuilderExtWindows; - /// - /// let mut builder = EventLoopBuilder::new(); - /// #[cfg(target_os = "windows")] - /// builder.with_dpi_aware(false); - /// # if false { // We can't test this part - /// let event_loop = builder.build(); - /// # } - /// ``` - fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self; - - /// A callback to be executed before dispatching a win32 message to the window procedure. - /// Return true to disable tao's internal message dispatching. - /// - /// # Example - /// - /// ``` - /// # use windows::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG}; - /// use tao::event_loop::EventLoopBuilder; - /// #[cfg(target_os = "windows")] - /// use tao::platform::windows::EventLoopBuilderExtWindows; - /// - /// let mut builder = EventLoopBuilder::new(); - /// #[cfg(target_os = "windows")] - /// builder.with_msg_hook(|msg|{ - /// let msg = msg as *const MSG; - /// # let accels_: Vec = Vec::new(); - /// # let accels = accels_.as_slice(); - /// let translated = unsafe { - /// TranslateAcceleratorW( - /// (*msg).hwnd, - /// CreateAcceleratorTableW(accels).unwrap(), - /// msg, - /// ) == 1 - /// }; - /// translated - /// }); - /// ``` - fn with_msg_hook(&mut self, callback: F) -> &mut Self - where - F: FnMut(*const std::ffi::c_void) -> bool + 'static; - - /// Forces a theme or uses the system settings if `None` was provided. - /// - /// This will only affect some controls like context menus. - /// - /// ## Note - /// - /// Since this setting is app-wide, using [`WindowBuilder::with_theme`] - /// will not change the affected controls for that specific window, - /// so it is recommended to always use the same theme used for this app-wide setting - /// or use `None` so it automatically uses the theme of this method - /// or falls back to the system preference. - fn with_theme(&mut self, theme: Option) -> &mut Self; -} - -impl EventLoopBuilderExtWindows for EventLoopBuilder { - #[inline] - fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { - self.platform_specific.any_thread = any_thread; - self - } - - #[inline] - fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self { - self.platform_specific.dpi_aware = dpi_aware; - self - } - - #[inline] - fn with_msg_hook(&mut self, callback: F) -> &mut Self - where - F: FnMut(*const std::ffi::c_void) -> bool + 'static, - { - self.platform_specific.msg_hook = Some(Box::new(callback)); - self - } - - #[inline] - fn with_theme(&mut self, theme: Option) -> &mut Self { - self.platform_specific.preferred_theme = theme; - self - } -} - -/// Additional methods on `Window` that are specific to Windows. -pub trait WindowExtWindows { - /// Returns the HINSTANCE of the window - fn hinstance(&self) -> isize; - /// Returns the native handle that is used by this window. - /// - /// The pointer will become invalid when the native window was destroyed. - fn hwnd(&self) -> isize; - - /// Enables or disables mouse and keyboard input to the specified window. - /// - /// A window must be enabled before it can be activated. - /// If an application has create a modal dialog box by disabling its owner window - /// (as described in [`WindowBuilderExtWindows::with_owner_window`]), the application must enable - /// the owner window before destroying the dialog box. - /// Otherwise, another window will receive the keyboard focus and be activated. - /// - /// If a child window is disabled, it is ignored when the system tries to determine which - /// window should receive mouse messages. - /// - /// For more information, see - /// and - fn set_enable(&self, enabled: bool); - - /// This sets `ICON_BIG`. A good ceiling here is 256x256. - fn set_taskbar_icon(&self, taskbar_icon: Option); - - /// This sets the overlay icon - fn set_overlay_icon(&self, icon: Option<&Icon>); - - /// Returns the current window theme. - fn theme(&self) -> Theme; - - /// Reset the dead key state of the keyboard. - /// - /// This is useful when a dead key is bound to trigger an action. Then - /// this function can be called to reset the dead key state so that - /// follow-up text input won't be affected by the dead key. - fn reset_dead_keys(&self); - - /// Starts the resizing drag from given edge - fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32); - - /// Whether to show the window icon in the taskbar or not. - fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError>; - - /// Shows or hides the background drop shadow for undecorated windows. - /// - /// Enabling the shadow causes a thin 1px line to appear on the top of the window. - fn set_undecorated_shadow(&self, shadow: bool); - - /// Returns whether this window has shadow for undecorated windows. - fn has_undecorated_shadow(&self) -> bool; - - /// Sets right-to-left layout. - /// - /// Enabling this mainly flips the orientation of menus and title bar buttons - fn set_rtl(&self, rtl: bool); -} - -impl WindowExtWindows for Window { - #[inline] - fn hinstance(&self) -> isize { - self.window.hinstance().0 as _ - } - - #[inline] - fn hwnd(&self) -> isize { - self.window.hwnd().0 as _ - } - - #[inline] - fn set_enable(&self, enabled: bool) { - unsafe { - let _ = EnableWindow(self.window.hwnd(), enabled); - } - } - - #[inline] - fn set_taskbar_icon(&self, taskbar_icon: Option) { - self.window.set_taskbar_icon(taskbar_icon) - } - - #[inline] - fn theme(&self) -> Theme { - self.window.theme() - } - - #[inline] - fn reset_dead_keys(&self) { - self.window.reset_dead_keys(); - } - - #[inline] - fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32) { - self.window.begin_resize_drag(edge, button, x, y) - } - - #[inline] - fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { - self.window.set_skip_taskbar(skip) - } - - #[inline] - fn set_undecorated_shadow(&self, shadow: bool) { - self.window.set_undecorated_shadow(shadow) - } - - #[inline] - fn has_undecorated_shadow(&self) -> bool { - self.window.has_undecorated_shadow() - } - - #[inline] - fn set_rtl(&self, rtl: bool) { - self.window.set_rtl(rtl) - } - - #[inline] - fn set_overlay_icon(&self, icon: Option<&Icon>) { - self.window.set_overlay_icon(icon); - } -} - -/// Additional methods on `WindowBuilder` that are specific to Windows. -pub trait WindowBuilderExtWindows { - /// Sets a parent to the window to be created. - /// - /// A child window has the WS_CHILD style and is confined to the client area of its parent window. - /// - /// For more information, see - fn with_parent_window(self, parent: HWND) -> WindowBuilder; - - /// Set an owner to the window to be created. Can be used to create a dialog box, for example. - /// Can be used in combination with [`WindowExtWindows::set_enable(false)`](WindowExtWindows::set_enable) - /// on the owner window to create a modal dialog box. - /// - /// From MSDN: - /// - An owned window is always above its owner in the z-order. - /// - The system automatically destroys an owned window when its owner is destroyed. - /// - An owned window is hidden when its owner is minimized. - /// - /// For more information, see - fn with_owner_window(self, parent: HWND) -> WindowBuilder; - - /// Sets a menu on the window to be created. - /// - /// Parent and menu are mutually exclusive; a child window cannot have a menu! - /// - /// The menu must have been manually created beforehand with [`windows::Win32::UI::WindowsAndMessaging::CreateMenu`] - /// or similar. - /// - /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. - /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. - fn with_menu(self, menu: HMENU) -> WindowBuilder; - - /// This sets `ICON_BIG`. A good ceiling here is 256x256. - fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; - - /// This sets `WS_EX_NOREDIRECTIONBITMAP`. - fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder; - - /// Enables or disables drag and drop support (enabled by default). Will interfere with other crates - /// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of - /// `COINIT_APARTMENTTHREADED`) on the same thread. Note that tao may still attempt to initialize - /// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. - /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any tao functions. - /// See for more information. - fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; - - /// Whether to create the window icon with the taskbar icon or not. - fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; - - /// Customize the window class name. - fn with_window_classname>(self, classname: S) -> WindowBuilder; - - /// Shows or hides the background drop shadow for undecorated windows. - /// - /// The shadow is hidden by default. - /// Enabling the shadow causes a thin 1px line to appear on the top of the window. - fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder; - - /// Sets right-to-left layout. - fn with_rtl(self, rtl: bool) -> WindowBuilder; -} - -impl WindowBuilderExtWindows for WindowBuilder { - #[inline] - fn with_parent_window(mut self, parent: HWND) -> WindowBuilder { - self.platform_specific.parent = Parent::ChildOf(windows::Win32::Foundation::HWND(parent as _)); - self - } - - #[inline] - fn with_owner_window(mut self, parent: HWND) -> WindowBuilder { - self.platform_specific.parent = Parent::OwnedBy(windows::Win32::Foundation::HWND(parent as _)); - self - } - - #[inline] - fn with_menu(mut self, menu: HMENU) -> WindowBuilder { - self.platform_specific.menu = Some(windows::Win32::UI::WindowsAndMessaging::HMENU(menu as _)); - self - } - - #[inline] - fn with_taskbar_icon(mut self, taskbar_icon: Option) -> WindowBuilder { - self.platform_specific.taskbar_icon = taskbar_icon; - self - } - - #[inline] - fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder { - self.platform_specific.no_redirection_bitmap = flag; - self - } - - #[inline] - fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder { - self.platform_specific.drag_and_drop = flag; - self - } - - #[inline] - fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder { - self.platform_specific.skip_taskbar = skip; - self - } - - #[inline] - fn with_window_classname>(mut self, classname: S) -> WindowBuilder { - self.platform_specific.window_classname = classname.into(); - self - } - - #[inline] - fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder { - self.platform_specific.decoration_shadow = shadow; - self - } - - #[inline] - fn with_rtl(mut self, rtl: bool) -> WindowBuilder { - self.platform_specific.rtl = rtl; - self - } -} - -/// Additional methods on `MonitorHandle` that are specific to Windows. -pub trait MonitorHandleExtWindows { - /// Returns the name of the monitor adapter specific to the Win32 API. - fn native_id(&self) -> String; - - /// Returns the handle of the monitor - `HMONITOR`. - fn hmonitor(&self) -> isize; -} - -impl MonitorHandleExtWindows for MonitorHandle { - #[inline] - fn native_id(&self) -> String { - self.inner.native_identifier() - } - - #[inline] - fn hmonitor(&self) -> isize { - self.inner.hmonitor().0 as _ - } -} - -/// Additional methods on `DeviceId` that are specific to Windows. -pub trait DeviceIdExtWindows { - /// Returns an identifier that persistently refers to this specific device. - /// - /// Will return `None` if the device is no longer available. - fn persistent_identifier(&self) -> Option; -} - -impl DeviceIdExtWindows for DeviceId { - #[inline] - fn persistent_identifier(&self) -> Option { - self.0.persistent_identifier() - } -} - -/// Additional methods on `Icon` that are specific to Windows. -pub trait IconExtWindows: Sized { - /// Create an icon from a file path. - /// - /// Specify `size` to load a specific icon size from the file, or `None` to load the default - /// icon size from the file. - /// - /// In cases where the specified size does not exist in the file, Windows may perform scaling - /// to get an icon of the desired size. - fn from_path>(path: P, size: Option>) -> Result; - - /// Create an icon from a resource embedded in this executable or library. - /// - /// Specify `size` to load a specific icon size from the file, or `None` to load the default - /// icon size from the file. - /// - /// In cases where the specified size does not exist in the file, Windows may perform scaling - /// to get an icon of the desired size. - fn from_resource(ordinal: u16, size: Option>) -> Result; -} - -impl IconExtWindows for Icon { - fn from_path>(path: P, size: Option>) -> Result { - let win_icon = WinIcon::from_path(path, size)?; - Ok(Icon { inner: win_icon }) - } - - fn from_resource(ordinal: u16, size: Option>) -> Result { - let win_icon = WinIcon::from_resource(ordinal, size)?; - Ok(Icon { inner: win_icon }) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs deleted file mode 100644 index b78113853..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs +++ /dev/null @@ -1,1490 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - error, event, - event_loop::{self, ControlFlow}, - keyboard::{KeyCode, NativeKeyCode}, - monitor, - window::{self, ResizeDirection, Theme, WindowSizeConstraints}, -}; -use crossbeam_channel::{Receiver, Sender}; -use ndk::{ - configuration::Configuration, - looper::{ForeignLooper, Poll, ThreadLooper}, -}; -use once_cell::sync::Lazy; -use std::{ - collections::VecDeque, - error::Error, - fmt, - sync::RwLock, - time::{Duration, Instant}, -}; - -#[derive(Debug)] -pub struct JniCallError { - source: jni::errors::Error, - java_exception: Option, -} - -impl JniCallError { - fn new(source: jni::errors::Error, java_exception: Option) -> Self { - Self { - source, - java_exception, - } - } -} - -impl From for JniCallError { - fn from(source: jni::errors::Error) -> Self { - Self::new(source, None) - } -} - -impl fmt::Display for JniCallError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.java_exception { - Some(java_exception) => write!(f, "{}: {java_exception}", self.source), - None => self.source.fmt(f), - } - } -} - -impl Error for JniCallError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.source) - } -} - -/// Logs and clears a pending Java exception while preserving the original JNI error. -macro_rules! jni_handle_error { - ($env:ident, $e:expr) => {{ - let e = $e; - let message = (|| -> jni::errors::Result> { - if $env.exception_check()? { - let throwable = $env.exception_occurred()?; - $env.exception_clear()?; - - let message = $env - .call_method(&throwable, "toString", "()Ljava/lang/String;", &[])? - .l()?; - let message: jni::objects::JString = message.into(); - return $env.get_string(&message).map(|s| Some(s.into())); - } - - Ok(None) - })(); - - let java_exception = match message { - Ok(Some(message)) => { - log::error!( - "tao: JNI call failed at {}:{} with Java exception: {message}", - file!(), - line!() - ); - Some(message) - } - Ok(None) => { - log::error!("tao: JNI call failed at {}:{}: {e}", file!(), line!()); - None - } - Err(err) => { - let message = format!("failed to read Java exception: {err}"); - log::error!( - "tao: JNI call failed at {}:{}: {e}; {message}", - file!(), - line!() - ); - Some(message) - } - }; - - $crate::platform_impl::platform::JniCallError::new(e, java_exception) - }}; -} - -macro_rules! jni_call_method { - ($env:ident, $obj:expr, $method:expr, $sig:expr, $args:expr, $ret_typ:ident) => {{ - $env - .call_method($obj, $method, $sig, $args) - .and_then(|v| v.$ret_typ()) - .map_err(|e| jni_handle_error!($env, e)) - }}; - - ($env:ident, $obj:expr, $method:expr, $sig:expr, $ret_typ:ident) => { - jni_call_method!($env, $obj, $method, $sig, &[], $ret_typ) - }; -} - -pub mod ndk_glue; -use ndk_glue::{ActivityId, Event, Rect, WindowEvent}; - -static CONFIG: Lazy> = Lazy::new(|| RwLock::new(Configuration::new())); - -#[derive(Debug)] -pub enum OsError { - JniCallError(JniCallError), - NoAvailableActivity, -} - -impl std::error::Error for OsError {} -impl std::fmt::Display for OsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - OsError::JniCallError(e) => write!(f, "JNI error: {e}"), - OsError::NoAvailableActivity => write!(f, "no available activity"), - } - } -} - -enum EventSource { - Callback, - InputQueue, - User, -} - -fn poll(poll: Poll) -> Option { - match poll { - Poll::Event { ident, .. } => match ident { - ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback), - ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue), - _ => unreachable!(), - }, - Poll::Timeout => None, - Poll::Wake => Some(EventSource::User), - Poll::Callback => unreachable!(), - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct KeyEventExtra {} - -pub struct EventLoop { - window_target: event_loop::EventLoopWindowTarget, - receiver: Receiver, - sender_to_clone: Sender, - first_event: Option, - start_cause: event::StartCause, - looper: ThreadLooper, - running: bool, -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PlatformSpecificEventLoopAttributes {} - -macro_rules! call_event_handler { - ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ - if let ControlFlow::ExitWithCode(code) = $cf { - $event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code)); - } else { - $event_handler($event, $window_target, &mut $cf); - } - }}; -} - -impl EventLoop { - pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { - let (sender, receiver) = crossbeam_channel::unbounded(); - - Self { - window_target: event_loop::EventLoopWindowTarget { - p: EventLoopWindowTarget { - _marker: std::marker::PhantomData, - }, - _marker: std::marker::PhantomData, - }, - sender_to_clone: sender, - receiver, - first_event: None, - start_cause: event::StartCause::Init, - looper: ThreadLooper::for_thread().unwrap(), - running: false, - } - } - - pub fn run(mut self, event_handler: F) -> ! - where - F: - 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), - { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); - } - - pub fn run_return(&mut self, mut event_handler: F) -> i32 - where - F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), - { - let mut control_flow = ControlFlow::default(); - - 'event_loop: loop { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::NewEvents(self.start_cause) - ); - - let mut redraw_window_id = None; - let mut resized_window_id = None; - - match self.first_event.take() { - Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { - Event::Resume => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Resumed - ); - } - Event::WindowEvent { - id: window_id, - event, - } => match event { - WindowEvent::Resized => resized_window_id = Some(window_id), - WindowEvent::RedrawNeeded => redraw_window_id = Some(window_id), - WindowEvent::Focused(focused) => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Focused(focused) - } - ); - } - WindowEvent::Destroyed => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Destroyed, - } - ); - } - _ => {} - }, - Event::Pause => { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Suspended - ); - } - Event::Stop => self.running = false, - Event::Start => self.running = true, - Event::Opened => { - let urls = ndk_glue::take_intent_urls(); - if !urls.is_empty() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Opened { urls } - ); - } - } - //Event::ConfigChanged => { - // #[allow(deprecated)] // TODO: use ndk-context instead - // let am = ndk_glue::native_activity().asset_manager(); - // let config = Configuration::from_asset_manager(&am); - // let old_scale_factor = MonitorHandle.scale_factor(); - // *CONFIG.write().unwrap() = config; - // let scale_factor = MonitorHandle.scale_factor(); - // if (scale_factor - old_scale_factor).abs() < f64::EPSILON { - // let mut size = MonitorHandle.size(); - // let event = event::Event::WindowEvent { - // window_id: window::WindowId(WindowId), - // event: event::WindowEvent::ScaleFactorChanged { - // new_inner_size: &mut size, - // scale_factor, - // }, - // }; - // call_event_handler!(event_handler, self.window_target(), control_flow, event); - // } - //} - _ => {} - }, - - Some(EventSource::InputQueue) => { - /* - if let Some(input_queue) = ndk_glue::input_queue().as_ref() { - while let Ok(Some(event)) = input_queue.event() { - if let Some(event) = input_queue.pre_dispatch(event) { - let mut handled = true; - let window_id = window::WindowId(WindowId); - let device_id = event::DeviceId(DeviceId); - match &event { - InputEvent::MotionEvent(motion_event) => { - let phase = match motion_event.action() { - MotionAction::Down | MotionAction::PointerDown => { - Some(event::TouchPhase::Started) - } - MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended), - MotionAction::Move => Some(event::TouchPhase::Moved), - MotionAction::Cancel => Some(event::TouchPhase::Cancelled), - _ => { - handled = false; - None // TODO mouse events - } - }; - if let Some(phase) = phase { - let pointers: Box>> = match phase - { - event::TouchPhase::Started | event::TouchPhase::Ended => { - Box::new(std::iter::once( - motion_event.pointer_at_index(motion_event.pointer_index()), - )) - } - event::TouchPhase::Moved | event::TouchPhase::Cancelled => { - Box::new(motion_event.pointers()) - } - }; - - for pointer in pointers { - let location = PhysicalPosition { - x: pointer.x() as _, - y: pointer.y() as _, - }; - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Touch(event::Touch { - device_id, - phase, - location, - id: pointer.pointer_id() as u64, - force: None, - }), - }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event - ); - } - } - } - InputEvent::KeyEvent(key) => { - let state = match key.action() { - KeyAction::Down => event::ElementState::Pressed, - KeyAction::Up => event::ElementState::Released, - _ => event::ElementState::Released, - }; - - let keycode = key.key_code(); - let native = NativeKeyCode::Android(keycode.into()); - let physical_key = KeyCode::Unidentified(native); - let logical_key = keycode_to_logical(keycode, native); - // TODO: maybe use getUnicodeChar to get the logical key - - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::KeyboardInput { - device_id, - event: event::KeyEvent { - state, - physical_key, - logical_key, - location: keycode_to_location(keycode), - repeat: key.repeat_count() > 0, - text: None, - platform_specific: KeyEventExtra {}, - }, - is_synthetic: false, - }, - }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); - } - _ => {} - }; - input_queue.finish_event(event, handled); - } - } - } - */ - } - Some(EventSource::User) => { - while let Ok(event) = self.receiver.try_recv() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::UserEvent(event) - ); - } - } - None => {} - } - - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::MainEventsCleared - ); - - if let Some(window_id) = resized_window_id { - if self.running { - let size = MonitorHandle.size(); - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Resized(size), - }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); - } - } - - if let Some(window_id) = redraw_window_id { - if self.running { - let event = event::Event::RedrawRequested(window_id); - call_event_handler!(event_handler, self.window_target(), control_flow, event); - } - } - - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::RedrawEventsCleared - ); - - match control_flow { - ControlFlow::ExitWithCode(code) => { - self.first_event = poll( - self - .looper - .poll_once_timeout(Duration::from_millis(0)) - .unwrap(), - ); - self.start_cause = event::StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }; - - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::LoopDestroyed - ); - - break 'event_loop code; - } - ControlFlow::Poll => { - self.first_event = poll( - self - .looper - .poll_all_timeout(Duration::from_millis(0)) - .unwrap(), - ); - self.start_cause = event::StartCause::Poll; - } - ControlFlow::Wait => { - self.first_event = poll(self.looper.poll_all().unwrap()); - self.start_cause = event::StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - } - } - ControlFlow::WaitUntil(instant) => { - let start = Instant::now(); - let duration = if instant <= start { - Duration::default() - } else { - instant - start - }; - self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap()); - self.start_cause = if self.first_event.is_some() { - event::StartCause::WaitCancelled { - start, - requested_resume: Some(instant), - } - } else { - event::StartCause::ResumeTimeReached { - start, - requested_resume: instant, - } - } - } - } - } - } - - pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { - &self.window_target - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - queue: self.sender_to_clone.clone(), - looper: ForeignLooper::for_thread().expect("called from event loop thread"), - } - } -} - -pub struct EventLoopProxy { - queue: Sender, - looper: ForeignLooper, -} - -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { - _ = self.queue.try_send(event); - self.looper.wake(); - Ok(()) - } -} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - EventLoopProxy { - queue: self.queue.clone(), - looper: self.looper.clone(), - } - } -} - -#[derive(Clone)] -pub struct EventLoopWindowTarget { - _marker: std::marker::PhantomData, -} - -impl EventLoopWindowTarget { - pub fn primary_monitor(&self) -> Option { - Some(monitor::MonitorHandle { - inner: MonitorHandle, - }) - } - - #[inline] - pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { - warn!("`Window::monitor_from_point` is ignored on Android"); - None - } - - pub fn available_monitors(&self) -> VecDeque { - let mut v = VecDeque::with_capacity(1); - v.push_back(MonitorHandle); - v - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::Android( - rwh_06::AndroidDisplayHandle::new(), - )) - } - - pub fn cursor_position(&self) -> Result, error::ExternalError> { - debug!("`EventLoopWindowTarget::cursor_position` is ignored on Android"); - Ok((0, 0).into()) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct WindowId(ActivityId); - -impl WindowId { - pub fn dummy() -> Self { - WindowId(0) - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct DeviceId; - -impl DeviceId { - pub fn dummy() -> Self { - DeviceId - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub activity_id: Option, - pub activity_name: Option, - pub created_by_activity_name: Option, -} - -pub struct Window { - activity_id: ActivityId, - activity_name: String, -} - -impl Window { - pub fn new( - _el: &EventLoopWindowTarget, - _window_attrs: window::WindowAttributes, - pl_attrs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - // FIXME this ignores requested window attributes - - let (activity_id, activity_name) = match pl_attrs.activity_name { - Some(activity_name) => { - let ctx = if let Some(created_by_activity_name) = pl_attrs.created_by_activity_name { - ndk_glue::CONTEXTS - .lock() - .unwrap() - .values() - .find(|ctx| ctx.activity_name == created_by_activity_name) - .cloned() - } else { - ndk_glue::main_android_context() - } - .ok_or_else(|| os_error!(OsError::NoAvailableActivity))?; - let activity_id = ctx - .create_activity(&activity_name) - .map_err(|error| os_error!(OsError::JniCallError(error)))?; - (activity_id, activity_name) - } - None => ndk_glue::next_available_activity() - .map(|(activity_id, ctx)| (activity_id, ctx.activity_name.clone())) - .ok_or_else(|| os_error!(OsError::NoAvailableActivity))?, - }; - Ok(Self { - activity_id, - activity_name, - }) - } - - pub fn id(&self) -> WindowId { - WindowId(self.activity_id) - } - - pub fn primary_monitor(&self) -> Option { - Some(monitor::MonitorHandle { - inner: MonitorHandle, - }) - } - - pub fn available_monitors(&self) -> VecDeque { - let mut v = VecDeque::with_capacity(1); - v.push_back(MonitorHandle); - v - } - - #[inline] - pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { - warn!("`Window::monitor_from_point` is ignored on Android"); - None - } - - pub fn current_monitor(&self) -> Option { - Some(monitor::MonitorHandle { - inner: MonitorHandle, - }) - } - - pub fn scale_factor(&self) -> f64 { - MonitorHandle.scale_factor() - } - - pub fn request_redraw(&self) { - // TODO - } - - pub fn inner_position(&self) -> Result, error::NotSupportedError> { - Err(error::NotSupportedError::new()) - } - - pub fn outer_position(&self) -> Result, error::NotSupportedError> { - Err(error::NotSupportedError::new()) - } - - pub fn set_outer_position(&self, _position: Position) { - // no effect - } - - pub fn inner_size(&self) -> PhysicalSize { - self.outer_size() - } - - pub fn set_inner_size(&self, _size: Size) { - warn!("Cannot set window size on Android"); - } - - pub fn outer_size(&self) -> PhysicalSize { - MonitorHandle.size() - } - - pub fn set_min_inner_size(&self, _: Option) {} - pub fn set_max_inner_size(&self, _: Option) {} - pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) {} - - pub fn set_title(&self, _title: &str) {} - pub fn title(&self) -> String { - String::new() - } - - pub fn set_visible(&self, _visibility: bool) {} - - pub fn set_focus(&self) { - //FIXME: implementation goes here - warn!("set_focus not yet implemented on Android"); - } - - pub fn set_focusable(&self, _focusable: bool) { - warn!("set_focusable not yet implemented on Android"); - } - - pub fn is_focused(&self) -> bool { - log::warn!("`Window::is_focused` is ignored on Android"); - false - } - - pub fn is_always_on_top(&self) -> bool { - log::warn!("`Window::is_always_on_top` is ignored on Android"); - false - } - - pub fn set_resizable(&self, _resizeable: bool) { - warn!("`Window::set_resizable` is ignored on Android") - } - - pub fn set_minimizable(&self, _minimizable: bool) { - warn!("`Window::set_minimizable` is ignored on Android") - } - - pub fn set_maximizable(&self, _maximizable: bool) { - warn!("`Window::set_maximizable` is ignored on Android") - } - - pub fn set_closable(&self, _closable: bool) { - warn!("`Window::set_closable` is ignored on Android") - } - - pub fn set_minimized(&self, _minimized: bool) {} - - pub fn set_maximized(&self, _maximized: bool) {} - - pub fn is_maximized(&self) -> bool { - false - } - - pub fn is_minimized(&self) -> bool { - false - } - - pub fn is_visible(&self) -> bool { - log::warn!("`Window::is_visible` is ignored on Android"); - false - } - - pub fn is_resizable(&self) -> bool { - warn!("`Window::is_resizable` is ignored on Android"); - false - } - - pub fn is_minimizable(&self) -> bool { - warn!("`Window::is_minimizable` is ignored on Android"); - false - } - - pub fn is_maximizable(&self) -> bool { - warn!("`Window::is_maximizable` is ignored on Android"); - false - } - - pub fn is_closable(&self) -> bool { - warn!("`Window::is_closable` is ignored on Android"); - false - } - - pub fn is_decorated(&self) -> bool { - warn!("`Window::is_decorated` is ignored on Android"); - false - } - - pub fn set_fullscreen(&self, _monitor: Option) { - warn!("Cannot set fullscreen on Android"); - } - - pub fn fullscreen(&self) -> Option { - None - } - - pub fn set_decorations(&self, _decorations: bool) {} - - pub fn set_always_on_bottom(&self, _always_on_bottom: bool) {} - - pub fn set_always_on_top(&self, _always_on_top: bool) {} - - pub fn set_window_icon(&self, _window_icon: Option) {} - - pub fn set_ime_position(&self, _position: Position) {} - - pub fn request_user_attention(&self, _request_type: Option) {} - - pub fn set_cursor_icon(&self, _: window::CursorIcon) {} - - pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { - Err(error::ExternalError::NotSupported( - error::NotSupportedError::new(), - )) - } - - pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { - Err(error::ExternalError::NotSupported( - error::NotSupportedError::new(), - )) - } - - pub fn set_cursor_visible(&self, _: bool) {} - - pub fn drag_window(&self) -> Result<(), error::ExternalError> { - Err(error::ExternalError::NotSupported( - error::NotSupportedError::new(), - )) - } - - pub fn drag_resize_window( - &self, - _direction: ResizeDirection, - ) -> Result<(), error::ExternalError> { - Err(error::ExternalError::NotSupported( - error::NotSupportedError::new(), - )) - } - - pub fn set_background_color(&self, _color: Option) {} - - pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), error::ExternalError> { - Err(error::ExternalError::NotSupported( - error::NotSupportedError::new(), - )) - } - - pub fn cursor_position(&self) -> Result, error::ExternalError> { - debug!("`Window::cursor_position` is ignored on Android"); - Ok((0, 0).into()) - } - - #[cfg(feature = "rwh_04")] - pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { - // TODO: Use main activity instead? - let mut handle = rwh_04::AndroidNdkHandle::empty(); - if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { - handle.a_native_window = w.as_obj().as_raw() as *mut _; - } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); - }; - rwh_04::RawWindowHandle::AndroidNdk(handle) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { - // TODO: Use main activity instead? - let mut handle = rwh_05::AndroidNdkWindowHandle::empty(); - if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { - handle.a_native_window = w.as_obj().as_raw() as *mut _; - } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); - }; - rwh_05::RawWindowHandle::AndroidNdk(handle) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - pub fn raw_window_handle_rwh_06(&self) -> Result { - // TODO: Use main activity instead? - if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { - let native_window = - unsafe { std::ptr::NonNull::new_unchecked(w.as_obj().as_raw() as *mut _) }; - // native_window shuldn't be null - let handle = rwh_06::AndroidNdkWindowHandle::new(native_window); - Ok(rwh_06::RawWindowHandle::AndroidNdk(handle)) - } else { - Err(rwh_06::HandleError::Unavailable) - } - } - - #[cfg(feature = "rwh_06")] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::Android( - rwh_06::AndroidDisplayHandle::new(), - )) - } - pub fn config(&self) -> Configuration { - CONFIG.read().unwrap().clone() - } - - pub fn content_rect(&self) -> Rect { - ndk_glue::content_rect() - } - - pub fn activity_name(&self) -> &str { - &self.activity_name - } - - pub fn theme(&self) -> Theme { - Theme::Light - } -} - -pub(crate) use crate::icon::NoIcon as PlatformIcon; - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct MonitorHandle; - -fn current_window_metrics_size( - env: &mut jni::JNIEnv<'_>, - window_manager: &jni::objects::JObject<'_>, -) -> Option> { - let metrics = jni_call_method!( - env, - window_manager, - "getCurrentWindowMetrics", - "()Landroid/view/WindowMetrics;", - l - ) - .ok()?; - - let rect = jni_call_method!(env, &metrics, "getBounds", "()Landroid/graphics/Rect;", l).ok()?; - let width = jni_call_method!(env, &rect, "width", "()I", i).ok()?; - let height = jni_call_method!(env, &rect, "height", "()I", i).ok()?; - Some(PhysicalSize::new(width as u32, height as u32)) -} - -fn legacy_display_metrics_size( - env: &mut jni::JNIEnv<'_>, - window_manager: &jni::objects::JObject<'_>, -) -> Option> { - let display = jni_call_method!( - env, - window_manager, - "getDefaultDisplay", - "()Landroid/view/Display;", - l - ) - .ok()?; - let display_metrics = env - .new_object("android/util/DisplayMetrics", "()V", &[]) - .ok()?; - - jni_call_method!( - env, - &display, - "getRealMetrics", - "(Landroid/util/DisplayMetrics;)V", - &[(&display_metrics).into()], - v - ) - .ok()?; - - let width = env - .get_field(&display_metrics, "widthPixels", "I") - .and_then(|v| v.i()) - .ok()?; - let height = env - .get_field(&display_metrics, "heightPixels", "I") - .and_then(|v| v.i()) - .ok()?; - - Some(PhysicalSize::new(width as u32, height as u32)) -} - -impl MonitorHandle { - pub fn name(&self) -> Option { - Some("Android Device".to_owned()) - } - - pub fn size(&self) -> PhysicalSize { - // TODO: support multi-window (main_window_manager might be incorrect) - - // TODO decide how to get JNIENV - let window_manager = ndk_glue::main_window_manager(); - let Some(w) = window_manager.as_ref() else { - return PhysicalSize::new(0, 0); - }; - let Some(ctx) = ndk_glue::main_android_context() else { - return PhysicalSize::new(0, 0); - }; - let Ok(vm) = (unsafe { jni::JavaVM::from_raw(ctx.java_vm.cast()) }) else { - return PhysicalSize::new(0, 0); - }; - let Ok(mut env) = vm.attach_current_thread() else { - return PhysicalSize::new(0, 0); - }; - let window_manager = w.as_obj(); - - let sdk_int = env - .get_static_field("android/os/Build$VERSION", "SDK_INT", "I") - .and_then(|v| v.i()) - .unwrap_or(0); - - let metrics_size = if sdk_int >= 30 { - current_window_metrics_size(&mut env, window_manager) - } else { - legacy_display_metrics_size(&mut env, window_manager) - }; - - if metrics_size.is_none() && env.exception_check().unwrap_or(false) { - let _ = env.exception_clear(); - } - - metrics_size.unwrap_or(PhysicalSize::new(0, 0)) - } - - pub fn position(&self) -> PhysicalPosition { - (0, 0).into() - } - - pub fn scale_factor(&self) -> f64 { - let config = CONFIG.read().unwrap(); - config - .density() - .map(|dpi| dpi as f64 / 160.0) - .unwrap_or(1.0) - } - - pub fn video_modes(&self) -> impl Iterator { - let size = self.size().into(); - // FIXME this is not the real refresh rate - // (it is guarunteed to support 32 bit color though) - let v = vec![monitor::VideoMode { - video_mode: VideoMode { - size, - bit_depth: 32, - refresh_rate: 60, - monitor: self.clone(), - }, - }]; - - v.into_iter() - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct VideoMode { - size: (u32, u32), - bit_depth: u16, - refresh_rate: u16, - monitor: MonitorHandle, -} - -impl VideoMode { - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - pub fn monitor(&self) -> monitor::MonitorHandle { - monitor::MonitorHandle { - inner: self.monitor.clone(), - } - } -} - -/* -fn keycode_to_logical(keycode: ndk::event::Keycode, native: NativeKeyCode) -> Key<'static> { - use ndk::event::Keycode::*; - - // The android `Keycode` is sort-of layout dependent. More specifically - // if I press the Z key using a US layout, then I get KEYCODE_Z, - // but if I press the same key after switching to a HUN layout, I get - // KEYCODE_Y. - // - // To prevents us from using this value to determine the `physical_key` - // (also know as winit's `KeyCode`) - // - // Unfortunately the documentation says that the scancode values - // "are not reliable and vary from device to device". Which seems to mean - // that there's no way to reliably get the physical_key on android. - - match keycode { - Unknown => Key::Unidentified(native), - - // Can be added on demand - SoftLeft => Key::Unidentified(native), - SoftRight => Key::Unidentified(native), - - // Using `BrowserHome` instead of `GoHome` according to - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - Home => Key::BrowserHome, - Back => Key::BrowserBack, - Call => Key::Call, - Endcall => Key::EndCall, - - //------------------------------------------------------------------------------- - // Reporting unidentified, because the specific character is layout dependent. - // (I'm not sure though) - Keycode0 => Key::Unidentified(native), - Keycode1 => Key::Unidentified(native), - Keycode2 => Key::Unidentified(native), - Keycode3 => Key::Unidentified(native), - Keycode4 => Key::Unidentified(native), - Keycode5 => Key::Unidentified(native), - Keycode6 => Key::Unidentified(native), - Keycode7 => Key::Unidentified(native), - Keycode8 => Key::Unidentified(native), - Keycode9 => Key::Unidentified(native), - Star => Key::Unidentified(native), - Pound => Key::Unidentified(native), - A => Key::Unidentified(native), - B => Key::Unidentified(native), - C => Key::Unidentified(native), - D => Key::Unidentified(native), - E => Key::Unidentified(native), - F => Key::Unidentified(native), - G => Key::Unidentified(native), - H => Key::Unidentified(native), - I => Key::Unidentified(native), - J => Key::Unidentified(native), - K => Key::Unidentified(native), - L => Key::Unidentified(native), - M => Key::Unidentified(native), - N => Key::Unidentified(native), - O => Key::Unidentified(native), - P => Key::Unidentified(native), - Q => Key::Unidentified(native), - R => Key::Unidentified(native), - S => Key::Unidentified(native), - T => Key::Unidentified(native), - U => Key::Unidentified(native), - V => Key::Unidentified(native), - W => Key::Unidentified(native), - X => Key::Unidentified(native), - Y => Key::Unidentified(native), - Z => Key::Unidentified(native), - Comma => Key::Unidentified(native), - Period => Key::Unidentified(native), - Grave => Key::Unidentified(native), - Minus => Key::Unidentified(native), - Equals => Key::Unidentified(native), - LeftBracket => Key::Unidentified(native), - RightBracket => Key::Unidentified(native), - Backslash => Key::Unidentified(native), - Semicolon => Key::Unidentified(native), - Apostrophe => Key::Unidentified(native), - Slash => Key::Unidentified(native), - At => Key::Unidentified(native), - Plus => Key::Unidentified(native), - //------------------------------------------------------------------------------- - DpadUp => Key::ArrowUp, - DpadDown => Key::ArrowDown, - DpadLeft => Key::ArrowLeft, - DpadRight => Key::ArrowRight, - DpadCenter => Key::Enter, - - VolumeUp => Key::AudioVolumeUp, - VolumeDown => Key::AudioVolumeDown, - Power => Key::Power, - Camera => Key::Camera, - Clear => Key::Clear, - - AltLeft => Key::Alt, - AltRight => Key::Alt, - ShiftLeft => Key::Shift, - ShiftRight => Key::Shift, - Tab => Key::Tab, - Space => Key::Space, - Sym => Key::Symbol, - Explorer => Key::LaunchWebBrowser, - Envelope => Key::LaunchMail, - Enter => Key::Enter, - Del => Key::Backspace, - - // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM - Num => Key::Alt, - - Headsethook => Key::HeadsetHook, - Focus => Key::CameraFocus, - - Menu => Key::Unidentified(native), - - Notification => Key::Notification, - Search => Key::BrowserSearch, - MediaPlayPause => Key::MediaPlayPause, - MediaStop => Key::MediaStop, - MediaNext => Key::MediaTrackNext, - MediaPrevious => Key::MediaTrackPrevious, - MediaRewind => Key::MediaRewind, - MediaFastForward => Key::MediaFastForward, - Mute => Key::MicrophoneVolumeMute, - PageUp => Key::PageUp, - PageDown => Key::PageDown, - Pictsymbols => Key::Unidentified(native), - SwitchCharset => Key::Unidentified(native), - - // ----------------------------------------------------------------- - // Gamepad events should be exposed through a separate API, not - // keyboard events - ButtonA => Key::Unidentified(native), - ButtonB => Key::Unidentified(native), - ButtonC => Key::Unidentified(native), - ButtonX => Key::Unidentified(native), - ButtonY => Key::Unidentified(native), - ButtonZ => Key::Unidentified(native), - ButtonL1 => Key::Unidentified(native), - ButtonR1 => Key::Unidentified(native), - ButtonL2 => Key::Unidentified(native), - ButtonR2 => Key::Unidentified(native), - ButtonThumbl => Key::Unidentified(native), - ButtonThumbr => Key::Unidentified(native), - ButtonStart => Key::Unidentified(native), - ButtonSelect => Key::Unidentified(native), - ButtonMode => Key::Unidentified(native), - // ----------------------------------------------------------------- - Escape => Key::Escape, - ForwardDel => Key::Delete, - CtrlLeft => Key::Control, - CtrlRight => Key::Control, - CapsLock => Key::CapsLock, - ScrollLock => Key::ScrollLock, - MetaLeft => Key::Super, - MetaRight => Key::Super, - Function => Key::Fn, - Sysrq => Key::PrintScreen, - Break => Key::Pause, - MoveHome => Key::Home, - MoveEnd => Key::End, - Insert => Key::Insert, - Forward => Key::BrowserForward, - MediaPlay => Key::MediaPlay, - MediaPause => Key::MediaPause, - MediaClose => Key::MediaClose, - MediaEject => Key::Eject, - MediaRecord => Key::MediaRecord, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - NumLock => Key::NumLock, - Numpad0 => Key::Unidentified(native), - Numpad1 => Key::Unidentified(native), - Numpad2 => Key::Unidentified(native), - Numpad3 => Key::Unidentified(native), - Numpad4 => Key::Unidentified(native), - Numpad5 => Key::Unidentified(native), - Numpad6 => Key::Unidentified(native), - Numpad7 => Key::Unidentified(native), - Numpad8 => Key::Unidentified(native), - Numpad9 => Key::Unidentified(native), - NumpadDivide => Key::Unidentified(native), - NumpadMultiply => Key::Unidentified(native), - NumpadSubtract => Key::Unidentified(native), - NumpadAdd => Key::Unidentified(native), - NumpadDot => Key::Unidentified(native), - NumpadComma => Key::Unidentified(native), - NumpadEnter => Key::Unidentified(native), - NumpadEquals => Key::Unidentified(native), - NumpadLeftParen => Key::Unidentified(native), - NumpadRightParen => Key::Unidentified(native), - - VolumeMute => Key::AudioVolumeMute, - Info => Key::Info, - ChannelUp => Key::ChannelUp, - ChannelDown => Key::ChannelDown, - ZoomIn => Key::ZoomIn, - ZoomOut => Key::ZoomOut, - Tv => Key::TV, - Window => Key::Unidentified(native), - Guide => Key::Guide, - Dvr => Key::DVR, - Bookmark => Key::BrowserFavorites, - Captions => Key::ClosedCaptionToggle, - Settings => Key::Settings, - TvPower => Key::TVPower, - TvInput => Key::TVInput, - StbPower => Key::STBPower, - StbInput => Key::STBInput, - AvrPower => Key::AVRPower, - AvrInput => Key::AVRInput, - ProgRed => Key::ColorF0Red, - ProgGreen => Key::ColorF1Green, - ProgYellow => Key::ColorF2Yellow, - ProgBlue => Key::ColorF3Blue, - AppSwitch => Key::AppSwitch, - Button1 => Key::Unidentified(native), - Button2 => Key::Unidentified(native), - Button3 => Key::Unidentified(native), - Button4 => Key::Unidentified(native), - Button5 => Key::Unidentified(native), - Button6 => Key::Unidentified(native), - Button7 => Key::Unidentified(native), - Button8 => Key::Unidentified(native), - Button9 => Key::Unidentified(native), - Button10 => Key::Unidentified(native), - Button11 => Key::Unidentified(native), - Button12 => Key::Unidentified(native), - Button13 => Key::Unidentified(native), - Button14 => Key::Unidentified(native), - Button15 => Key::Unidentified(native), - Button16 => Key::Unidentified(native), - LanguageSwitch => Key::GroupNext, - MannerMode => Key::MannerMode, - Keycode3dMode => Key::TV3DMode, - Contacts => Key::LaunchContacts, - Calendar => Key::LaunchCalendar, - Music => Key::LaunchMusicPlayer, - Calculator => Key::LaunchApplication2, - ZenkakuHankaku => Key::ZenkakuHankaku, - Eisu => Key::Eisu, - Muhenkan => Key::NonConvert, - Henkan => Key::Convert, - KatakanaHiragana => Key::HiraganaKatakana, - Yen => Key::Unidentified(native), - Ro => Key::Unidentified(native), - Kana => Key::KanjiMode, - Assist => Key::Unidentified(native), - BrightnessDown => Key::BrightnessDown, - BrightnessUp => Key::BrightnessUp, - MediaAudioTrack => Key::MediaAudioTrack, - Sleep => Key::Standby, - Wakeup => Key::WakeUp, - Pairing => Key::Pairing, - MediaTopMenu => Key::MediaTopMenu, - Keycode11 => Key::Unidentified(native), - Keycode12 => Key::Unidentified(native), - LastChannel => Key::MediaLast, - TvDataService => Key::TVDataService, - VoiceAssist => Key::VoiceDial, - TvRadioService => Key::TVRadioService, - TvTeletext => Key::Teletext, - TvNumberEntry => Key::TVNumberEntry, - TvTerrestrialAnalog => Key::TVTerrestrialAnalog, - TvTerrestrialDigital => Key::TVTerrestrialDigital, - TvSatellite => Key::TVSatellite, - TvSatelliteBs => Key::TVSatelliteBS, - TvSatelliteCs => Key::TVSatelliteCS, - TvSatelliteService => Key::TVSatelliteToggle, - TvNetwork => Key::TVNetwork, - TvAntennaCable => Key::TVAntennaCable, - TvInputHdmi1 => Key::TVInputHDMI1, - TvInputHdmi2 => Key::TVInputHDMI2, - TvInputHdmi3 => Key::TVInputHDMI3, - TvInputHdmi4 => Key::TVInputHDMI4, - TvInputComposite1 => Key::TVInputComposite1, - TvInputComposite2 => Key::TVInputComposite2, - TvInputComponent1 => Key::TVInputComponent1, - TvInputComponent2 => Key::TVInputComponent2, - TvInputVga1 => Key::TVInputVGA1, - TvAudioDescription => Key::TVAudioDescription, - TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, - TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, - TvZoomMode => Key::ZoomToggle, - TvContentsMenu => Key::TVContentsMenu, - TvMediaContextMenu => Key::TVMediaContext, - TvTimerProgramming => Key::TVTimer, - Help => Key::Help, - NavigatePrevious => Key::NavigatePrevious, - NavigateNext => Key::NavigateNext, - NavigateIn => Key::NavigateIn, - NavigateOut => Key::NavigateOut, - StemPrimary => Key::Unidentified(native), - Stem1 => Key::Unidentified(native), - Stem2 => Key::Unidentified(native), - Stem3 => Key::Unidentified(native), - DpadUpLeft => Key::Unidentified(native), - DpadDownLeft => Key::Unidentified(native), - DpadUpRight => Key::Unidentified(native), - DpadDownRight => Key::Unidentified(native), - MediaSkipForward => Key::MediaSkipForward, - MediaSkipBackward => Key::MediaSkipBackward, - MediaStepForward => Key::MediaStepForward, - MediaStepBackward => Key::MediaStepBackward, - SoftSleep => Key::Unidentified(native), - Cut => Key::Cut, - Copy => Key::Copy, - Paste => Key::Paste, - SystemNavigationUp => Key::Unidentified(native), - SystemNavigationDown => Key::Unidentified(native), - SystemNavigationLeft => Key::Unidentified(native), - SystemNavigationRight => Key::Unidentified(native), - AllApps => Key::Unidentified(native), - Refresh => Key::BrowserRefresh, - ThumbsUp => Key::Unidentified(native), - ThumbsDown => Key::Unidentified(native), - ProfileSwitch => Key::Unidentified(native), - _ => Key::Unidentified(native), - } -} - -fn keycode_to_location(keycode: ndk::event::Keycode) -> KeyLocation { - use ndk::event::Keycode::*; - - match keycode { - AltLeft => KeyLocation::Left, - AltRight => KeyLocation::Right, - ShiftLeft => KeyLocation::Left, - ShiftRight => KeyLocation::Right, - - // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM - Num => KeyLocation::Left, - - CtrlLeft => KeyLocation::Left, - CtrlRight => KeyLocation::Right, - MetaLeft => KeyLocation::Left, - MetaRight => KeyLocation::Right, - - NumLock => KeyLocation::Numpad, - Numpad0 => KeyLocation::Numpad, - Numpad1 => KeyLocation::Numpad, - Numpad2 => KeyLocation::Numpad, - Numpad3 => KeyLocation::Numpad, - Numpad4 => KeyLocation::Numpad, - Numpad5 => KeyLocation::Numpad, - Numpad6 => KeyLocation::Numpad, - Numpad7 => KeyLocation::Numpad, - Numpad8 => KeyLocation::Numpad, - Numpad9 => KeyLocation::Numpad, - NumpadDivide => KeyLocation::Numpad, - NumpadMultiply => KeyLocation::Numpad, - NumpadSubtract => KeyLocation::Numpad, - NumpadAdd => KeyLocation::Numpad, - NumpadDot => KeyLocation::Numpad, - NumpadComma => KeyLocation::Numpad, - NumpadEnter => KeyLocation::Numpad, - NumpadEquals => KeyLocation::Numpad, - NumpadLeftParen => KeyLocation::Numpad, - NumpadRightParen => KeyLocation::Numpad, - - _ => KeyLocation::Standard, - } -} -*/ - -// FIXME: Implement android -pub fn keycode_to_scancode(_code: KeyCode) -> Option { - None -} - -pub fn keycode_from_scancode(_scancode: u32) -> KeyCode { - KeyCode::Unidentified(NativeKeyCode::Unidentified) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs deleted file mode 100644 index 396936a9e..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs +++ /dev/null @@ -1,786 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::window::WindowId; -use crossbeam_channel::Sender; -pub use jni::{ - self, - errors::Result as JniResult, - objects::{GlobalRef, JClass, JMap, JObject, JString}, - sys::jobject, - JNIEnv, -}; -use log::Level; -pub use ndk; -use ndk::{ - input_queue::InputQueue, - looper::{FdEvent, ForeignLooper, ThreadLooper}, -}; -use once_cell::sync::{Lazy, OnceCell}; -use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; -use std::{ - collections::{BTreeMap, HashSet}, - ffi::{c_void, CStr, CString}, - fs::File, - io::{BufRead, BufReader}, - os::unix::prelude::*, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Condvar, Mutex, RwLock, RwLockReadGuard, - }, - thread, - time::Duration, -}; - -/// Android pacakge name that could be used to reference classes -/// in the android project. -pub static PACKAGE: OnceCell<&str> = OnceCell::new(); - -/// Character set for encoding text content in data URLs. -/// Encodes all control characters and special characters that might cause issues in URLs. -const DATA_URL_ENCODING_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'#') - .add(b'%') - .add(b'&') - .add(b'<') - .add(b'>') - .add(b'?') - .add(b'[') - .add(b'\\') - .add(b']') - .add(b'^') - .add(b'`') - .add(b'{') - .add(b'|') - .add(b'}'); - -/// Generate JNI compilant functions that are necessary for -/// building android apps with tao. -/// -/// Arguments in order: -/// 1. android app domain name in reverse snake_case as an ident (for ex: com_example) -/// 2. android package anme (for ex: wryapp) -/// 3. the android activity that has external linking for the following functions and calls them: -/// - `private external fun onActivityCreate(activity: WryActivity)`` -/// - `private external fun start()` -/// - `private external fun resume()` -/// - `private external fun pause()` -/// - `private external fun stop()` -/// - `private external fun onActivitySaveInstanceState()` -/// - `private external fun onActivityDestroy(activity: WryActivity)` -/// - `private external fun onActivityLowMemory()` -/// - `private external fun onWindowFocusChanged(activity: WryActivity, focus: Boolean)` -/// 4. a on_activity_create function that will be ran once after the `onActivityCreate` function above. -/// 5. the main entry point of your android application. -#[rustfmt::skip] -#[macro_export] -macro_rules! android_binding { - ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident) => { - ::tao::android_binding!($domain, $package, $activity, $setup, $main, ::tao) - }; - ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident, $tao:path) => {{ - // NOTE: be careful when changing how this use statement is written - use $tao::{platform::android::prelude::android_fn, platform::android::prelude::*}; - fn _____tao_store_package_name__() { - PACKAGE.get_or_init(move || generate_package_name!($domain, $package)); - } - - android_fn!( - $domain, - $package, - $activity, - create, - [JObject], - __VOID__, - [$main], - ); - android_fn!( - $domain, - $package, - $activity, - onActivityCreate, - [JObject], - __VOID__, - [$on_activity_create], - _____tao_store_package_name__, - ); - android_fn!($domain, $package, $activity, start, [JObject]); - android_fn!($domain, $package, $activity, stop, [JObject]); - android_fn!($domain, $package, $activity, resume, [JObject]); - android_fn!($domain, $package, $activity, pause, [JObject]); - android_fn!($domain, $package, $activity, onActivitySaveInstanceState, [JObject]); - android_fn!($domain, $package, $activity, onActivityDestroy, [JObject]); - android_fn!($domain, $package, $activity, onActivityLowMemory, [JObject]); - android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject,i32]); - android_fn!($domain, $package, $activity, onNewIntent, [JObject]); - }}; -} - -/// `ndk-glue` macros register the reading end of an event pipe with the -/// main [`ThreadLooper`] under this `ident`. -/// When returned from [`ThreadLooper::poll_*`](ThreadLooper::poll_once) -/// an event can be retrieved from [`poll_events()`]. -pub const NDK_GLUE_LOOPER_EVENT_PIPE_IDENT: i32 = 0; - -/// The [`InputQueue`] received from Android is registered with the main -/// [`ThreadLooper`] under this `ident`. -/// When returned from [`ThreadLooper::poll_*`](ThreadLooper::poll_once) -/// an event can be retrieved from [`input_queue()`]. -pub const NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT: i32 = 1; - -pub fn android_log(level: Level, tag: &CStr, msg: &CStr) { - let prio = match level { - Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, - Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, - Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, - Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, - Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, - }; - unsafe { - ndk_sys::__android_log_write(prio.0 as _, tag.as_ptr(), msg.as_ptr()); - } -} - -fn find_class<'a>( - env: &mut JNIEnv<'a>, - activity: &JObject<'_>, - name: String, -) -> Result, super::JniCallError> { - let class_name = env.new_string(name.replace('/', "."))?; - let my_class = jni_call_method!( - env, - activity, - "getAppClass", - "(Ljava/lang/String;)Ljava/lang/Class;", - &[(&class_name).into()], - l - )?; - Ok(my_class.into()) -} - -#[derive(Clone, Debug)] -pub struct AndroidContext { - pub java_vm: *mut c_void, - pub context_jobject: *mut c_void, - pub activity_name: String, - pub window_created: bool, -} - -impl AndroidContext { - pub fn create_activity(&self, activity_name: &str) -> Result { - let vm = unsafe { jni::JavaVM::from_raw(self.java_vm.cast()) }?; - let mut env = vm.attach_current_thread_as_daemon()?; - let main_activity = unsafe { JObject::from_raw(self.context_jobject.cast()) }; - - let activity_class = find_class( - &mut env, - &main_activity, - format!("{}/{activity_name}", PACKAGE.get().unwrap()), - )?; - - let activity_id = jni_call_method!( - env, - &main_activity, - "startActivity", - "(Ljava/lang/Class;)I", - &[(&activity_class).into()], - i - )?; - - let (tx, rx) = crossbeam_channel::bounded(1); - ACTIVITY_CREATED_SENDERS - .lock() - .unwrap() - .insert(activity_id, tx); - rx.recv_timeout(Duration::from_secs(5)).map_err(|e| { - log::error!("failed to create activity {activity_name}: {e}"); - super::JniCallError::new( - jni::errors::Error::JniCall(jni::errors::JniError::Unknown), - Some(e.to_string()), - ) - })?; - - Ok(activity_id) - } -} - -unsafe impl Send for AndroidContext {} -unsafe impl Sync for AndroidContext {} - -pub type ActivityId = i32; - -pub(crate) static CONTEXTS: Lazy>> = - Lazy::new(Default::default); -static WINDOW_MANAGER: Lazy>> = Lazy::new(Default::default); -pub(crate) static ACTIVITY_CREATED_SENDERS: Lazy>>> = - Lazy::new(Default::default); -static INTENT_URLS: Lazy>> = Lazy::new(Default::default); -static INPUT_QUEUE: Lazy>> = Lazy::new(Default::default); -static CONTENT_RECT: Lazy> = Lazy::new(Default::default); -static LOOPER: Lazy>> = Lazy::new(Default::default); -static DID_RESUME: AtomicBool = AtomicBool::new(false); - -pub fn main_window_manager() -> Option { - WINDOW_MANAGER.lock().unwrap().values().next().cloned() -} - -pub fn activity_window_manager(activity_id: ActivityId) -> Option { - WINDOW_MANAGER.lock().unwrap().get(&activity_id).cloned() -} - -pub fn window_manager(activity_id: ActivityId) -> Option { - WINDOW_MANAGER.lock().unwrap().get(&activity_id).cloned() -} - -pub fn input_queue() -> RwLockReadGuard<'static, Option> { - INPUT_QUEUE.read().unwrap() -} - -pub fn content_rect() -> Rect { - CONTENT_RECT.read().unwrap().clone() -} - -pub fn main_android_context() -> Option { - CONTEXTS.lock().unwrap().values().next().cloned() -} - -pub fn next_available_activity() -> Option<(ActivityId, AndroidContext)> { - CONTEXTS - .lock() - .unwrap() - .iter() - .filter(|(_, ctx)| !ctx.window_created) - .next() - .map(|(id, ctx)| (*id, ctx.clone())) -} - -pub static PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { - let mut pipe: [RawFd; 2] = Default::default(); - unsafe { libc::pipe(pipe.as_mut_ptr()) }; - pipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) -}); - -pub fn poll_events() -> Option { - unsafe { - let size = std::mem::size_of::(); - let mut event = Event::Start; - if libc::read(PIPE[0].as_raw_fd(), &mut event as *mut _ as *mut _, size) - == size as libc::ssize_t - { - Some(event) - } else { - None - } - } -} - -pub fn take_intent_urls() -> Vec { - INTENT_URLS.lock().unwrap().drain(..).collect() -} - -unsafe fn wake(event: Event) { - log::trace!("{:?}", event); - let size = std::mem::size_of::(); - let res = libc::write(PIPE[1].as_raw_fd(), &event as *const _ as *const _, size); - assert_eq!(res, size as libc::ssize_t); -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Rect { - pub left: u32, - pub top: u32, - pub right: u32, - pub bottom: u32, -} - -// event must be copyable to be used in the event loop -#[derive(Clone, Debug, Eq, PartialEq, Copy)] -#[repr(u8)] -pub enum Event { - Start, - Resume, - Pause, - Stop, - LowMemory, - WindowEvent { id: WindowId, event: WindowEvent }, - ContentRectChanged, - Opened, -} - -#[derive(Clone, Debug, Eq, PartialEq, Copy)] -#[repr(u8)] -pub enum WindowEvent { - Focused(bool), - Created, - Resized, - RedrawNeeded, - Destroyed, -} - -pub unsafe fn create(_env: JNIEnv, _: JClass, _: JObject, main: fn()) { - let logpipe = { - let mut logpipe: [RawFd; 2] = Default::default(); - libc::pipe(logpipe.as_mut_ptr()); - libc::dup2(logpipe[1], libc::STDOUT_FILENO); - libc::dup2(logpipe[1], libc::STDERR_FILENO); - - logpipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) - }; - thread::spawn(move || { - let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); - let file = File::from_raw_fd(logpipe[0].as_raw_fd()); - let mut reader = BufReader::new(file); - let mut buffer = String::new(); - loop { - buffer.clear(); - if let Ok(len) = reader.read_line(&mut buffer) { - if len == 0 { - break; - } else if let Ok(msg) = CString::new(buffer.clone()) { - android_log(Level::Info, tag, &msg); - } - } - } - }); - - let looper_ready = Arc::new(Condvar::new()); - let signal_looper_ready = looper_ready.clone(); - - thread::spawn(move || { - let looper = ThreadLooper::prepare(); - let foreign = looper.into_foreign(); - foreign - .add_fd( - PIPE[0].as_fd(), - NDK_GLUE_LOOPER_EVENT_PIPE_IDENT, - FdEvent::INPUT, - std::ptr::null_mut(), - ) - .unwrap(); - - { - let mut locked_looper = LOOPER.lock().unwrap(); - *locked_looper = Some(foreign); - signal_looper_ready.notify_one(); - } - - main(); - }); - - // Don't return from this function (`ANativeActivity_onCreate`) until the thread - // has created its `ThreadLooper` and assigned it to the static `LOOPER` variable. - let locked_looper = LOOPER.lock().unwrap(); - let _mutex_guard = looper_ready - .wait_while(locked_looper, |looper| looper.is_none()) - .unwrap(); -} - -#[allow(non_snake_case)] -pub unsafe fn onActivityCreate( - mut env: JNIEnv, - _jclass: JClass, - activity: JObject, - setup: unsafe fn(&str, JNIEnv, &ThreadLooper, GlobalRef), -) { - let intent = - jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).ok(); - - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); - - let activity_name: JString = jni_call_method!( - env, - &activity, - "getLocalClassName", - "()Ljava/lang/String;", - l - ) - .unwrap() - .into(); - let activity_name = env - .get_string(&activity_name) - .unwrap() - .to_string_lossy() - .to_string(); - - // Initialize global context - let window_manager = jni_call_method!( - env, - &activity, - "getWindowManager", - "()Landroid/view/WindowManager;", - l - ) - .unwrap(); - let window_manager = env.new_global_ref(window_manager).unwrap(); - WINDOW_MANAGER - .lock() - .unwrap() - .insert(activity_id, window_manager); - let activity = env.new_global_ref(activity).unwrap(); - let vm = env.get_java_vm().unwrap(); - let thread_env = vm.attach_current_thread_as_daemon().unwrap(); - - CONTEXTS.lock().unwrap().insert( - activity_id, - AndroidContext { - java_vm: vm.get_java_vm_pointer() as *mut _, - context_jobject: activity.as_obj().as_raw() as *mut _, - activity_name, - window_created: false, - }, - ); - let looper = ThreadLooper::for_thread().unwrap(); - setup(PACKAGE.get().unwrap(), thread_env, &looper, activity); - - if let Some(tx) = ACTIVITY_CREATED_SENDERS - .lock() - .unwrap() - .remove(&activity_id) - { - let _ = tx.send(()); - } - - if let Some(intent) = intent { - handle_intent(env, intent); - } -} - -pub unsafe fn resume(_: JNIEnv, _: JClass, _: JObject) { - let did_resume = DID_RESUME.swap(true, Ordering::Relaxed); - // first Activity onResume() is called even after onCreate() - // to match the iOS implementation, we ignore the first resume event - if did_resume { - wake(Event::Resume); - } -} - -pub unsafe fn pause(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Pause); -} - -#[allow(non_snake_case)] -pub unsafe fn onWindowFocusChanged( - mut env: JNIEnv, - _: JClass, - activity: JObject, - has_focus: libc::c_int, -) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); - let event = Event::WindowEvent { - id: WindowId(super::WindowId(activity_id)), - event: WindowEvent::Focused(has_focus != 0), - }; - wake(event); -} - -#[allow(non_snake_case)] -pub unsafe fn onNewIntent(env: JNIEnv, _: JClass, intent: JObject) { - handle_intent(env, intent); -} - -pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { - // Maple local patch for https://github.com/tauri-apps/tao/issues/1217. - // - // Android can provide null intent/action/type objects during cold launch, - // task restoration, and OEM deep-link delivery. The upstream tao 0.35.2 - // handler unwraps these JNI calls, which aborts the process across the Rust - // JNI boundary. Treat malformed intent pieces as absent and return/continue. - if intent.is_null() { - return; - } - - let Ok(action) = jni_call_method!(env, &intent, "getAction", "()Ljava/lang/String;", l) else { - return; - }; - if action.is_null() { - return; - } - let action: JString = action.into(); - let Ok(action) = env - .get_string(&action) - .map(|action| action.to_string_lossy().to_string()) - else { - return; - }; - - // Only handle SEND, SEND_MULTIPLE, and VIEW actions - if action != "android.intent.action.SEND" - && action != "android.intent.action.VIEW" - && action != "android.intent.action.SEND_MULTIPLE" - { - return; - } - - let mut urls = HashSet::new(); - - // Get intent type (may be null) - let intent_type = jni_call_method!(env, &intent, "getType", "()Ljava/lang/String;", l) - .ok() - .filter(|intent_type| !intent_type.is_null()) - .and_then(|jstr| { - let intent_type: JString = jstr.into(); - env - .get_string(&intent_type) - .ok() - .map(|intent_type| intent_type.to_string_lossy().to_string()) - }); - - // Handle text/plain intents (EXTRA_TEXT) - if intent_type.as_deref() == Some("text/plain") { - let extra_text_key = env.new_string("android.intent.extra.TEXT").unwrap(); - let extra_text = jni_call_method!( - env, - &intent, - "getStringExtra", - "(Ljava/lang/String;)Ljava/lang/String;", - &[(&extra_text_key).into()], - l - ) - .ok() - .and_then(|jstr| { - let jstr: JString = jstr.into(); - env - .get_string(&jstr) - .ok() - .map(|s| s.to_string_lossy().to_string()) - }); - - if let Some(text) = extra_text { - if !text.is_empty() { - // Check if it's a valid URL - if let Ok(url) = url::Url::parse(&text) { - urls.insert(url); - } else { - // If not a URL, create a data URL for plain text - // Use percent encoding for the text content - let encoded = utf8_percent_encode(&text, DATA_URL_ENCODING_SET).to_string(); - if let Ok(url) = url::Url::parse(&format!("data:text/plain,{}", encoded)) { - urls.insert(url); - } - } - } - } - } - - // Handle ClipData (API >= KITKAT, which is API 19) - // We'll try to get clip data, and if it fails, we'll continue - let clip_data = jni_call_method!( - env, - &intent, - "getClipData", - "()Landroid/content/ClipData;", - l - ) - .ok() - .filter(|clip_data| !clip_data.is_null()); - - if let Some(clip_data) = clip_data { - // getItemCount may return null if the intent has only a single uri in which case the uri can be received by getData|String instead. - if let Ok(item_count) = jni_call_method!(env, &clip_data, "getItemCount", "()I", i) { - for i in 0..item_count { - let Ok(clip_item) = jni_call_method!( - env, - &clip_data, - "getItemAt", - "(I)Landroid/content/ClipData$Item;", - &[i.into()], - l - ) else { - continue; - }; - if clip_item.is_null() { - continue; - } - - let uri = jni_call_method!(env, &clip_item, "getUri", "()Landroid/net/Uri;", l) - .ok() - .filter(|uri| !uri.is_null()); - - if let Some(uri) = uri { - let Ok(uri_string) = jni_call_method!(env, &uri, "toString", "()Ljava/lang/String;", l) - else { - continue; - }; - if uri_string.is_null() { - continue; - } - let uri_string: JString = uri_string.into(); - let Ok(uri_str) = env - .get_string(&uri_string) - .map(|uri_str| uri_str.to_string_lossy().to_string()) - else { - continue; - }; - if let Ok(url) = url::Url::parse(&uri_str) { - urls.insert(url); - } else { - log::error!("failed to parse URI: {}", uri_str); - } - } - } - } - } - - // Handle EXTRA_STREAM (for file sharing) - let extras = jni_call_method!(env, &intent, "getExtras", "()Landroid/os/Bundle;", l) - .ok() - .filter(|extras| !extras.is_null()); - - if let Some(extras) = extras { - let extra_stream_key = env.new_string("android.intent.extra.STREAM").unwrap(); - let extra_stream = jni_call_method!( - env, - &extras, - "get", - "(Ljava/lang/String;)Ljava/lang/Object;", - &[(&extra_stream_key).into()], - l - ) - .ok() - .filter(|extra_stream| !extra_stream.is_null()); - - if let Some(stream_uri) = extra_stream { - let uri_str = jni_call_method!( - env, - &stream_uri, - "toString", - "()Ljava/lang/String;", - l - ) - .ok() - .filter(|uri_string| !uri_string.is_null()) - .and_then(|uri_string| { - let uri_string: JString = uri_string.into(); - env - .get_string(&uri_string) - .ok() - .map(|uri_str| uri_str.to_string_lossy().to_string()) - }); - - if let Some(uri_str) = uri_str { - if let Ok(url) = url::Url::parse(&uri_str) { - urls.insert(url); - } else { - log::error!("failed to parse URI: {}", uri_str); - } - } - } - } - - // Handle getDataString() for VIEW intents (deeplinks) - if action == "android.intent.action.VIEW" { - let data_string = jni_call_method!(env, &intent, "getDataString", "()Ljava/lang/String;", l) - .ok() - .filter(|data_string| !data_string.is_null()) - .and_then(|jstr| { - let jstr: JString = jstr.into(); - env - .get_string(&jstr) - .ok() - .map(|s| s.to_string_lossy().to_string()) - }); - - if let Some(data_str) = data_string { - if let Ok(url) = url::Url::parse(&data_str) { - urls.insert(url); - } else { - log::error!("failed to parse data string: {}", data_str); - } - } else { - log::error!("Intent data string is null"); - } - } - - if !urls.is_empty() { - INTENT_URLS.lock().unwrap().extend(urls); - wake(Event::Opened); - } -} - -pub unsafe fn start(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Start); -} - -pub unsafe fn stop(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Stop); -} - -#[allow(non_snake_case)] -pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); - - let is_changing_configurations = - jni_call_method!(env, &activity, "isChangingConfigurations", "()Z", z).unwrap(); - - // keep our Rust window references alive when the activity is going to be recreated due to configuration changes - // e.g. rotation, multi window mode change etc - if !is_changing_configurations { - wake(Event::WindowEvent { - id: WindowId(super::WindowId(activity_id)), - event: WindowEvent::Destroyed, - }); - CONTEXTS.lock().unwrap().remove(&activity_id); - WINDOW_MANAGER.lock().unwrap().remove(&activity_id); - } -} - -/////////////////////////////////////////////// -// Events below are not used by event loop yet. -/////////////////////////////////////////////// - -#[allow(non_snake_case)] -pub unsafe fn onActivitySaveInstanceState(_: JNIEnv, _: JClass, _: JObject) {} - -#[allow(non_snake_case)] -pub unsafe fn onActivityLowMemory(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::LowMemory); -} - -/* -unsafe extern "C" fn on_window_resized( - activity: *mut ANativeActivity, - _window: *mut ANativeWindow, -) { - wake(activity, Event::WindowResized); -} - -unsafe extern "C" fn on_input_queue_created( - activity: *mut ANativeActivity, - queue: *mut AInputQueue, -) { - let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap()); - let locked_looper = LOOPER.lock().unwrap(); - // The looper should always be `Some` after `fn init()` returns, unless - // future code cleans it up and sets it back to `None` again. - let looper = locked_looper.as_ref().expect("Looper does not exist"); - input_queue.attach_looper(looper, NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT); - *INPUT_QUEUE.write().unwrap() = Some(input_queue); - wake(activity, Event::InputQueueCreated); -} - -unsafe extern "C" fn on_input_queue_destroyed( - activity: *mut ANativeActivity, - queue: *mut AInputQueue, -) { - wake(activity, Event::InputQueueDestroyed); - let mut input_queue_guard = INPUT_QUEUE.write().unwrap(); - assert_eq!(input_queue_guard.as_ref().unwrap().ptr().as_ptr(), queue); - let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap()); - input_queue.detach_looper(); - *input_queue_guard = None; -} - -unsafe extern "C" fn on_content_rect_changed(activity: *mut ANativeActivity, rect: *const ARect) { - let rect = Rect { - left: (*rect).left as _, - top: (*rect).top as _, - right: (*rect).right as _, - bottom: (*rect).bottom as _, - }; - *CONTENT_RECT.write().unwrap() = rect; - wake(activity, Event::ContentRectChanged); -} -*/ diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs deleted file mode 100644 index e1c5e2667..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs +++ /dev/null @@ -1,1092 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![deny(unused_results)] - -use std::{ - cell::{RefCell, RefMut}, - collections::HashSet, - mem, - os::raw::c_void, - ptr, - time::Instant, -}; - -use objc2::{rc::Retained, runtime::AnyObject, MainThreadMarker, Message}; -use objc2_ui_kit::{UIApplication, UIScene, UISceneConnectionOptions, UIWindowScene}; - -use crate::{ - dpi::LogicalSize, - event::{Event, StartCause, WindowEvent}, - event_loop::ControlFlow, - platform_impl::platform::{ - event_loop::{EventHandler, EventProxy, EventWrapper}, - ffi::{ - id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, - CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, - NSOperatingSystemVersion, NSUInteger, - }, - scene::multiple_scenes_enabled, - }, - window::WindowId as RootWindowId, -}; - -macro_rules! bug { - ($($msg:tt)*) => { - panic!("tao iOS bug, file an issue: {}", format!($($msg)*)) - }; -} - -macro_rules! bug_assert { - ($test:expr, $($msg:tt)*) => { - assert!($test, "tao iOS bug, file an issue: {}", format!($($msg)*)) - }; -} - -enum UserCallbackTransitionResult<'a> { - Success { - event_handler: Box, - active_control_flow: ControlFlow, - processing_redraws: bool, - }, - ReentrancyPrevented { - queued_events: &'a mut Vec, - }, -} - -// this is the state machine for the app lifecycle -#[derive(Debug)] -#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] -enum AppStateImpl { - NotLaunched { - queued_windows: Vec, - queued_events: Vec, - queued_gpu_redraws: HashSet, - }, - Launching { - queued_windows: Vec, - queued_events: Vec, - queued_event_handler: Box, - queued_gpu_redraws: HashSet, - }, - ProcessingEvents { - event_handler: Box, - queued_gpu_redraws: HashSet, - active_control_flow: ControlFlow, - }, - // special state to deal with reentrancy and prevent mutable aliasing. - InUserCallback { - queued_events: Vec, - queued_gpu_redraws: HashSet, - }, - ProcessingRedraws { - event_handler: Box, - active_control_flow: ControlFlow, - }, - Waiting { - waiting_event_handler: Box, - start: Instant, - }, - PollFinished { - waiting_event_handler: Box, - }, - Terminated, -} - -struct AppState { - // This should never be `None`, except for briefly during a state transition. - app_state: Option, - control_flow: ControlFlow, - waker: EventLoopWaker, - // Stores the UIWindow instances that do not have a scene assigned yet - // Window::new might create a window before a scene is ready for it, - // requesting a new scene to be activated and deferring the setWindowScene call to the scene delegate - windows_for_next_scenes: Vec, - did_first_scene_connect: bool, -} - -impl Drop for AppState { - fn drop(&mut self) { - match self.state_mut() { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - .. - } - | &mut AppStateImpl::Launching { - ref mut queued_windows, - .. - } => { - for &mut window in queued_windows { - unsafe { - let () = msg_send![window, release]; - } - } - } - _ => {} - } - } -} - -impl AppState { - // requires main thread - unsafe fn get_mut() -> RefMut<'static, AppState> { - // basically everything in UIKit requires the main thread, so it's pointless to use the - // std::sync APIs. - // must be mut because plain `static` requires `Sync` - static mut APP_STATE: RefCell> = RefCell::new(None); - - if cfg!(debug_assertions) { - assert_main_thread!( - "bug in tao: `AppState::get_mut()` can only be called on the main thread" - ); - } - let mut guard = APP_STATE.borrow_mut(); - if guard.is_none() { - #[inline(never)] - #[cold] - unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { - let waker = EventLoopWaker::new(CFRunLoopGetMain()); - **guard = Some(AppState { - app_state: Some(AppStateImpl::NotLaunched { - queued_windows: Vec::new(), - queued_events: Vec::new(), - queued_gpu_redraws: HashSet::new(), - }), - control_flow: ControlFlow::default(), - waker, - windows_for_next_scenes: Vec::new(), - did_first_scene_connect: false, - }); - } - init_guard(&mut guard) - } - RefMut::map(guard, |state| state.as_mut().unwrap()) - } - - fn state(&self) -> &AppStateImpl { - match &self.app_state { - Some(ref state) => state, - None => bug!("`AppState` previously failed a state transition"), - } - } - - fn state_mut(&mut self) -> &mut AppStateImpl { - match &mut self.app_state { - Some(ref mut state) => state, - None => bug!("`AppState` previously failed a state transition"), - } - } - - fn take_state(&mut self) -> AppStateImpl { - match self.app_state.take() { - Some(state) => state, - None => bug!("`AppState` previously failed a state transition"), - } - } - - fn set_state(&mut self, new_state: AppStateImpl) { - bug_assert!( - self.app_state.is_none(), - "attempted to set an `AppState` without calling `take_state` first {:?}", - self.app_state - ); - self.app_state = Some(new_state) - } - - fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { - match &mut self.app_state { - Some(ref mut state) => mem::replace(state, new_state), - None => bug!("`AppState` previously failed a state transition"), - } - } - - fn has_launched(&self) -> bool { - !matches!( - self.state(), - &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } - ) - } - - fn will_launch_transition(&mut self, queued_event_handler: Box) { - let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { - AppStateImpl::NotLaunched { - queued_windows, - queued_events, - queued_gpu_redraws, - } => (queued_windows, queued_events, queued_gpu_redraws), - s => bug!("unexpected state {:?}", s), - }; - self.set_state(AppStateImpl::Launching { - queued_windows, - queued_events, - queued_event_handler, - queued_gpu_redraws, - }); - } - - fn did_finish_launching_transition(&mut self) -> (Vec, Vec) { - let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { - AppStateImpl::Launching { - queued_windows, - queued_events, - queued_event_handler, - queued_gpu_redraws, - } => ( - queued_windows, - queued_events, - queued_event_handler, - queued_gpu_redraws, - ), - s => bug!("unexpected state {:?}", s), - }; - self.set_state(AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Poll, - queued_gpu_redraws, - }); - (windows, events) - } - - fn wakeup_transition(&mut self) -> Option { - // before `AppState::did_finish_launching` is called, pretend there is no running - // event loop. - if !self.has_launched() { - return None; - } - - let (event_handler, event) = match (self.control_flow, self.take_state()) { - ( - ControlFlow::Poll, - AppStateImpl::PollFinished { - waiting_event_handler, - }, - ) => ( - waiting_event_handler, - EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), - ), - ( - ControlFlow::Wait, - AppStateImpl::Waiting { - waiting_event_handler, - start, - }, - ) => ( - waiting_event_handler, - EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: None, - })), - ), - ( - ControlFlow::WaitUntil(requested_resume), - AppStateImpl::Waiting { - waiting_event_handler, - start, - }, - ) => { - let event = if Instant::now() >= requested_resume { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume, - })) - } else { - EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - })) - }; - (waiting_event_handler, event) - } - (ControlFlow::ExitWithCode(_), _) => bug!("unexpected `ControlFlow` `Exit`"), - s => bug!("`EventHandler` unexpectedly woke up {:?}", s), - }; - - self.set_state(AppStateImpl::ProcessingEvents { - event_handler, - queued_gpu_redraws: Default::default(), - active_control_flow: self.control_flow, - }); - Some(event) - } - - fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { - // If we're not able to process an event due to recursion or `Init` not having been sent out - // yet, then queue the events up. - match self.state_mut() { - &mut AppStateImpl::Launching { - ref mut queued_events, - .. - } - | &mut AppStateImpl::NotLaunched { - ref mut queued_events, - .. - } - | &mut AppStateImpl::InUserCallback { - ref mut queued_events, - .. - } => { - // A lifetime cast: early returns are not currently handled well with NLL, but - // polonius handles them well. This transmute is a safe workaround. - return unsafe { - mem::transmute::, UserCallbackTransitionResult<'_>>( - UserCallbackTransitionResult::ReentrancyPrevented { queued_events }, - ) - }; - } - - &mut AppStateImpl::ProcessingEvents { .. } | &mut AppStateImpl::ProcessingRedraws { .. } => {} - - s @ &mut AppStateImpl::PollFinished { .. } - | s @ &mut AppStateImpl::Waiting { .. } - | s @ &mut AppStateImpl::Terminated => { - bug!("unexpected attempted to process an event {:?}", s) - } - } - - let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) = - match self.take_state() { - AppStateImpl::Launching { .. } - | AppStateImpl::NotLaunched { .. } - | AppStateImpl::InUserCallback { .. } => unreachable!(), - AppStateImpl::ProcessingEvents { - event_handler, - queued_gpu_redraws, - active_control_flow, - } => ( - event_handler, - queued_gpu_redraws, - active_control_flow, - false, - ), - AppStateImpl::ProcessingRedraws { - event_handler, - active_control_flow, - } => (event_handler, Default::default(), active_control_flow, true), - AppStateImpl::PollFinished { .. } - | AppStateImpl::Waiting { .. } - | AppStateImpl::Terminated => unreachable!(), - }; - self.set_state(AppStateImpl::InUserCallback { - queued_events: Vec::new(), - queued_gpu_redraws, - }); - UserCallbackTransitionResult::Success { - event_handler, - active_control_flow, - processing_redraws, - } - } - - fn main_events_cleared_transition(&mut self) -> HashSet { - let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { - AppStateImpl::ProcessingEvents { - event_handler, - queued_gpu_redraws, - active_control_flow, - } => (event_handler, queued_gpu_redraws, active_control_flow), - s => bug!("unexpected state {:?}", s), - }; - self.set_state(AppStateImpl::ProcessingRedraws { - event_handler, - active_control_flow, - }); - queued_gpu_redraws - } - - fn events_cleared_transition(&mut self) { - if !self.has_launched() { - return; - } - let (waiting_event_handler, old) = match self.take_state() { - AppStateImpl::ProcessingRedraws { - event_handler, - active_control_flow, - } => (event_handler, active_control_flow), - s => bug!("unexpected state {:?}", s), - }; - - let new = self.control_flow; - match (old, new) { - (ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished { - waiting_event_handler, - }), - (ControlFlow::Wait, ControlFlow::Wait) => { - let start = Instant::now(); - self.set_state(AppStateImpl::Waiting { - waiting_event_handler, - start, - }); - } - (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) - if old_instant == new_instant => - { - let start = Instant::now(); - self.set_state(AppStateImpl::Waiting { - waiting_event_handler, - start, - }); - } - (_, ControlFlow::Wait) => { - let start = Instant::now(); - self.set_state(AppStateImpl::Waiting { - waiting_event_handler, - start, - }); - self.waker.stop() - } - (_, ControlFlow::WaitUntil(new_instant)) => { - let start = Instant::now(); - self.set_state(AppStateImpl::Waiting { - waiting_event_handler, - start, - }); - self.waker.start_at(new_instant) - } - (_, ControlFlow::Poll) => { - self.set_state(AppStateImpl::PollFinished { - waiting_event_handler, - }); - self.waker.start() - } - (_, ControlFlow::ExitWithCode(_)) => { - // https://developer.apple.com/library/archive/qa/qa1561/_index.html - // it is not possible to quit an iOS app gracefully and programatically - warn!("`ControlFlow::Exit` ignored on iOS"); - self.control_flow = old - } - } - } - - fn terminated_transition(&mut self) -> Box { - match self.replace_state(AppStateImpl::Terminated) { - AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler, - s => bug!( - "`LoopDestroyed` happened while not processing events {:?}", - s - ), - } - } -} - -// tries to find an unitialized scene (no windows) -pub unsafe fn unitialized_scene() -> Option> { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - for scene in application.connectedScenes().iter() { - if let Some(window_scene) = scene.downcast_ref::() { - if window_scene.windows().count() == 0 { - return Some(window_scene.retain()); - } - } - } - - None -} - -pub unsafe fn scene_by_id(id: &str) -> Option> { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - for scene in application.connectedScenes().iter() { - let scene_id = scene.session().persistentIdentifier().to_string(); - if scene_id == id { - return Some(scene); - } - } - None -} - -pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions) { - let did_first_scene_connect = AppState::get_mut().did_first_scene_connect; - let is_first_scene = !did_first_scene_connect; - // on scene mode, we run on_app_ready() when the main scene is connected - // instead of on AppDelegate::didFinishLaunching - // this optimizes app startup, since the first created window can immediately see the main scene - // instead of having to create a new one (since it can't synchronously wait for it to be connected) - if !did_first_scene_connect { - AppState::get_mut().did_first_scene_connect = true; - on_app_ready(); - } - - if let Some(window_scene) = scene.downcast_ref::() { - let window = { - let mut this = AppState::get_mut(); - if this.windows_for_next_scenes.is_empty() { - None - } else { - Some(this.windows_for_next_scenes.remove(0)) - } - }; - if let Some(window) = window { - let () = msg_send![window, setWindowScene: window_scene]; - let () = msg_send![window, release]; - } else if !is_first_scene { - // only emit the SceneRequest event for scenes that were not requested by a tao window - // also ignore the main scene - handle_nonuser_event(EventWrapper::StaticEvent(Event::SceneRequested { - scene: scene.retain(), - options: options.retain(), - })); - } - } -} - -pub unsafe fn register_window_for_scene(window: id) { - AppState::get_mut() - .windows_for_next_scenes - .push(msg_send![window, retain]); -} - -// requires main thread and window is a UIWindow -// retains window -pub unsafe fn set_key_window(window: id) { - bug_assert!( - msg_send![window, isKindOfClass: class!(UIWindow)], - "set_key_window called with an incorrect type" - ); - let mut this = AppState::get_mut(); - match this.state_mut() { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - .. - } => return queued_windows.push(msg_send![window, retain]), - &mut AppStateImpl::ProcessingEvents { .. } - | &mut AppStateImpl::InUserCallback { .. } - | &mut AppStateImpl::ProcessingRedraws { .. } => {} - s @ &mut AppStateImpl::Launching { .. } - | s @ &mut AppStateImpl::Waiting { .. } - | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), - &mut AppStateImpl::Terminated => { - panic!("Attempt to create a `Window` after the app has terminated") - } - } - drop(this); - msg_send![window, makeKeyAndVisible] -} - -// requires main thread and window is a UIWindow -// retains window -pub unsafe fn queue_gl_or_metal_redraw(window: id) { - bug_assert!( - msg_send![window, isKindOfClass: class!(UIWindow)], - "set_key_window called with an incorrect type" - ); - let mut this = AppState::get_mut(); - match this.state_mut() { - &mut AppStateImpl::NotLaunched { - ref mut queued_gpu_redraws, - .. - } - | &mut AppStateImpl::Launching { - ref mut queued_gpu_redraws, - .. - } - | &mut AppStateImpl::ProcessingEvents { - ref mut queued_gpu_redraws, - .. - } - | &mut AppStateImpl::InUserCallback { - ref mut queued_gpu_redraws, - .. - } => drop(queued_gpu_redraws.insert(window)), - s @ &mut AppStateImpl::ProcessingRedraws { .. } - | s @ &mut AppStateImpl::Waiting { .. } - | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), - &mut AppStateImpl::Terminated => { - panic!("Attempt to create a `Window` after the app has terminated") - } - } - drop(this); -} - -// requires main thread -pub unsafe fn will_launch(queued_event_handler: Box) { - AppState::get_mut().will_launch_transition(queued_event_handler) -} - -// requires main thread -pub unsafe fn did_finish_launching() { - // when app is run in scenes lifecycle mode, we defer the did_finish_launching call to the first scene setup - if !multiple_scenes_enabled() { - on_app_ready(); - } -} - -unsafe fn on_app_ready() { - let mut this = AppState::get_mut(); - let windows = match this.state_mut() { - AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), - s => bug!("unexpected state {:?}", s), - }; - - // start waking up the event loop now! - bug_assert!( - this.control_flow == ControlFlow::Poll, - "unexpectedly not setup to `Poll` on launch!" - ); - this.waker.start(); - - // have to drop RefMut because the window setup code below can trigger new events - drop(this); - - for window in windows { - let count: NSUInteger = msg_send![window, retainCount]; - // make sure the window is still referenced - if count > 1 { - // Do a little screen dance here to account for windows being created before - // `UIApplicationMain` is called. This fixes visual issues such as being - // offcenter and sized incorrectly. Additionally, to fix orientation issues, we - // gotta reset the `rootViewController`. - // - // relevant iOS log: - // ``` - // [ApplicationLifecycle] Windows were created before application initialization - // completed. This may result in incorrect visual appearance. - // ``` - let screen: id = msg_send![window, screen]; - let _: id = msg_send![screen, retain]; - let () = msg_send![window, setScreen:0 as id]; - let () = msg_send![window, setScreen: screen]; - let () = msg_send![screen, release]; - let controller: id = msg_send![window, rootViewController]; - let () = msg_send![window, setRootViewController: ptr::null::()]; - let () = msg_send![window, setRootViewController: controller]; - let () = msg_send![window, makeKeyAndVisible]; - } - let () = msg_send![window, release]; - } - - let (windows, events) = AppState::get_mut().did_finish_launching_transition(); - - let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( - StartCause::Init, - ))) - .chain(events); - handle_nonuser_events(events); - - // the above window dance hack, could possibly trigger new windows to be created. - // we can just set those windows up normally, as they were created after didFinishLaunching - // so the app window can attach to it directly - for window in windows { - let count: NSUInteger = msg_send![window, retainCount]; - // make sure the window is still referenced - if count > 1 { - let () = msg_send![window, makeKeyAndVisible]; - } - let () = msg_send![window, release]; - } -} - -// requires main thread -// AppState::did_finish_launching handles the special transition `Init` -pub unsafe fn handle_wakeup_transition() { - let mut this = AppState::get_mut(); - let wakeup_event = match this.wakeup_transition() { - None => return, - Some(wakeup_event) => wakeup_event, - }; - drop(this); - - handle_nonuser_event(wakeup_event) -} - -// requires main thread -pub unsafe fn handle_nonuser_event(event: EventWrapper) { - handle_nonuser_events(std::iter::once(event)) -} - -// requires main thread -pub unsafe fn handle_nonuser_events>(events: I) { - let mut this = AppState::get_mut(); - let (mut event_handler, active_control_flow, processing_redraws) = - match this.try_user_callback_transition() { - UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { - queued_events.extend(events); - return; - } - UserCallbackTransitionResult::Success { - event_handler, - active_control_flow, - processing_redraws, - } => (event_handler, active_control_flow, processing_redraws), - }; - let mut control_flow = this.control_flow; - drop(this); - - for wrapper in events { - match wrapper { - EventWrapper::StaticEvent(event) => { - event_handler.handle_nonuser_event(event, &mut control_flow) - } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) - } - } - } - - loop { - let mut this = AppState::get_mut(); - let queued_events = match this.state_mut() { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - queued_gpu_redraws: _, - } => mem::take(queued_events), - s => bug!("unexpected state {:?}", s), - }; - if queued_events.is_empty() { - let queued_gpu_redraws = match this.take_state() { - AppStateImpl::InUserCallback { - queued_events: _, - queued_gpu_redraws, - } => queued_gpu_redraws, - _ => unreachable!(), - }; - this.app_state = Some(if processing_redraws { - bug_assert!( - queued_gpu_redraws.is_empty(), - "redraw queued while processing redraws" - ); - AppStateImpl::ProcessingRedraws { - event_handler, - active_control_flow, - } - } else { - AppStateImpl::ProcessingEvents { - event_handler, - queued_gpu_redraws, - active_control_flow, - } - }); - this.control_flow = control_flow; - break; - } - drop(this); - - for wrapper in queued_events { - match wrapper { - EventWrapper::StaticEvent(event) => { - event_handler.handle_nonuser_event(event, &mut control_flow) - } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) - } - } - } - } -} - -// requires main thread -unsafe fn handle_user_events() { - let mut this = AppState::get_mut(); - let mut control_flow = this.control_flow; - let (mut event_handler, active_control_flow, processing_redraws) = - match this.try_user_callback_transition() { - UserCallbackTransitionResult::ReentrancyPrevented { .. } => { - bug!("unexpected attempted to process an event") - } - UserCallbackTransitionResult::Success { - event_handler, - active_control_flow, - processing_redraws, - } => (event_handler, active_control_flow, processing_redraws), - }; - if processing_redraws { - bug!("user events attempted to be sent out while `ProcessingRedraws`"); - } - drop(this); - - event_handler.handle_user_events(&mut control_flow); - - loop { - let mut this = AppState::get_mut(); - let queued_events = match this.state_mut() { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - queued_gpu_redraws: _, - } => mem::take(queued_events), - s => bug!("unexpected state {:?}", s), - }; - if queued_events.is_empty() { - let queued_gpu_redraws = match this.take_state() { - AppStateImpl::InUserCallback { - queued_events: _, - queued_gpu_redraws, - } => queued_gpu_redraws, - _ => unreachable!(), - }; - this.app_state = Some(AppStateImpl::ProcessingEvents { - event_handler, - queued_gpu_redraws, - active_control_flow, - }); - this.control_flow = control_flow; - break; - } - drop(this); - - for wrapper in queued_events { - match wrapper { - EventWrapper::StaticEvent(event) => { - event_handler.handle_nonuser_event(event, &mut control_flow) - } - EventWrapper::EventProxy(proxy) => { - handle_event_proxy(&mut event_handler, control_flow, proxy) - } - } - } - event_handler.handle_user_events(&mut control_flow); - } -} - -// requires main thread -pub unsafe fn handle_main_events_cleared() { - let mut this = AppState::get_mut(); - if !this.has_launched() { - return; - } - match this.state_mut() { - &mut AppStateImpl::ProcessingEvents { .. } => {} - _ => bug!("`ProcessingRedraws` happened unexpectedly"), - }; - drop(this); - - // User events are always sent out at the end of the "MainEventLoop" - handle_user_events(); - handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); - - let mut this = AppState::get_mut(); - let mut redraw_events: Vec = this - .main_events_cleared_transition() - .into_iter() - .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) - .collect(); - - if !redraw_events.is_empty() { - redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); - } - drop(this); - - handle_nonuser_events(redraw_events); -} - -// requires main thread -pub unsafe fn handle_events_cleared() { - AppState::get_mut().events_cleared_transition(); -} - -// requires main thread -pub unsafe fn terminated() { - let mut this = AppState::get_mut(); - let mut event_handler = this.terminated_transition(); - let mut control_flow = this.control_flow; - drop(this); - - event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) -} - -fn handle_event_proxy( - event_handler: &mut Box, - control_flow: ControlFlow, - proxy: EventProxy, -) { - match proxy { - EventProxy::DpiChangedProxy { - suggested_size, - scale_factor, - window_id, - } => handle_hidpi_proxy( - event_handler, - control_flow, - suggested_size, - scale_factor, - window_id, - ), - } -} - -fn handle_hidpi_proxy( - event_handler: &mut Box, - mut control_flow: ControlFlow, - suggested_size: LogicalSize, - scale_factor: f64, - window_id: id, -) { - let mut size = suggested_size.to_physical(scale_factor); - let new_inner_size = &mut size; - let event = Event::WindowEvent { - window_id: RootWindowId(window_id.into()), - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - }, - }; - event_handler.handle_nonuser_event(event, &mut control_flow); - let (view, screen_frame) = get_view_and_screen_frame(window_id); - let physical_size = *new_inner_size; - let logical_size = physical_size.to_logical(scale_factor); - let size = CGSize::new(logical_size); - let new_frame: CGRect = CGRect::new(screen_frame.origin, size); - unsafe { - let () = msg_send![view, setFrame: new_frame]; - } -} - -fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) { - unsafe { - let view_controller: id = msg_send![window_id, rootViewController]; - let view: id = msg_send![view_controller, view]; - let bounds: CGRect = msg_send![window_id, bounds]; - let screen: id = msg_send![window_id, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![window_id, convertRect:bounds, toCoordinateSpace:screen_space]; - (view, screen_frame) - } -} - -struct EventLoopWaker { - timer: CFRunLoopTimerRef, -} - -impl Drop for EventLoopWaker { - fn drop(&mut self) { - unsafe { - CFRunLoopTimerInvalidate(self.timer); - CFRelease(self.timer as _); - } - } -} - -impl EventLoopWaker { - fn new(rl: CFRunLoopRef) -> EventLoopWaker { - extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} - unsafe { - // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. - // It is initially setup with a first fire time really far into the - // future, but that gets changed to fire immediately in did_finish_launching - let timer = CFRunLoopTimerCreate( - ptr::null_mut(), - f64::MAX, - 0.000_000_1, - 0, - 0, - wakeup_main_loop, - ptr::null_mut(), - ); - CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes); - - EventLoopWaker { timer } - } - } - - fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } - } - - fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } - } - - fn start_at(&mut self, instant: Instant) { - let now = Instant::now(); - if now >= instant { - self.start(); - } else { - unsafe { - let current = CFAbsoluteTimeGetCurrent(); - let duration = instant - now; - let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) - } - } - } -} - -macro_rules! os_capabilities { - ( - $( - $(#[$attr:meta])* - $error_name:ident: $objc_call:literal, - $name:ident: $major:literal-$minor:literal - ),* - $(,)* - ) => { - #[derive(Clone, Debug)] - pub struct OSCapabilities { - $( - pub $name: bool, - )* - - os_version: NSOperatingSystemVersion, - } - - impl From for OSCapabilities { - fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities { - $(let $name = os_version.meets_requirements($major, $minor);)* - OSCapabilities { $($name,)* os_version, } - } - } - - impl OSCapabilities {$( - $(#[$attr])* - pub fn $error_name(&self, extra_msg: &str) { - log::warn!( - concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), - $major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch, - extra_msg - ) - } - )*} - }; -} - -os_capabilities! { - /// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc - #[allow(unused)] // error message unused - safe_area_err_msg: "-[UIView safeAreaInsets]", - safe_area: 11-0, - /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc - home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", - home_indicator_hidden: 11-0, - /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc - defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", - defer_system_gestures: 11-0, - /// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc - maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", - maximum_frames_per_second: 10-3, - /// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc - #[allow(unused)] // error message unused - force_touch_err_msg: "-[UITouch force]", - force_touch: 9-0, -} - -impl NSOperatingSystemVersion { - fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool { - (self.major, self.minor) >= (required_major, required_minor) - } -} - -pub fn os_capabilities() -> OSCapabilities { - use once_cell::sync::Lazy; - static OS_CAPABILITIES: Lazy = Lazy::new(|| { - let version: NSOperatingSystemVersion = unsafe { - let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; - let atleast_ios_8: bool = msg_send![ - process_info, - respondsToSelector: sel!(operatingSystemVersion) - ]; - // tao requires atleast iOS 8 because no one has put the time into supporting earlier os versions. - // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support - // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS - // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 - // has been tested to not even run on macOS 10.15 - Xcode 8 might? - // - // The minimum required iOS version is likely to grow in the future. - assert!(atleast_ios_8, "`tao` requires iOS version 8 or greater"); - msg_send![process_info, operatingSystemVersion] - }; - version.into() - }); - OS_CAPABILITIES.clone() -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs deleted file mode 100644 index 4a1f3b3d7..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::ffi::CStr; - -use objc2::{ - msg_send, - runtime::{AnyClass, AnyObject}, -}; - -pub fn set_badge_count(count: i32) { - unsafe { - let ui_application = AnyClass::get(CStr::from_bytes_with_nul(b"UIApplication\0").unwrap()) - .expect("Failed to get UIApplication class"); - let app: *mut AnyObject = msg_send![ui_application, sharedApplication]; - let _: () = msg_send![app, setApplicationIconBadgeNumber:count]; - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs deleted file mode 100644 index 9c3985440..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::VecDeque, - ffi::c_void, - fmt::{self, Debug}, - marker::PhantomData, - mem, ptr, -}; - -use crossbeam_channel::{self as channel, Receiver, Sender}; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition}, - error::ExternalError, - event::Event, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget}, - monitor::MonitorHandle as RootMonitorHandle, - platform::ios::Idiom, -}; - -use crate::platform_impl::platform::{ - app_state, - ffi::{ - id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, - kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease, - CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, - CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, - CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, - NSStringRust, UIApplicationMain, UIUserInterfaceIdiom, - }, - monitor, set_badge_count, view, MonitorHandle, -}; - -#[non_exhaustive] -#[derive(Debug)] -pub enum EventWrapper { - StaticEvent(Event<'static, Never>), - EventProxy(EventProxy), -} - -#[non_exhaustive] -#[derive(Debug, PartialEq)] -pub enum EventProxy { - DpiChangedProxy { - window_id: id, - suggested_size: LogicalSize, - scale_factor: f64, - }, -} - -#[derive(Clone)] -pub struct EventLoopWindowTarget { - receiver: Receiver, - sender_to_clone: Sender, -} - -impl EventLoopWindowTarget { - pub fn available_monitors(&self) -> VecDeque { - // guaranteed to be on main thread - unsafe { monitor::uiscreens() } - } - - #[inline] - pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { - warn!("`Window::monitor_from_point` is ignored on iOS"); - None - } - - pub fn primary_monitor(&self) -> Option { - // guaranteed to be on main thread - let monitor = unsafe { monitor::main_uiscreen() }; - - Some(RootMonitorHandle { inner: monitor }) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::UiKit( - rwh_06::UiKitDisplayHandle::new(), - )) - } - - pub fn cursor_position(&self) -> Result, ExternalError> { - debug!("`EventLoopWindowTarget::cursor_position` is ignored on iOS"); - Ok((0, 0).into()) - } - - /// Sets badge count on iOS launcher. 0 hides the count - pub fn set_badge_count(&self, count: i32) { - set_badge_count(count); - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PlatformSpecificEventLoopAttributes {} - -pub struct EventLoop { - window_target: RootEventLoopWindowTarget, -} - -impl EventLoop { - pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop { - static mut SINGLETON_INIT: bool = false; - unsafe { - assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); - assert!( - !SINGLETON_INIT, - "Only one `EventLoop` is supported on iOS. \ - `EventLoopProxy` might be helpful" - ); - SINGLETON_INIT = true; - view::create_delegate_class(); - } - - let (sender_to_clone, receiver) = channel::unbounded(); - - // this line sets up the main run loop before `UIApplicationMain` - setup_control_flow_observers(); - - EventLoop { - window_target: RootEventLoopWindowTarget { - p: EventLoopWindowTarget { - receiver, - sender_to_clone, - }, - _marker: PhantomData, - }, - } - } - - pub fn run(self, event_handler: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), - { - unsafe { - let application: id = msg_send![class!(UIApplication), sharedApplication]; - assert_eq!( - application, - ptr::null_mut(), - "\ - `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ - Note: `EventLoop::run` calls `UIApplicationMain` on iOS" - ); - app_state::will_launch(Box::new(EventLoopHandler { - f: event_handler, - event_loop: self.window_target, - })); - - UIApplicationMain( - 0, - ptr::null(), - nil, - NSStringRust::alloc(nil).init_str("AppDelegate"), - ); - unreachable!() - } - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) - } - - pub fn window_target(&self) -> &RootEventLoopWindowTarget { - &self.window_target - } -} - -// EventLoopExtIOS -impl EventLoop { - pub fn idiom(&self) -> Idiom { - // guaranteed to be on main thread - unsafe { self::get_idiom() } - } -} - -pub struct EventLoopProxy { - sender: Sender, - source: CFRunLoopSourceRef, -} - -unsafe impl Send for EventLoopProxy {} -unsafe impl Sync for EventLoopProxy {} - -impl Clone for EventLoopProxy { - fn clone(&self) -> EventLoopProxy { - EventLoopProxy::new(self.sender.clone()) - } -} - -impl Drop for EventLoopProxy { - fn drop(&mut self) { - unsafe { - CFRunLoopSourceInvalidate(self.source); - CFRelease(self.source as _); - } - } -} - -impl EventLoopProxy { - fn new(sender: Sender) -> EventLoopProxy { - unsafe { - // just wake up the eventloop - extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} - - // adding a Source to the main CFRunLoop lets us wake it up and - // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain(); - // we want all the members of context to be zero/null, except one - let mut context: CFRunLoopSourceContext = mem::zeroed(); - context.perform = Some(event_loop_proxy_handler); - let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); - CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); - CFRunLoopWakeUp(rl); - - EventLoopProxy { sender, source } - } - } - - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self - .sender - .send(event) - .map_err(|channel::SendError(x)| EventLoopClosed(x))?; - unsafe { - // let the main thread know there's a new event - CFRunLoopSourceSignal(self.source); - let rl = CFRunLoopGetMain(); - CFRunLoopWakeUp(rl); - } - Ok(()) - } -} - -fn setup_control_flow_observers() { - unsafe { - // begin is queued with the highest priority to ensure it is processed before other observers - extern "C" fn control_flow_begin_handler( - _: CFRunLoopObserverRef, - activity: CFRunLoopActivity, - _: *mut c_void, - ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), - kCFRunLoopEntry => unimplemented!(), // not expected to ever happen - _ => unreachable!(), - } - } - } - - // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in - // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end - // priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was - // chosen conservatively to guard against apple using different priorities for their redraw - // observers in different OS's or on different devices. If it so happens that it's too - // conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`. - // - // The value of `0x1e8480` was determined by inspecting stack traces and the associated - // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. - // - // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. - extern "C" fn control_flow_main_end_handler( - _: CFRunLoopObserverRef, - activity: CFRunLoopActivity, - _: *mut c_void, - ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), - kCFRunLoopExit => (), // Mode is changed to others like `UITrackingRunLoopMode` - _ => unreachable!(), - } - } - } - - // end is queued with the lowest priority to ensure it is processed after other observers - extern "C" fn control_flow_end_handler( - _: CFRunLoopObserverRef, - activity: CFRunLoopActivity, - _: *mut c_void, - ) { - unsafe { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), - kCFRunLoopExit => (), // Mode is changed to others like `UITrackingRunLoopMode` - _ => unreachable!(), - } - } - } - - let main_loop = CFRunLoopGetMain(); - - let begin_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopEntry | kCFRunLoopAfterWaiting, - 1, // repeat = true - CFIndex::MIN, - control_flow_begin_handler, - ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); - - let main_end_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopExit | kCFRunLoopBeforeWaiting, - 1, // repeat = true - 0, // see comment on `control_flow_main_end_handler` - control_flow_main_end_handler, - ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); - - let end_observer = CFRunLoopObserverCreate( - ptr::null_mut(), - kCFRunLoopExit | kCFRunLoopBeforeWaiting, - 1, // repeat = true - CFIndex::MAX, - control_flow_end_handler, - ptr::null_mut(), - ); - CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); - } -} - -#[non_exhaustive] -#[derive(Debug)] -pub enum Never {} - -pub trait EventHandler: Debug { - fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); - fn handle_user_events(&mut self, control_flow: &mut ControlFlow); -} - -struct EventLoopHandler { - f: F, - event_loop: RootEventLoopWindowTarget, -} - -impl Debug for EventLoopHandler { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EventLoopHandler") - .field("event_loop", &self.event_loop) - .finish() - } -} - -impl EventHandler for EventLoopHandler -where - F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), - T: 'static, -{ - fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { - (self.f)( - event.map_nonuser_event().unwrap(), - &self.event_loop, - control_flow, - ); - } - - fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { - for event in self.event_loop.p.receiver.try_iter() { - (self.f)(Event::UserEvent(event), &self.event_loop, control_flow); - } - } -} - -// must be called on main thread -pub unsafe fn get_idiom() -> Idiom { - let device: id = msg_send![class!(UIDevice), currentDevice]; - let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom]; - raw_idiom.into() -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs deleted file mode 100644 index 3fcff99a2..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] - -use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*}; - -use objc2::{ - encode::{Encode, Encoding}, - runtime::{AnyObject, Bool}, -}; - -use crate::{ - dpi::LogicalSize, - platform::ios::{Idiom, ScreenEdge, ValidOrientations}, -}; - -pub type id = *mut AnyObject; -pub const nil: id = 0 as id; - -#[allow(non_camel_case_types)] -pub type BOOL = Bool; -#[allow(deprecated)] -pub const YES: Bool = Bool::YES; -#[allow(deprecated)] -pub const NO: Bool = Bool::NO; - -#[cfg(target_pointer_width = "32")] -pub type CGFloat = f32; -#[cfg(target_pointer_width = "64")] -pub type CGFloat = f64; - -pub type NSInteger = isize; -pub type NSUInteger = usize; - -#[repr(C)] -#[derive(Clone, Debug)] -pub struct NSOperatingSystemVersion { - pub major: NSInteger, - pub minor: NSInteger, - pub patch: NSInteger, -} - -unsafe impl Encode for NSOperatingSystemVersion { - const ENCODING: Encoding = Encoding::Struct( - "?", - &[ - NSInteger::ENCODING, - NSInteger::ENCODING, - NSInteger::ENCODING, - ], - ); -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct CGPoint { - pub x: CGFloat, - pub y: CGFloat, -} - -unsafe impl Encode for CGPoint { - const ENCODING: Encoding = Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]); -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct CGSize { - pub width: CGFloat, - pub height: CGFloat, -} - -impl CGSize { - pub fn new(size: LogicalSize) -> CGSize { - CGSize { - width: size.width as _, - height: size.height as _, - } - } -} - -unsafe impl Encode for CGSize { - const ENCODING: Encoding = Encoding::Struct("CGSize", &[CGFloat::ENCODING, CGFloat::ENCODING]); -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct CGRect { - pub origin: CGPoint, - pub size: CGSize, -} - -impl CGRect { - pub fn new(origin: CGPoint, size: CGSize) -> CGRect { - CGRect { origin, size } - } -} - -unsafe impl Encode for CGRect { - const ENCODING: Encoding = Encoding::Struct("CGRect", &[CGPoint::ENCODING, CGSize::ENCODING]); -} - -#[derive(Debug)] -#[allow(dead_code)] -#[repr(isize)] -pub enum UITouchPhase { - Began = 0, - Moved, - Stationary, - Ended, - Cancelled, -} - -unsafe impl Encode for UITouchPhase { - const ENCODING: Encoding = isize::ENCODING; -} - -#[derive(Debug, PartialEq)] -#[allow(dead_code)] -#[repr(isize)] -pub enum UIForceTouchCapability { - Unknown = 0, - Unavailable, - Available, -} - -unsafe impl Encode for UIForceTouchCapability { - const ENCODING: Encoding = isize::ENCODING; -} - -#[derive(Debug, PartialEq)] -#[allow(dead_code)] -#[repr(isize)] -pub enum UITouchType { - Direct = 0, - Indirect, - Pencil, -} - -unsafe impl Encode for UITouchType { - const ENCODING: Encoding = isize::ENCODING; -} - -#[repr(C)] -#[derive(Debug, Clone)] -pub struct UIEdgeInsets { - pub top: CGFloat, - pub left: CGFloat, - pub bottom: CGFloat, - pub right: CGFloat, -} - -unsafe impl Encode for UIEdgeInsets { - const ENCODING: Encoding = Encoding::Struct( - "UIEdgeInsets", - &[ - CGFloat::ENCODING, - CGFloat::ENCODING, - CGFloat::ENCODING, - CGFloat::ENCODING, - ], - ); -} - -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct UIUserInterfaceIdiom(NSInteger); - -unsafe impl Encode for UIUserInterfaceIdiom { - const ENCODING: Encoding = NSInteger::ENCODING; -} - -impl UIUserInterfaceIdiom { - pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1); - pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0); - pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1); - pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2); - pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3); -} - -impl From for UIUserInterfaceIdiom { - fn from(idiom: Idiom) -> UIUserInterfaceIdiom { - match idiom { - Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified, - Idiom::Phone => UIUserInterfaceIdiom::Phone, - Idiom::Pad => UIUserInterfaceIdiom::Pad, - Idiom::TV => UIUserInterfaceIdiom::TV, - Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay, - } - } -} - -impl Into for UIUserInterfaceIdiom { - fn into(self) -> Idiom { - match self { - UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified, - UIUserInterfaceIdiom::Phone => Idiom::Phone, - UIUserInterfaceIdiom::Pad => Idiom::Pad, - UIUserInterfaceIdiom::TV => Idiom::TV, - UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay, - _ => unreachable!(), - } - } -} - -#[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct UIInterfaceOrientationMask(NSUInteger); - -unsafe impl Encode for UIInterfaceOrientationMask { - const ENCODING: Encoding = NSUInteger::ENCODING; -} - -impl UIInterfaceOrientationMask { - pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1); - pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2); - pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4); - pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3); - pub const Landscape: UIInterfaceOrientationMask = - UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0); - pub const AllButUpsideDown: UIInterfaceOrientationMask = - UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0); - pub const All: UIInterfaceOrientationMask = - UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0); -} - -impl BitOr for UIInterfaceOrientationMask { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self { - UIInterfaceOrientationMask(self.0 | rhs.0) - } -} - -impl UIInterfaceOrientationMask { - pub fn from_valid_orientations_idiom( - valid_orientations: ValidOrientations, - idiom: Idiom, - ) -> UIInterfaceOrientationMask { - match (valid_orientations, idiom) { - (ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => { - UIInterfaceOrientationMask::AllButUpsideDown - } - (ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All, - (ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape, - (ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait, - (ValidOrientations::Portrait, _) => { - UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown - } - } - } -} - -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct UIRectEdge(NSUInteger); - -unsafe impl Encode for UIRectEdge { - const ENCODING: Encoding = NSUInteger::ENCODING; -} - -impl From for UIRectEdge { - fn from(screen_edge: ScreenEdge) -> UIRectEdge { - assert_eq!( - screen_edge.bits() & !ScreenEdge::ALL.bits(), - 0, - "invalid `ScreenEdge`" - ); - UIRectEdge(screen_edge.bits().into()) - } -} - -impl Into for UIRectEdge { - fn into(self) -> ScreenEdge { - let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`"); - ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`") - } -} - -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct UIScreenOverscanCompensation(NSInteger); - -unsafe impl Encode for UIScreenOverscanCompensation { - const ENCODING: Encoding = NSInteger::ENCODING; -} - -#[allow(dead_code)] -impl UIScreenOverscanCompensation { - pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0); - pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1); - pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2); -} - -#[allow(clippy::duplicated_attributes)] -#[link(name = "UIKit", kind = "framework")] -#[link(name = "CoreFoundation", kind = "framework")] -extern "C" { - pub static kCFRunLoopDefaultMode: CFRunLoopMode; - pub static kCFRunLoopCommonModes: CFRunLoopMode; - - pub fn UIApplicationMain( - argc: c_int, - argv: *const c_char, - principalClassName: id, - delegateClassName: id, - ) -> c_int; - - pub fn CFRunLoopGetMain() -> CFRunLoopRef; - pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); - - pub fn CFRunLoopObserverCreate( - allocator: CFAllocatorRef, - activities: CFOptionFlags, - repeats: Boolean, - order: CFIndex, - callout: CFRunLoopObserverCallBack, - context: *mut CFRunLoopObserverContext, - ) -> CFRunLoopObserverRef; - pub fn CFRunLoopAddObserver( - rl: CFRunLoopRef, - observer: CFRunLoopObserverRef, - mode: CFRunLoopMode, - ); - - pub fn CFRunLoopTimerCreate( - allocator: CFAllocatorRef, - fireDate: CFAbsoluteTime, - interval: CFTimeInterval, - flags: CFOptionFlags, - order: CFIndex, - callout: CFRunLoopTimerCallBack, - context: *mut CFRunLoopTimerContext, - ) -> CFRunLoopTimerRef; - pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode); - pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime); - pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); - - pub fn CFRunLoopSourceCreate( - allocator: CFAllocatorRef, - order: CFIndex, - context: *mut CFRunLoopSourceContext, - ) -> CFRunLoopSourceRef; - pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); - pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); - pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); - - pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; - pub fn CFRelease(cftype: *const c_void); -} - -pub type Boolean = u8; -pub enum CFAllocator {} -pub type CFAllocatorRef = *mut CFAllocator; -pub enum CFRunLoop {} -pub type CFRunLoopRef = *mut CFRunLoop; -pub type CFRunLoopMode = CFStringRef; -pub enum CFRunLoopObserver {} -pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; -pub enum CFRunLoopTimer {} -pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; -pub enum CFRunLoopSource {} -pub type CFRunLoopSourceRef = *mut CFRunLoopSource; -pub enum CFString {} -pub type CFStringRef = *const CFString; - -pub type CFHashCode = c_ulong; -pub type CFIndex = c_long; -pub type CFOptionFlags = c_ulong; -pub type CFRunLoopActivity = CFOptionFlags; - -pub type CFAbsoluteTime = CFTimeInterval; -pub type CFTimeInterval = f64; - -pub const kCFRunLoopEntry: CFRunLoopActivity = 0; -pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; -pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; -pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; - -pub type CFRunLoopObserverCallBack = - extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void); -pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void); - -pub enum CFRunLoopObserverContext {} -pub enum CFRunLoopTimerContext {} - -#[repr(C)] -pub struct CFRunLoopSourceContext { - pub version: CFIndex, - pub info: *mut c_void, - pub retain: Option *const c_void>, - pub release: Option, - pub copyDescription: Option CFStringRef>, - pub equal: Option Boolean>, - pub hash: Option CFHashCode>, - pub schedule: Option, - pub cancel: Option, - pub perform: Option, -} - -// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of -// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy -// so please test if you change the name back to NSString. -pub trait NSStringRust: Sized { - unsafe fn alloc(_: Self) -> id { - msg_send![class!(NSString), alloc] - } - - unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id; - unsafe fn stringByAppendingString_(self, other: id) -> id; - unsafe fn init_str(self, string: &str) -> Self; - unsafe fn UTF8String(self) -> *const c_char; -} - -impl NSStringRust for id { - unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id { - msg_send![self, initWithUTF8String: c_string] - } - - unsafe fn stringByAppendingString_(self, other: id) -> id { - msg_send![self, stringByAppendingString: other] - } - - unsafe fn init_str(self, string: &str) -> id { - let cstring = CString::new(string).unwrap(); - self.initWithUTF8String_(cstring.as_ptr()) - } - - unsafe fn UTF8String(self) -> *const c_char { - msg_send![self, UTF8String] - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs deleted file mode 100644 index e84dbac6c..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::keyboard::{KeyCode, NativeKeyCode}; - -pub fn keycode_to_scancode(_code: KeyCode) -> Option { - None -} - -pub fn keycode_from_scancode(_scancode: u32) -> KeyCode { - KeyCode::Unidentified(NativeKeyCode::Unidentified) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs deleted file mode 100644 index ba183d445..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! iOS support -//! -//! # Building app -//! To build ios app you will need rustc built for this targets: -//! -//! - armv7-apple-ios -//! - armv7s-apple-ios -//! - i386-apple-ios -//! - aarch64-apple-ios -//! - x86_64-apple-ios -//! -//! Then -//! -//! ``` -//! cargo build --target=... -//! ``` -//! The simplest way to integrate your app into xcode environment is to build it -//! as a static library. Wrap your main function and export it. -//! -//! ```rust, ignore -//! #[no_mangle] -//! pub extern fn start_tao_app() { -//! start_inner() -//! } -//! -//! fn start_inner() { -//! ... -//! } -//! ``` -//! -//! Compile project and then drag resulting .a into Xcode project. Add tao.h to xcode. -//! -//! ```ignore -//! void start_tao_app(); -//! ``` -//! -//! Use start_tao_app inside your xcode's main function. -//! -//! -//! # App lifecycle and events -//! -//! iOS environment is very different from other platforms and you must be very -//! careful with it's events. Familiarize yourself with -//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/). -//! -//! -//! This is how those event are represented in tao: -//! -//! - applicationWillEnterForeground is Resumed -//! - applicationWillResignActive is Suspended -//! - applicationWillTerminate is LoopDestroyed -//! -//! Keep in mind that after LoopDestroyed event is received every attempt to draw with -//! opengl will result in segfault. -//! -//! Also note that app may not receive the LoopDestroyed event if suspended; it might be SIGKILL'ed. - -// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be -// worked around in the future by using GCD (grand central dispatch) and/or caching of values like -// window size/position. -macro_rules! assert_main_thread { - ($($t:tt)*) => { - let is_main_thread: bool = msg_send![class!(NSThread), isMainThread]; - if !is_main_thread { - panic!($($t)*); - } - }; -} - -mod app_state; -mod badge; -mod event_loop; -mod ffi; -mod keycode; -mod monitor; -mod scene; -mod view; -mod window; - -use std::fmt; - -pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; -pub use self::{ - event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - keycode::{keycode_from_scancode, keycode_to_scancode}, - monitor::{MonitorHandle, VideoMode}, - window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, -}; -pub(crate) use crate::icon::NoIcon as PlatformIcon; -pub(crate) use badge::set_badge_count; - -// todo: implement iOS keyboard event -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct KeyEventExtra {} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId { - uiscreen: ffi::id, -} - -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId { - uiscreen: std::ptr::null_mut(), - } - } -} - -unsafe impl Send for DeviceId {} -unsafe impl Sync for DeviceId {} - -#[non_exhaustive] -#[derive(Debug)] -pub enum OsError {} - -impl fmt::Display for OsError { - fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { - unreachable!() - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs deleted file mode 100644 index cc1bafb13..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::{BTreeSet, VecDeque}, - fmt, - ops::{Deref, DerefMut}, -}; - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::{ - app_state, - ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, - }, -}; - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, - pub(crate) screen_mode: NativeDisplayMode, - pub(crate) monitor: MonitorHandle, -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct NativeDisplayMode(pub id); - -unsafe impl Send for NativeDisplayMode {} - -impl Drop for NativeDisplayMode { - fn drop(&mut self) { - unsafe { - let () = msg_send![self.0, release]; - } - } -} - -impl Clone for NativeDisplayMode { - fn clone(&self) -> Self { - unsafe { - let _: id = msg_send![self.0, retain]; - } - NativeDisplayMode(self.0) - } -} - -impl Clone for VideoMode { - fn clone(&self) -> VideoMode { - VideoMode { - size: self.size, - bit_depth: self.bit_depth, - refresh_rate: self.refresh_rate, - screen_mode: self.screen_mode.clone(), - monitor: self.monitor.clone(), - } - } -} - -impl VideoMode { - unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode { - assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); - let os_capabilities = app_state::os_capabilities(); - let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second { - msg_send![uiscreen, maximumFramesPerSecond] - } else { - // https://developer.apple.com/library/archive/technotes/tn2460/_index.html - // https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison - // - // All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not - // supported, they are all guaranteed to have 60hz refresh rates. This does not - // correctly handle external displays. ProMotion displays support 120fps, but they were - // introduced at the same time as the `maximumFramesPerSecond` API. - // - // FIXME: earlier OSs could calculate the refresh rate using - // `-[CADisplayLink duration]`. - os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); - 60 - }; - let size: CGSize = msg_send![screen_mode, size]; - let screen_mode: id = msg_send![screen_mode, retain]; - let screen_mode = NativeDisplayMode(screen_mode); - VideoMode { - size: (size.width as u32, size.height as u32), - bit_depth: 32, - refresh_rate: refresh_rate as u16, - screen_mode, - monitor: MonitorHandle::retained_new(uiscreen), - } - } - - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - pub fn monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: self.monitor.clone(), - } - } -} - -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Inner { - uiscreen: id, -} - -impl Drop for Inner { - fn drop(&mut self) { - unsafe { - let () = msg_send![self.uiscreen, release]; - } - } -} - -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct MonitorHandle { - inner: Inner, -} - -impl Deref for MonitorHandle { - type Target = Inner; - - fn deref(&self) -> &Inner { - unsafe { - assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); - } - &self.inner - } -} - -impl DerefMut for MonitorHandle { - fn deref_mut(&mut self) -> &mut Inner { - unsafe { - assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS"); - } - &mut self.inner - } -} - -unsafe impl Send for MonitorHandle {} -unsafe impl Sync for MonitorHandle {} - -impl Clone for MonitorHandle { - fn clone(&self) -> MonitorHandle { - MonitorHandle::retained_new(self.uiscreen) - } -} - -impl Drop for MonitorHandle { - fn drop(&mut self) { - unsafe { - assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS"); - } - } -} - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - #[allow(unused)] - struct MonitorHandle { - name: Option, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - pub fn retained_new(uiscreen: id) -> MonitorHandle { - unsafe { - assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS"); - let _: id = msg_send![uiscreen, retain]; - } - MonitorHandle { - inner: Inner { uiscreen }, - } - } -} - -impl Inner { - pub fn name(&self) -> Option { - unsafe { - let main = main_uiscreen(); - if self.uiscreen == main.uiscreen { - Some("Primary".to_string()) - } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen { - Some("Mirrored".to_string()) - } else { - uiscreens() - .iter() - .position(|rhs| rhs.uiscreen == self.uiscreen) - .map(|idx| idx.to_string()) - } - } - } - - pub fn size(&self) -> PhysicalSize { - unsafe { - let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; - PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) - } - } - - pub fn position(&self) -> PhysicalPosition { - unsafe { - let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; - (bounds.origin.x as f64, bounds.origin.y as f64).into() - } - } - - pub fn scale_factor(&self) -> f64 { - unsafe { - let scale: CGFloat = msg_send![self.ui_screen(), nativeScale]; - scale as f64 - } - } - - pub fn video_modes(&self) -> impl Iterator { - let mut modes = BTreeSet::new(); - unsafe { - let available_modes: id = msg_send![self.uiscreen, availableModes]; - let available_mode_count: NSUInteger = msg_send![available_modes, count]; - - for i in 0..available_mode_count { - let mode: id = msg_send![available_modes, objectAtIndex: i]; - modes.insert(RootVideoMode { - video_mode: VideoMode::retained_new(self.uiscreen, mode), - }); - } - } - - modes.into_iter() - } -} - -// MonitorHandleExtIOS -impl Inner { - pub fn ui_screen(&self) -> id { - self.uiscreen - } - - pub fn preferred_video_mode(&self) -> RootVideoMode { - unsafe { - let mode: id = msg_send![self.uiscreen, preferredMode]; - RootVideoMode { - video_mode: VideoMode::retained_new(self.uiscreen, mode), - } - } - } -} - -// requires being run on main thread -pub unsafe fn main_uiscreen() -> MonitorHandle { - let uiscreen: id = msg_send![class!(UIScreen), mainScreen]; - MonitorHandle::retained_new(uiscreen) -} - -// requires being run on main thread -unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle { - let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen]; - MonitorHandle::retained_new(uiscreen) -} - -// requires being run on main thread -pub unsafe fn uiscreens() -> VecDeque { - let screens: id = msg_send![class!(UIScreen), screens]; - let count: NSUInteger = msg_send![screens, count]; - let mut result = VecDeque::with_capacity(count as _); - let screens_enum: id = msg_send![screens, objectEnumerator]; - loop { - let screen: id = msg_send![screens_enum, nextObject]; - if screen == nil { - break result; - } - result.push_back(MonitorHandle::retained_new(screen)); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs deleted file mode 100644 index 23414f06b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2021-2025 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; -use objc2_foundation::{ - NSBundle, NSDictionary, NSError, NSNumber, NSObject, NSObjectProtocol, NSSet, NSString, - NSUserActivity, -}; -use objc2_ui_kit::{ - UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, - UISceneSession, UIWindowScene, -}; - -use crate::{ - event::{Event, WindowEvent}, - platform_impl::platform::{app_state, event_loop::EventWrapper}, - window::WindowId as RootWindowId, -}; - -// true when the system allows the app to display multiple scenes and multiple_scenes_enabled() returns true -// https://developer.apple.com/documentation/uikit/uiapplication/supportsmultiplescenes?language=objc -pub unsafe fn app_supports_multiple_scenes() -> bool { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.supportsMultipleScenes() -} - -// check whether the app's Info.plist enabled multiple scenes -pub unsafe fn multiple_scenes_enabled() -> bool { - let bundle = NSBundle::mainBundle(); - let Some(info) = bundle.infoDictionary() else { - return false; - }; - - let key = NSString::from_str("UIApplicationSceneManifest"); - let Some(manifest) = (*info).objectForKey(&key) else { - return false; - }; - - let manifest_dict = Retained::cast_unchecked::>(manifest); - let supports_key = NSString::from_str("UIApplicationSupportsMultipleScenes"); - let Some(value) = (*manifest_dict).objectForKey(&supports_key) else { - return false; - }; - - let num = Retained::cast_unchecked::(value); - (*num).as_bool() -} - -define_class!( - #[unsafe(super(NSObject))] - #[name = "TaoSceneDelegate"] - #[thread_kind = MainThreadOnly] - pub struct TaoSceneDelegate; - - unsafe impl NSObjectProtocol for TaoSceneDelegate {} - - #[allow(non_snake_case)] - unsafe impl UISceneDelegate for TaoSceneDelegate { - #[unsafe(method(scene:willConnectToSession:options:))] - fn scene_willConnectToSession_options( - &self, - scene: &UIScene, - _session: &UISceneSession, - connection_options: &UISceneConnectionOptions, - ) { - unsafe { - app_state::connect_scene(scene, connection_options); - } - } - - #[unsafe(method(sceneDidDisconnect:))] - fn sceneDidDisconnect(&self, _scene: &UIScene) {} - - #[unsafe(method(sceneDidBecomeActive:))] - fn sceneDidBecomeActive(&self, scene: &UIScene) { - unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(true), - })); - } - } - } - } - - #[unsafe(method(sceneWillResignActive:))] - fn sceneWillResignActive(&self, scene: &UIScene) { - unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(false), - })); - } - } - } - } - - #[unsafe(method(sceneWillEnterForeground:))] - fn sceneWillEnterForeground(&self, _scene: &UIScene) {} - - #[unsafe(method(sceneDidEnterBackground:))] - fn sceneDidEnterBackground(&self, _scene: &UIScene) {} - - #[unsafe(method(scene:openURLContexts:))] - fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet) { - unsafe { - let urls: Vec = url_contexts - .iter() - .filter_map(|ctx| { - ctx.URL().absoluteString().and_then(|url| { - let url = url.to_string(); - url - .parse() - .map_err(|e| { - log::error!("failed to parse URL {url} from scene:openURLContexts: {e}"); - e - }) - .ok() - }) - }) - .collect(); - if !urls.is_empty() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); - } - } - } - - #[unsafe(method(stateRestorationActivityForScene:))] - fn stateRestorationActivityForScene( - &self, - _scene: &UIScene, - ) -> Option> { - None - } - - #[unsafe(method(scene:restoreInteractionStateWithUserActivity:))] - fn scene_restoreInteractionStateWithUserActivity( - &self, - _scene: &UIScene, - _state_restoration_activity: &NSUserActivity, - ) { - } - - #[unsafe(method(scene:willContinueUserActivityWithType:))] - fn scene_willContinueUserActivityWithType( - &self, - _scene: &UIScene, - _user_activity_type: &NSString, - ) { - } - - #[unsafe(method(scene:continueUserActivity:))] - fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) { - unsafe { - // universal app links - if let Some(url) = user_activity - .webpageURL() - .and_then(|url| url.absoluteString()) - { - let url = url.to_string().parse::().unwrap(); - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { - urls: vec![url], - })); - } - } - } - - #[unsafe(method(scene:didFailToContinueUserActivityWithType:error:))] - fn scene_didFailToContinueUserActivityWithType_error( - &self, - _scene: &UIScene, - _user_activity_type: &NSString, - _error: &NSError, - ) { - } - - #[unsafe(method(scene:didUpdateUserActivity:))] - fn scene_didUpdateUserActivity(&self, _scene: &UIScene, _user_activity: &NSUserActivity) {} - } -); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs deleted file mode 100644 index 7f37832fc..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs +++ /dev/null @@ -1,787 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::HashMap, - ffi::{c_char, CStr, CString}, -}; - -use objc2::{ - rc::Retained, - runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, - ClassType, MainThreadMarker, -}; -use objc2_foundation::NSString; -use objc2_ui_kit::{ - UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, - UISceneSessionActivationRequest, -}; - -use crate::{ - dpi::PhysicalPosition, - event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, - platform::ios::{operating_system_version, MonitorHandleExtIOS}, - platform_impl::platform::{ - app_state::{self, OSCapabilities}, - event_loop::{self, EventProxy, EventWrapper}, - ffi::{ - id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, - UIRectEdge, UITouchPhase, UITouchType, BOOL, NO, YES, - }, - scene::{app_supports_multiple_scenes, multiple_scenes_enabled}, - window::PlatformSpecificWindowBuilderAttributes, - DeviceId, - }, - window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, -}; - -macro_rules! add_property { - ( - $decl:ident, - $name:ident: $t:ty, - $setter_name:ident: |$object:ident| $after_set:expr, - $getter_name:ident, - ) => { - add_property!( - $decl, - $name: $t, - $setter_name: true, |_, _|{}; |$object| $after_set, - $getter_name, - ) - }; - ( - $decl:ident, - $name:ident: $t:ty, - $setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr, - $getter_name:ident, - ) => { - { - const VAR_NAME: &'static str = concat!("_", stringify!($name)); - $decl.add_ivar::<$t>(&CString::new(VAR_NAME).unwrap()); - let setter = if $capability { - #[allow(non_snake_case)] - extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { - #[allow(deprecated)] // TODO: define_class! - unsafe { - *$object.get_mut_ivar::<$t>(VAR_NAME) = value; - } - $after_set - } - $setter_name - } else { - #[allow(non_snake_case)] - extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { - #[allow(deprecated)] // TODO: define_class! - unsafe { - *$object.get_mut_ivar::<$t>(VAR_NAME) = value; - } - $err(&app_state::os_capabilities(), "ignoring") - } - $setter_name - }; - #[allow(non_snake_case)] - extern "C" fn $getter_name($object: &Object, _: Sel) -> $t { - #[allow(deprecated)] // TODO: define_class! - unsafe { *$object.get_ivar::<$t>(VAR_NAME) } - } - $decl.add_method( - sel!($setter_name:), - setter as extern "C" fn(_, _, _), - ); - $decl.add_method( - sel!($getter_name), - $getter_name as extern "C" fn(_, _) -> _, - ); - } - }; -} - -// requires main thread -unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { - static mut CLASSES: Option> = None; - static mut ID: usize = 0; - - if CLASSES.is_none() { - CLASSES = Some(HashMap::default()); - } - - let classes = CLASSES.as_mut().unwrap(); - - classes.entry(root_view_class).or_insert_with(move || { - let uiview_class = class!(UIView); - let is_uiview: bool = msg_send![root_view_class, isSubclassOfClass: uiview_class]; - assert!(is_uiview, "`root_view_class` must inherit from `UIView`"); - - extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { - unsafe { - let window: id = msg_send![object, window]; - assert!(!window.is_null()); - app_state::handle_nonuser_events( - std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested( - RootWindowId(window.into()), - ))) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::RedrawEventsCleared, - ))), - ); - let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), drawRect: rect]; - } - } - - extern "C" fn layout_subviews(object: &Object, _: Sel) { - unsafe { - let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), layoutSubviews]; - - let window: id = msg_send![object, window]; - assert!(!window.is_null()); - let window_bounds: CGRect = msg_send![window, bounds]; - let screen: id = msg_send![window, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![object, convertRect:window_bounds, toCoordinateSpace:screen_space]; - let scale_factor: CGFloat = msg_send![screen, scale]; - let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as f64, - height: screen_frame.size.height as f64, - } - .to_physical(scale_factor.into()); - - // If the app is started in landscape, the view frame and window bounds can be mismatched. - // The view frame will be in portrait and the window bounds in landscape. So apply the - // window bounds to the view frame to make it consistent. - let view_frame: CGRect = msg_send![object, frame]; - if view_frame != window_bounds { - let () = msg_send![object, setFrame: window_bounds]; - } - - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size), - })); - } - } - - extern "C" fn set_content_scale_factor( - object: &Object, - _: Sel, - untrusted_scale_factor: CGFloat, - ) { - unsafe { - let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![ - super(object, superclass), - setContentScaleFactor: untrusted_scale_factor - ]; - - let window: id = msg_send![object, window]; - // `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow - // makeKeyAndVisible]` at window creation time (either manually or internally by - // UIKit when the `UIView` is first created), in which case we send no events here - if window.is_null() { - return; - } - // `setContentScaleFactor` may be called with a value of 0, which means "reset the - // content scale factor to a device-specific default value", so we can't use the - // parameter here. We can query the actual factor using the getter - let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; - assert!( - !scale_factor.is_nan() - && scale_factor.is_finite() - && scale_factor.is_sign_positive() - && scale_factor > 0.0, - "invalid scale_factor set on UIView", - ); - let scale_factor: f64 = scale_factor.into(); - let bounds: CGRect = msg_send![object, bounds]; - let screen: id = msg_send![window, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![object, convertRect:bounds, toCoordinateSpace:screen_space]; - let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, - }; - app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - window_id: window, - scale_factor, - suggested_size: size, - })) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size.to_physical(scale_factor)), - }, - ))), - ); - } - } - - extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { - unsafe { - let window: id = msg_send![object, window]; - assert!(!window.is_null()); - let uiscreen: id = msg_send![window, screen]; - let touches_enum: id = msg_send![touches, objectEnumerator]; - let mut touch_events = Vec::new(); - let os_supports_force = app_state::os_capabilities().force_touch; - loop { - let touch: id = msg_send![touches_enum, nextObject]; - if touch == nil { - break; - } - let logical_location: CGPoint = msg_send![touch, locationInView: nil]; - let touch_type: UITouchType = msg_send![touch, type]; - let force = if os_supports_force { - let trait_collection: id = msg_send![object, traitCollection]; - let touch_capability: UIForceTouchCapability = - msg_send![trait_collection, forceTouchCapability]; - // Both the OS _and_ the device need to be checked for force touch support. - if touch_capability == UIForceTouchCapability::Available { - let force: CGFloat = msg_send![touch, force]; - let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; - let altitude_angle: Option = if touch_type == UITouchType::Pencil { - let angle: CGFloat = msg_send![touch, altitudeAngle]; - Some(angle as _) - } else { - None - }; - Some(Force::Calibrated { - force: force as _, - max_possible_force: max_possible_force as _, - altitude_angle, - }) - } else { - None - } - } else { - None - }; - let touch_id = touch as u64; - let phase: UITouchPhase = msg_send![touch, phase]; - let phase = match phase { - UITouchPhase::Began => TouchPhase::Started, - UITouchPhase::Moved => TouchPhase::Moved, - // 2 is UITouchPhase::Stationary and is not expected here - UITouchPhase::Ended => TouchPhase::Ended, - UITouchPhase::Cancelled => TouchPhase::Cancelled, - _ => panic!("unexpected touch phase: {:?}", phase as i32), - }; - - let physical_location = { - let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; - PhysicalPosition::from_logical::<(f64, f64), f64>( - (logical_location.x as _, logical_location.y as _), - scale_factor, - ) - }; - touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Touch(Touch { - device_id: RootDeviceId(DeviceId { uiscreen }), - id: touch_id, - location: physical_location, - force, - phase, - }), - })); - } - app_state::handle_nonuser_events(touch_events); - } - } - - let mut decl = ClassDecl::new( - &CString::new(format!("TaoUIView{}", ID)).unwrap(), - root_view_class, - ) - .expect("Failed to declare class `TaoUIView`"); - ID += 1; - decl.add_method(sel!(drawRect:), draw_rect as extern "C" fn(_, _, _)); - decl.add_method(sel!(layoutSubviews), layout_subviews as extern "C" fn(_, _)); - decl.add_method( - sel!(setContentScaleFactor:), - set_content_scale_factor as extern "C" fn(_, _, _), - ); - - decl.add_method( - sel!(touchesBegan:withEvent:), - handle_touches as extern "C" fn(_, _, _, _), - ); - decl.add_method( - sel!(touchesMoved:withEvent:), - handle_touches as extern "C" fn(_, _, _, _), - ); - decl.add_method( - sel!(touchesEnded:withEvent:), - handle_touches as extern "C" fn(_, _, _, _), - ); - decl.add_method( - sel!(touchesCancelled:withEvent:), - handle_touches as extern "C" fn(_, _, _, _), - ); - - decl.register() - }) -} - -// requires main thread -unsafe fn get_view_controller_class() -> &'static Class { - static mut CLASS: Option<&'static Class> = None; - if CLASS.is_none() { - let os_capabilities = app_state::os_capabilities(); - - let uiviewcontroller_class = class!(UIViewController); - - extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { - YES - } - - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoUIViewController\0").unwrap(), - uiviewcontroller_class, - ) - .expect("Failed to declare class `TaoUIViewController`"); - decl.add_method( - sel!(shouldAutorotate), - should_autorotate as extern "C" fn(_, _) -> _, - ); - add_property! { - decl, - prefers_status_bar_hidden: BOOL, - setPrefersStatusBarHidden: |object| { - unsafe { - let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; - } - }, - prefersStatusBarHidden, - } - add_property! { - decl, - prefers_home_indicator_auto_hidden: BOOL, - setPrefersHomeIndicatorAutoHidden: - os_capabilities.home_indicator_hidden, - OSCapabilities::home_indicator_hidden_err_msg; - |object| { - unsafe { - let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; - } - }, - prefersHomeIndicatorAutoHidden, - } - add_property! { - decl, - supported_orientations: UIInterfaceOrientationMask, - setSupportedInterfaceOrientations: |object| { - unsafe { - let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; - } - }, - supportedInterfaceOrientations, - } - add_property! { - decl, - preferred_screen_edges_deferring_system_gestures: UIRectEdge, - setPreferredScreenEdgesDeferringSystemGestures: - os_capabilities.defer_system_gestures, - OSCapabilities::defer_system_gestures_err_msg; - |object| { - unsafe { - let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; - } - }, - preferredScreenEdgesDeferringSystemGestures, - } - CLASS = Some(decl.register()); - } - CLASS.unwrap() -} - -// requires main thread -unsafe fn get_window_class() -> &'static Class { - static mut CLASS: Option<&'static Class> = None; - if CLASS.is_none() { - let uiwindow_class = class!(UIWindow); - - extern "C" fn become_key_window(object: &Object, _: Sel) { - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Focused(true), - })); - let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; - } - } - - extern "C" fn resign_key_window(object: &Object, _: Sel) { - unsafe { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Focused(false), - })); - let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; - } - } - - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoUIWindow\0").unwrap(), - uiwindow_class, - ) - .expect("Failed to declare class `TaoUIWindow`"); - decl.add_method( - sel!(becomeKeyWindow), - become_key_window as extern "C" fn(_, _), - ); - decl.add_method( - sel!(resignKeyWindow), - resign_key_window as extern "C" fn(_, _), - ); - - CLASS = Some(decl.register()); - } - CLASS.unwrap() -} - -// requires main thread -pub unsafe fn create_view( - _window_attributes: &WindowAttributes, - platform_attributes: &PlatformSpecificWindowBuilderAttributes, - frame: CGRect, -) -> id { - let class = get_view_class(platform_attributes.root_view_class); - - let view: id = msg_send![class, alloc]; - assert!(!view.is_null(), "Failed to create `UIView` instance"); - let view: id = msg_send![view, initWithFrame: frame]; - assert!(!view.is_null(), "Failed to initialize `UIView` instance"); - let () = msg_send![view, setMultipleTouchEnabled: YES]; - if let Some(scale_factor) = platform_attributes.scale_factor { - let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat]; - } - - view -} - -// requires main thread -pub unsafe fn create_view_controller( - _window_attributes: &WindowAttributes, - platform_attributes: &PlatformSpecificWindowBuilderAttributes, - view: id, -) -> id { - let class = get_view_controller_class(); - - let view_controller: id = msg_send![class, alloc]; - assert!( - !view_controller.is_null(), - "Failed to create `UIViewController` instance" - ); - let view_controller: id = msg_send![view_controller, init]; - assert!( - !view_controller.is_null(), - "Failed to initialize `UIViewController` instance" - ); - let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden { - YES - } else { - NO - }; - let idiom = event_loop::get_idiom(); - let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom( - platform_attributes.valid_orientations, - idiom, - ); - let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden { - YES - } else { - NO - }; - let edges: UIRectEdge = platform_attributes - .preferred_screen_edges_deferring_system_gestures - .into(); - let () = msg_send![ - view_controller, - setPrefersStatusBarHidden: status_bar_hidden - ]; - let () = msg_send![ - view_controller, - setSupportedInterfaceOrientations: supported_orientations - ]; - let () = msg_send![ - view_controller, - setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden - ]; - let () = msg_send![ - view_controller, - setPreferredScreenEdgesDeferringSystemGestures: edges - ]; - let () = msg_send![view_controller, setView: view]; - view_controller -} - -// requires main thread -pub unsafe fn create_window( - window_attributes: &WindowAttributes, - platform_attributes: &PlatformSpecificWindowBuilderAttributes, - frame: CGRect, - view_controller: id, -) -> id { - let class = get_window_class(); - - let window: id = msg_send![class, alloc]; - assert!(!window.is_null(), "Failed to create `UIWindow` instance"); - let window: id = msg_send![window, initWithFrame: frame]; - assert!( - !window.is_null(), - "Failed to initialize `UIWindow` instance" - ); - - if multiple_scenes_enabled() { - // if multiple scenes is enabled in Info.plist, we need to assign it - // regarless of whether the device supports multiple scenes or not - if let Some(scene) = app_state::unitialized_scene() { - let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; - } else if !app_supports_multiple_scenes() { - // if there's no unitialized scene and the app does not support multiple scenes - // we need to move this window to the main scene, otherwise it won't be visible - let scene = unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.connectedScenes().iter().next() - }; - let scene = scene - .as_ref() - .map(|s| Retained::as_ptr(s)) - .unwrap_or(std::ptr::null_mut()); - - let _: () = msg_send![window, setWindowScene: scene]; - } else { - // only request a new scene if the system actually supports it - // otherwise it is silently ignored - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - app_state::register_window_for_scene(window); - - let options = UISceneActivationRequestOptions::new(mtm); - if let Some(scene) = platform_attributes - .requesting_scene_identifier - .as_ref() - .and_then(|id| app_state::scene_by_id(id)) - { - options.setRequestingScene(Some(&scene)); - } - - let error_handler = block2::RcBlock::new(move |error| { - log::error!("error activating scene: {error:?}"); - }); - - if operating_system_version().0 >= 17 { - let request = UISceneSessionActivationRequest::request(); - request.setOptions(Some(&options)); - application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); - } else { - #[allow(deprecated)] - application.requestSceneSessionActivation_userActivity_options_errorHandler( - None, - None, - Some(&options), - Some(&error_handler), - ); - } - } - } - } - - let () = msg_send![window, setRootViewController: view_controller]; - match window_attributes.fullscreen { - Some(Fullscreen::Exclusive(ref video_mode)) => { - let uiscreen = video_mode.monitor().ui_screen() as id; - let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; - msg_send![window, setScreen:video_mode.monitor().ui_screen()] - } - Some(Fullscreen::Borderless(ref monitor)) => { - let uiscreen: id = match &monitor { - Some(monitor) => monitor.ui_screen() as id, - None => { - let uiscreen: id = msg_send![window, screen]; - uiscreen - } - }; - - msg_send![window, setScreen: uiscreen] - } - None => (), - } - - window -} - -pub fn create_delegate_class() { - extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id, _: id) -> BOOL { - unsafe { - app_state::did_finish_launching(); - } - YES - } - - extern "C" fn configuration_for_connecting_scene_session( - _: &Object, - _: Sel, - _application: id, - _connecting_scene_session: id, - _options: id, - ) -> id { - unsafe { - let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); - let config = UISceneConfiguration::configurationWithName_sessionRole( - Some(&NSString::from_str("TaoScene")), - &NSString::from_str("UIWindowSceneSessionRoleApplication"), - mtm, - ); - - // Dynamically set the delegate class name - config.setDelegateClass(Some(super::scene::TaoSceneDelegate::class())); - Retained::as_ptr(&config) as _ - } - } - - fn handle_deep_link(url: id) { - unsafe { - let absolute_url: id = msg_send![url, absoluteString]; - let bytes = { - let bytes: *const c_char = msg_send![absolute_url, UTF8String]; - bytes as *const u8 - }; - - // 4 represents utf8 encoding - let len = msg_send![absolute_url, lengthOfBytesUsingEncoding: 4]; - let bytes = std::slice::from_raw_parts(bytes, len); - - let url = url::Url::parse(std::str::from_utf8(bytes).unwrap()).unwrap(); - - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls: vec![url] })); - } - } - - // custom URL schemes - // https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app - extern "C" fn application_open_url( - _self: &Object, - _cmd: Sel, - _app: id, - url: id, - _options: id, - ) -> BOOL { - handle_deep_link(url); - - YES - } - - // universal links - // https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app - extern "C" fn application_continue( - _: &Object, - _: Sel, - _application: id, - user_activity: id, - _restoration_handler: id, - ) -> BOOL { - unsafe { - let webpage_url: id = msg_send![user_activity, webpageURL]; - if webpage_url == nil { - return NO; - } - - handle_deep_link(webpage_url); - - YES - } - } - - extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } - } - - extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } - } - - extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {} - - extern "C" fn will_terminate(_: &Object, _: Sel, _: id) { - unsafe { - let app: id = msg_send![class!(UIApplication), sharedApplication]; - let windows: id = msg_send![app, windows]; - let windows_enum: id = msg_send![windows, objectEnumerator]; - let mut events = Vec::new(); - loop { - let window: id = msg_send![windows_enum, nextObject]; - if window == nil { - break; - } - let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; - if is_tao_window { - events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Destroyed, - })); - } - } - app_state::handle_nonuser_events(events); - app_state::terminated(); - } - } - - let ui_responder = class!(UIResponder); - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"AppDelegate\0").unwrap(), - ui_responder, - ) - .expect("Failed to declare class `AppDelegate`"); - - unsafe { - decl.add_method( - sel!(application:didFinishLaunchingWithOptions:), - did_finish_launching as extern "C" fn(_, _, _, _) -> _, - ); - - if multiple_scenes_enabled() { - decl.add_method( - sel!(application:configurationForConnectingSceneSession:options:), - configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, - ); - } - - decl.add_method( - sel!(application:openURL:options:), - application_open_url as extern "C" fn(_, _, _, _, _) -> _, - ); - - decl.add_method( - sel!(application:continueUserActivity:restorationHandler:), - application_continue as extern "C" fn(_, _, _, _, _) -> _, - ); - - decl.add_method( - sel!(applicationWillResignActive:), - will_resign_active as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(applicationWillEnterForeground:), - will_enter_foreground as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(applicationDidEnterBackground:), - did_enter_background as extern "C" fn(_, _, _), - ); - - decl.add_method( - sel!(applicationWillTerminate:), - will_terminate as extern "C" fn(_, _, _), - ); - - decl.register(); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs deleted file mode 100644 index 7407a4b0a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs +++ /dev/null @@ -1,877 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::VecDeque, - ops::{Deref, DerefMut}, -}; - -use objc2::{ - rc::Retained, - runtime::{AnyClass, AnyObject}, - MainThreadMarker, -}; -use objc2_ui_kit::{ - UIApplication, UISceneActivationState, UISceneSessionActivationRequest, UIWindow, -}; - -use crate::{ - dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - event::{Event, WindowEvent}, - icon::Icon, - monitor::MonitorHandle as RootMonitorHandle, - platform::ios::{operating_system_version, MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, - platform_impl::platform::{ - app_state, - event_loop::{self, EventProxy, EventWrapper}, - ffi::{ - id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge, - UIScreenOverscanCompensation, NO, YES, - }, - monitor, set_badge_count, view, EventLoopWindowTarget, MonitorHandle, - }, - window::{ - CursorIcon, Fullscreen, ResizeDirection, Theme, UserAttentionType, WindowAttributes, - WindowId as RootWindowId, WindowSizeConstraints, - }, -}; - -pub struct Inner { - pub window: id, - pub view_controller: id, - pub view: id, - gl_or_metal_backed: bool, -} - -impl Drop for Inner { - fn drop(&mut self) { - unsafe { - let window = self.window(); - if let Some(scene) = window.windowScene() { - // our windows are tied to scenes - so when this is the last window of the scene, - // request the scene to be destroyed - if scene.windows().count() == 1 { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.requestSceneSessionDestruction_options_errorHandler( - &scene.session(), - None, - None, - ); - } - } - let () = msg_send![self.view, release]; - let () = msg_send![self.view_controller, release]; - let () = msg_send![self.window, release]; - - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(self.window.into()), - event: WindowEvent::Focused(false), - })); - } - } -} - -impl Inner { - pub fn set_title(&self, _title: &str) { - debug!("`Window::set_title` is ignored on iOS") - } - - pub fn title(&self) -> String { - String::new() - } - pub fn set_visible(&self, visible: bool) { - match visible { - true => unsafe { - let () = msg_send![self.window, setHidden: NO]; - }, - false => unsafe { - let () = msg_send![self.window, setHidden: YES]; - }, - } - } - - pub fn set_focus(&self) { - unsafe { - let window = self.window(); - // only call makeKeyAndVisible() when Info.plist was not set up to support scenes - let Some(scene) = window.windowScene() else { - window.makeKeyAndVisible(); - return; - }; - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - - let error_handler = block2::RcBlock::new(move |error| { - log::error!("error activating scene: {error:?}"); - }); - - // when we support multiple scenes, request the activation of this window's scene - if application.supportsMultipleScenes() { - if operating_system_version().0 >= 17 { - application.activateSceneSessionForRequest_errorHandler( - &UISceneSessionActivationRequest::request(), - Some(&error_handler), - ); - } else { - #[allow(deprecated)] - application.requestSceneSessionActivation_userActivity_options_errorHandler( - Some(&scene.session()), - None, - None, - Some(&error_handler), - ); - } - } else { - window.makeKeyAndVisible(); - } - } - } - - pub fn set_focusable(&self, _focusable: bool) { - warn!("set_focusable not yet implemented on iOS"); - } - - pub fn is_focused(&self) -> bool { - unsafe { - self - .window() - .windowScene() - .map(|scene| scene.activationState() == UISceneActivationState::ForegroundActive) - .unwrap_or_default() - } - } - - pub fn is_always_on_top(&self) -> bool { - log::warn!("`Window::is_always_on_top` is ignored on iOS"); - false - } - - pub fn request_redraw(&self) { - unsafe { - if self.gl_or_metal_backed { - // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. - // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using - // raw or gl/metal for drawing this work is completely avoided. - // - // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via - // testing. - // - // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc - app_state::queue_gl_or_metal_redraw(self.window); - } else { - let () = msg_send![self.view, setNeedsDisplay]; - } - } - } - - pub fn inner_position(&self) -> Result, NotSupportedError> { - unsafe { - let safe_area = self.safe_area_screen_space(); - let position = LogicalPosition { - x: safe_area.origin.x as f64, - y: safe_area.origin.y as f64, - }; - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } - } - - pub fn outer_position(&self) -> Result, NotSupportedError> { - unsafe { - let screen_frame = self.screen_frame(); - let position = LogicalPosition { - x: screen_frame.origin.x as f64, - y: screen_frame.origin.y as f64, - }; - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } - } - - pub fn set_outer_position(&self, physical_position: Position) { - unsafe { - let scale_factor = self.scale_factor(); - let position = physical_position.to_logical::(scale_factor); - let screen_frame = self.screen_frame(); - let new_screen_frame = CGRect { - origin: CGPoint { - x: position.x as _, - y: position.y as _, - }, - size: screen_frame.size, - }; - let bounds = self.from_screen_space(new_screen_frame); - let () = msg_send![self.window, setBounds: bounds]; - } - } - - pub fn inner_size(&self) -> PhysicalSize { - unsafe { - let scale_factor = self.scale_factor(); - let safe_area = self.safe_area_screen_space(); - let size = LogicalSize { - width: safe_area.size.width as f64, - height: safe_area.size.height as f64, - }; - size.to_physical(scale_factor) - } - } - - pub fn outer_size(&self) -> PhysicalSize { - unsafe { - let scale_factor = self.scale_factor(); - let screen_frame = self.screen_frame(); - let size = LogicalSize { - width: screen_frame.size.width as f64, - height: screen_frame.size.height as f64, - }; - size.to_physical(scale_factor) - } - } - - pub fn set_inner_size(&self, _size: Size) { - warn!("not clear what `Window::set_inner_size` means on iOS"); - } - - pub fn set_min_inner_size(&self, _: Option) { - warn!("`Window::set_min_inner_size` is ignored on iOS") - } - pub fn set_max_inner_size(&self, _: Option) { - warn!("`Window::set_max_inner_size` is ignored on iOS") - } - pub fn set_inner_size_constraints(&self, _: WindowSizeConstraints) { - warn!("`Window::set_inner_size_constraints` is ignored on iOS") - } - - pub fn set_resizable(&self, _resizable: bool) { - warn!("`Window::set_resizable` is ignored on iOS") - } - - pub fn set_minimizable(&self, _minimizable: bool) { - warn!("`Window::set_minimizable` is ignored on iOS") - } - - pub fn set_maximizable(&self, _maximizable: bool) { - warn!("`Window::set_maximizable` is ignored on iOS") - } - - pub fn set_closable(&self, _closable: bool) { - warn!("`Window::set_closable` is ignored on iOS") - } - - pub fn scale_factor(&self) -> f64 { - unsafe { - let hidpi: CGFloat = msg_send![self.view, contentScaleFactor]; - hidpi as _ - } - } - - pub fn set_cursor_icon(&self, _cursor: CursorIcon) { - debug!("`Window::set_cursor_icon` ignored on iOS") - } - - pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn set_cursor_visible(&self, _visible: bool) { - debug!("`Window::set_cursor_visible` is ignored on iOS") - } - - pub fn cursor_position(&self) -> Result, ExternalError> { - debug!("`Window::cursor_position` is ignored on iOS"); - Ok((0, 0).into()) - } - - pub fn drag_window(&self) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn set_minimized(&self, _minimized: bool) { - warn!("`Window::set_minimized` is ignored on iOS") - } - - pub fn set_maximized(&self, _maximized: bool) { - warn!("`Window::set_maximized` is ignored on iOS") - } - - pub fn is_maximized(&self) -> bool { - warn!("`Window::is_maximized` is ignored on iOS"); - false - } - - pub fn is_minimized(&self) -> bool { - warn!("`Window::is_minimized` is ignored on iOS"); - false - } - - pub fn is_visible(&self) -> bool { - !self.window().isHidden() - } - - pub fn is_resizable(&self) -> bool { - warn!("`Window::is_resizable` is ignored on iOS"); - false - } - - pub fn is_minimizable(&self) -> bool { - warn!("`Window::is_minimizable` is ignored on iOS"); - false - } - - pub fn is_maximizable(&self) -> bool { - warn!("`Window::is_maximizable` is ignored on iOS"); - false - } - - pub fn is_closable(&self) -> bool { - warn!("`Window::is_closable` is ignored on iOS"); - false - } - - pub fn is_decorated(&self) -> bool { - warn!("`Window::is_decorated` is ignored on iOS"); - false - } - - pub fn set_fullscreen(&self, monitor: Option) { - unsafe { - let uiscreen = match monitor { - Some(Fullscreen::Exclusive(video_mode)) => { - let uiscreen = video_mode.video_mode.monitor.ui_screen() as id; - let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; - uiscreen - } - Some(Fullscreen::Borderless(monitor)) => monitor - .unwrap_or_else(|| self.current_monitor_inner()) - .ui_screen() as id, - None => { - warn!("`Window::set_fullscreen(None)` ignored on iOS"); - return; - } - }; - - // this is pretty slow on iOS, so avoid doing it if we can - let current: id = msg_send![self.window, screen]; - if uiscreen != current { - let () = msg_send![self.window, setScreen: uiscreen]; - } - - let bounds: CGRect = msg_send![uiscreen, bounds]; - let () = msg_send![self.window, setFrame: bounds]; - - // For external displays, we must disable overscan compensation or - // the displayed image will have giant black bars surrounding it on - // each side - let () = msg_send![ - uiscreen, - setOverscanCompensation: UIScreenOverscanCompensation::None - ]; - } - } - - pub fn fullscreen(&self) -> Option { - unsafe { - let monitor = self.current_monitor_inner(); - let uiscreen = monitor.inner.ui_screen(); - let screen_space_bounds = self.screen_frame(); - let screen_bounds: CGRect = msg_send![uiscreen, bounds]; - - // TODO: track fullscreen instead of relying on brittle float comparisons - if screen_space_bounds.origin.x == screen_bounds.origin.x - && screen_space_bounds.origin.y == screen_bounds.origin.y - && screen_space_bounds.size.width == screen_bounds.size.width - && screen_space_bounds.size.height == screen_bounds.size.height - { - Some(Fullscreen::Borderless(Some(monitor))) - } else { - None - } - } - } - - pub fn set_decorations(&self, _decorations: bool) { - warn!("`Window::set_decorations` is ignored on iOS") - } - - pub fn set_always_on_bottom(&self, _always_on_bottom: bool) { - warn!("`Window::set_always_on_bottom` is ignored on iOS") - } - - pub fn set_always_on_top(&self, _always_on_top: bool) { - warn!("`Window::set_always_on_top` is ignored on iOS") - } - - pub fn set_window_icon(&self, _icon: Option) { - warn!("`Window::set_window_icon` is ignored on iOS") - } - - pub fn set_ime_position(&self, _position: Position) { - warn!("`Window::set_ime_position` is ignored on iOS") - } - - pub fn request_user_attention(&self, _request_type: Option) { - warn!("`Window::request_user_attention` is ignored on iOS") - } - - pub fn set_background_color(&self, _color: Option) {} - - // Allow directly accessing the current monitor internally without unwrapping. - fn current_monitor_inner(&self) -> RootMonitorHandle { - unsafe { - let uiscreen: id = msg_send![self.window, screen]; - RootMonitorHandle { - inner: MonitorHandle::retained_new(uiscreen), - } - } - } - - pub fn current_monitor(&self) -> Option { - Some(self.current_monitor_inner()) - } - - pub fn available_monitors(&self) -> VecDeque { - unsafe { monitor::uiscreens() } - } - - #[inline] - pub fn monitor_from_point(&self, _x: f64, _y: f64) -> Option { - warn!("`Window::monitor_from_point` is ignored on iOS"); - None - } - - pub fn primary_monitor(&self) -> Option { - let monitor = unsafe { monitor::main_uiscreen() }; - Some(RootMonitorHandle { inner: monitor }) - } - - pub fn id(&self) -> WindowId { - self.window.into() - } - - #[cfg(feature = "rwh_04")] - pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { - let mut window_handle = rwh_04::UiKitHandle::empty(); - window_handle.ui_window = self.window as _; - window_handle.ui_view = self.view as _; - window_handle.ui_view_controller = self.view_controller as _; - rwh_04::RawWindowHandle::UiKit(window_handle) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { - let mut window_handle = rwh_05::UiKitWindowHandle::empty(); - window_handle.ui_window = self.window as _; - window_handle.ui_view = self.view as _; - window_handle.ui_view_controller = self.view_controller as _; - rwh_05::RawWindowHandle::UiKit(window_handle) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - pub fn raw_window_handle_rwh_06(&self) -> Result { - let mut window_handle = rwh_06::UiKitWindowHandle::new({ - std::ptr::NonNull::new(self.view as _).expect("self.view should never be null") - }); - window_handle.ui_view_controller = std::ptr::NonNull::new(self.view_controller as _); - Ok(rwh_06::RawWindowHandle::UiKit(window_handle)) - } - - #[cfg(feature = "rwh_06")] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::UiKit( - rwh_06::UiKitDisplayHandle::new(), - )) - } - - pub fn theme(&self) -> Theme { - Theme::Light - } - - /// Sets badge count on iOS launcher. 0 hides the count - pub fn set_badge_count(&self, count: i32) { - set_badge_count(count); - } - - // instead of returning an Option here, we default to an empty string - // scene lifecycle will be enforced anyway soon (iOS 27) - pub fn scene_identifier(&self) -> String { - unsafe { - let window = self.window(); - let Some(scene) = window.windowScene() else { - return "".into(); - }; - scene.session().persistentIdentifier().to_string() - } - } -} - -pub struct Window { - pub inner: Inner, -} - -impl Drop for Window { - fn drop(&mut self) { - unsafe { - assert_main_thread!("`Window::drop` can only be run on the main thread on iOS"); - } - } -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -impl Deref for Window { - type Target = Inner; - - fn deref(&self) -> &Inner { - unsafe { - assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); - } - &self.inner - } -} - -impl DerefMut for Window { - fn deref_mut(&mut self) -> &mut Inner { - unsafe { - assert_main_thread!("`Window` methods can only be run on the main thread on iOS"); - } - &mut self.inner - } -} - -impl Window { - pub fn new( - _event_loop: &EventLoopWindowTarget, - window_attributes: WindowAttributes, - platform_attributes: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - if window_attributes.always_on_top { - warn!("`WindowAttributes::always_on_top` is unsupported on iOS"); - } - // TODO: transparency, visible - - unsafe { - let screen = match window_attributes.fullscreen { - Some(Fullscreen::Exclusive(ref video_mode)) => { - video_mode.video_mode.monitor.ui_screen() as id - } - Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(), - Some(Fullscreen::Borderless(None)) | None => monitor::main_uiscreen().ui_screen() as id, - }; - - let screen_bounds: CGRect = msg_send![screen, bounds]; - - let frame = match window_attributes.inner_size { - Some(dim) => { - let scale_factor = msg_send![screen, scale]; - let size = dim.to_logical::(scale_factor); - CGRect { - origin: screen_bounds.origin, - size: CGSize { - width: size.width as _, - height: size.height as _, - }, - } - } - None => screen_bounds, - }; - - let view = view::create_view(&window_attributes, &platform_attributes, frame); - - let gl_or_metal_backed = { - let view_class: *const AnyClass = msg_send![view, class]; - let layer_class: *const AnyClass = msg_send![view_class, layerClass]; - let is_metal: bool = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; - let is_gl: bool = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; - is_metal || is_gl - }; - - let view_controller = - view::create_view_controller(&window_attributes, &platform_attributes, view); - let window = view::create_window( - &window_attributes, - &platform_attributes, - frame, - view_controller, - ); - - let result = Window { - inner: Inner { - window, - view_controller, - view, - gl_or_metal_backed, - }, - }; - app_state::set_key_window(window); - - // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` - // event on window creation if the DPI factor != 1.0 - let scale_factor: CGFloat = msg_send![view, contentScaleFactor]; - let scale_factor: f64 = scale_factor.into(); - if scale_factor != 1.0 { - let bounds: CGRect = msg_send![view, bounds]; - let screen: id = msg_send![window, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![view, convertRect:bounds, toCoordinateSpace:screen_space]; - let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, - }; - app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - window_id: window, - scale_factor, - suggested_size: size, - })) - .chain(std::iter::once(EventWrapper::StaticEvent( - Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size.to_physical(scale_factor)), - }, - ))), - ); - } - - Ok(result) - } - } -} - -// WindowExtIOS -impl Inner { - pub fn ui_window(&self) -> id { - self.window - } - pub fn ui_view_controller(&self) -> id { - self.view_controller - } - pub fn ui_view(&self) -> id { - self.view - } - pub fn window(&self) -> Retained { - unsafe { Retained::::retain(self.window as _).unwrap() } - } - - pub fn set_scale_factor(&self, scale_factor: f64) { - unsafe { - assert!( - dpi::validate_scale_factor(scale_factor), - "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" - ); - let scale_factor = scale_factor as CGFloat; - let () = msg_send![self.view, setContentScaleFactor: scale_factor]; - } - } - - pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { - unsafe { - let idiom = event_loop::get_idiom(); - let supported_orientations = - UIInterfaceOrientationMask::from_valid_orientations_idiom(valid_orientations, idiom); - msg_send![ - self.view_controller, - setSupportedInterfaceOrientations: supported_orientations - ] - } - } - - pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { - unsafe { - let prefers_home_indicator_hidden = if hidden { YES } else { NO }; - let () = msg_send![ - self.view_controller, - setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden - ]; - } - } - - pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { - let edges: UIRectEdge = edges.into(); - unsafe { - let () = msg_send![ - self.view_controller, - setPreferredScreenEdgesDeferringSystemGestures: edges - ]; - } - } - - pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { - unsafe { - let status_bar_hidden = if hidden { YES } else { NO }; - let () = msg_send![ - self.view_controller, - setPrefersStatusBarHidden: status_bar_hidden - ]; - } - } -} - -impl Inner { - // requires main thread - unsafe fn screen_frame(&self) -> CGRect { - self.to_screen_space(msg_send![self.window, bounds]) - } - - // requires main thread - unsafe fn to_screen_space(&self, rect: CGRect) -> CGRect { - let screen: id = msg_send![self.window, screen]; - if !screen.is_null() { - let screen_space: id = msg_send![screen, coordinateSpace]; - msg_send![self.window, convertRect:rect, toCoordinateSpace:screen_space] - } else { - rect - } - } - - // requires main thread - unsafe fn from_screen_space(&self, rect: CGRect) -> CGRect { - let screen: id = msg_send![self.window, screen]; - if !screen.is_null() { - let screen_space: id = msg_send![screen, coordinateSpace]; - msg_send![self.window, convertRect:rect, fromCoordinateSpace:screen_space] - } else { - rect - } - } - - // requires main thread - unsafe fn safe_area_screen_space(&self) -> CGRect { - let bounds: CGRect = msg_send![self.window, bounds]; - if app_state::os_capabilities().safe_area { - let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; - let safe_bounds = CGRect { - origin: CGPoint { - x: bounds.origin.x + safe_area.left, - y: bounds.origin.y + safe_area.top, - }, - size: CGSize { - width: bounds.size.width - safe_area.left - safe_area.right, - height: bounds.size.height - safe_area.top - safe_area.bottom, - }, - }; - self.to_screen_space(safe_bounds) - } else { - let screen_frame = self.to_screen_space(bounds); - let status_bar_frame: CGRect = { - let app: id = msg_send![class!(UIApplication), sharedApplication]; - assert!( - !app.is_null(), - "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS" - ); - msg_send![app, statusBarFrame] - }; - let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height { - (screen_frame.origin.y, screen_frame.size.height) - } else { - let y = status_bar_frame.size.height; - let height = - screen_frame.size.height - (status_bar_frame.size.height - screen_frame.origin.y); - (y, height) - }; - CGRect { - origin: CGPoint { - x: screen_frame.origin.x, - y, - }, - size: CGSize { - width: screen_frame.size.width, - height, - }, - } - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId { - window: id, -} - -impl WindowId { - pub unsafe fn dummy() -> Self { - WindowId { - window: std::ptr::null_mut(), - } - } -} - -unsafe impl Send for WindowId {} -unsafe impl Sync for WindowId {} - -impl From<&AnyObject> for WindowId { - fn from(window: &AnyObject) -> WindowId { - WindowId { - window: window as *const _ as _, - } - } -} - -impl From<&mut AnyObject> for WindowId { - fn from(window: &mut AnyObject) -> WindowId { - WindowId { - window: window as _, - } - } -} - -impl From for WindowId { - fn from(window: id) -> WindowId { - WindowId { window } - } -} - -impl From> for WindowId { - fn from(window: Retained) -> WindowId { - WindowId { - window: Retained::as_ptr(&window) as _, - } - } -} - -#[derive(Clone)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub root_view_class: &'static AnyClass, - pub scale_factor: Option, - pub valid_orientations: ValidOrientations, - pub prefers_home_indicator_hidden: bool, - pub prefers_status_bar_hidden: bool, - pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, - pub requesting_scene_identifier: Option, -} - -impl Default for PlatformSpecificWindowBuilderAttributes { - fn default() -> PlatformSpecificWindowBuilderAttributes { - PlatformSpecificWindowBuilderAttributes { - root_view_class: class!(UIView), - scale_factor: None, - valid_orientations: Default::default(), - prefers_home_indicator_hidden: false, - prefers_status_bar_hidden: false, - preferred_screen_edges_deferring_system_gestures: Default::default(), - requesting_scene_identifier: None, - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs deleted file mode 100644 index f31971d73..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{ - os::raw::{c_int, c_uchar}, - ptr, -}; - -use gtk::glib; -use x11_dl::{xinput2, xlib}; - -use crate::event::{DeviceEvent, ElementState, RawKeyEvent}; - -use super::keycode_from_scancode; - -/// Spawn Device event thread. Only works on x11 since wayland doesn't have such global events. -pub fn spawn(device_tx: glib::Sender) { - std::thread::spawn(move || unsafe { - let xlib = xlib::Xlib::open().unwrap(); - let xinput2 = xinput2::XInput2::open().unwrap(); - let display = (xlib.XOpenDisplay)(ptr::null()); - let root = (xlib.XDefaultRootWindow)(display); - // TODO Add more device event mask - let mask = xinput2::XI_RawKeyPressMask | xinput2::XI_RawKeyReleaseMask; - let mut event_mask = xinput2::XIEventMask { - deviceid: xinput2::XIAllMasterDevices, - mask: &mask as *const _ as *mut c_uchar, - mask_len: std::mem::size_of_val(&mask) as c_int, - }; - (xinput2.XISelectEvents)(display, root, &mut event_mask as *mut _, 1); - - #[allow(clippy::uninit_assumed_init)] - let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init(); - loop { - (xlib.XNextEvent)(display, &mut event); - - // XFilterEvent tells us when an event has been discarded by the input method. - // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, - // along with an extra copy of the KeyRelease events. This also prevents backspace and - // arrow keys from being detected twice. - if xlib::True == { - (xlib.XFilterEvent)(&mut event, { - let xev: &xlib::XAnyEvent = event.as_ref(); - xev.window - }) - } { - continue; - } - - let event_type = event.get_type(); - if event_type == xlib::GenericEvent { - let mut xev = event.generic_event_cookie; - if (xlib.XGetEventData)(display, &mut xev) == xlib::True { - match xev.evtype { - xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { - let xev: &xinput2::XIRawEvent = &*(xev.data as *const _); - let physical_key = keycode_from_scancode(xev.detail as u32); - let state = match xev.evtype { - xinput2::XI_RawKeyPress => ElementState::Pressed, - xinput2::XI_RawKeyRelease => ElementState::Released, - _ => unreachable!(), - }; - - let event = RawKeyEvent { - physical_key, - state, - }; - - if let Err(e) = device_tx.send(DeviceEvent::Key(event)) { - log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e); - break; - } - } - _ => {} - } - } - } - } - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs deleted file mode 100644 index 4e9fe979a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs +++ /dev/null @@ -1,1312 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - cell::RefCell, - collections::{HashSet, VecDeque}, - error::Error, - process, - rc::Rc, - sync::atomic::{AtomicBool, Ordering}, - time::Instant, -}; - -use cairo::{RectangleInt, Region}; -use crossbeam_channel::SendError; -use gdk::{Cursor, CursorType, EventKey, EventMask, ScrollDirection, WindowEdge, WindowState}; -use gio::Cancellable; -use glib::{source::Priority, MainContext}; -use gtk::{ - cairo, gdk, gio, - glib::{self}, - prelude::*, - Settings, -}; - -#[cfg(feature = "x11")] -use crate::platform_impl::platform::device; -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, - error::ExternalError, - event::{ - ElementState, Event, MouseButton, MouseScrollDelta, StartCause, TouchPhase, WindowEvent, - }, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - keyboard::ModifiersState, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::DEVICE_ID, - window::{ - CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, WindowId as RootWindowId, - }, -}; - -use super::{ - keyboard, - monitor::{self, MonitorHandle}, - taskbar, util, - window::{WindowId, WindowRequest}, -}; - -use taskbar::TaskbarIndicator; - -#[derive(Clone)] -pub struct EventLoopWindowTarget { - /// Gdk display - pub(crate) display: gdk::Display, - /// Gtk application - pub(crate) app: gtk::Application, - /// Window Ids of the application - pub(crate) windows: Rc>>, - /// Window requests sender - pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, - /// Draw event sender - pub(crate) draw_tx: crossbeam_channel::Sender, - _marker: std::marker::PhantomData, -} - -impl EventLoopWindowTarget { - #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - monitor::from_point(&self.display, x, y) - } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut handles = VecDeque::new(); - let display = &self.display; - let numbers = display.n_monitors(); - - for i in 0..numbers { - let monitor = MonitorHandle::new(display, i); - handles.push_back(monitor); - } - - handles - } - - #[inline] - pub fn primary_monitor(&self) -> Option { - let monitor = self.display.primary_monitor(); - monitor.map(|monitor| RootMonitorHandle { - inner: MonitorHandle { monitor }, - }) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - if self.is_wayland() { - let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); - display_handle.display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) - }; - rwh_05::RawDisplayHandle::Wayland(display_handle) - } else { - let mut display_handle = rwh_05::XlibDisplayHandle::empty(); - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - display_handle.display = display as _; - display_handle.screen = (xlib.XDefaultScreen)(display) as _; - } - } - - rwh_05::RawDisplayHandle::Xlib(display_handle) - } - } - - #[cfg(feature = "rwh_06")] - pub fn raw_display_handle_rwh_06(&self) -> Result { - if self.is_wayland() { - let display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.display.as_ptr() as *mut _) - }; - let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; - let display_handle = rwh_06::WaylandDisplayHandle::new(display); - Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) - } else { - #[cfg(feature = "x11")] - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - let screen = (xlib.XDefaultScreen)(display) as _; - let display = std::ptr::NonNull::new_unchecked(display as _); - let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); - Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) - } else { - Err(rwh_06::HandleError::Unavailable) - } - } - #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) - } - } - - pub fn is_wayland(&self) -> bool { - self.display.backend().is_wayland() - } - - #[cfg(feature = "x11")] - pub fn is_x11(&self) -> bool { - self.display.backend().is_x11() - } - - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position(self.is_wayland()) - } - - #[inline] - pub fn set_progress_bar(&self, progress: ProgressBarState) { - if let Err(e) = self - .window_requests_tx - .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) - { - log::warn!("Fail to send update progress bar request: {e}"); - } - } - - #[inline] - pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { - if let Err(e) = self.window_requests_tx.send(( - WindowId::dummy(), - WindowRequest::BadgeCount(count, desktop_filename), - )) { - log::warn!("Fail to send update progress bar request: {e}"); - } - } - - #[inline] - pub fn set_theme(&self, theme: Option) { - if let Err(e) = self - .window_requests_tx - .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) - { - log::warn!("Fail to send update theme request: {e}"); - } - } -} - -pub struct EventLoop { - /// Window target. - window_target: RootELW, - /// User event sender for EventLoopProxy - pub(crate) user_event_tx: crossbeam_channel::Sender>, - /// Event queue of EventLoop - events: crossbeam_channel::Receiver>, - /// Draw queue of EventLoop - draws: crossbeam_channel::Receiver, - /// Boolean to control device event thread - run_device_thread: Option>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PlatformSpecificEventLoopAttributes { - pub(crate) any_thread: bool, - pub(crate) app_id: Option, -} - -impl EventLoop { - pub(crate) fn new(attrs: &PlatformSpecificEventLoopAttributes) -> EventLoop { - if !attrs.any_thread { - assert_is_main_thread("new_any_thread"); - } - - let context = MainContext::default(); - context - .with_thread_default(|| { - EventLoop::new_gtk(attrs.app_id.as_deref()).expect("Failed to initialize gtk backend!") - }) - .expect("Failed to initialize gtk backend!") - } - - fn new_gtk(app_id: Option<&str>) -> Result, Box> { - // This should be done by gtk::Application::new, but does not work properly - gtk::init()?; - let context = MainContext::default(); - let app = gtk::Application::new(app_id, gio::ApplicationFlags::empty()); - let app_ = app.clone(); - let cancellable: Option<&Cancellable> = None; - app.register(cancellable)?; - - // Send StartCause::Init event - let (event_tx, event_rx) = crossbeam_channel::unbounded(); - let (draw_tx, draw_rx) = crossbeam_channel::unbounded(); - let event_tx_ = event_tx.clone(); - app.connect_activate(move |_| { - if let Err(e) = event_tx_.send(Event::NewEvents(StartCause::Init)) { - log::warn!("Failed to send init event to event channel: {}", e); - } - }); - let draw_tx_ = draw_tx.clone(); - let user_event_tx = event_tx.clone(); - - // Create event loop window target. - let (window_requests_tx, window_requests_rx) = glib::MainContext::channel(Priority::default()); - let display = gdk::Display::default() - .expect("GdkDisplay not found. This usually means `gkt_init` hasn't called yet."); - let window_target = EventLoopWindowTarget { - display, - app, - windows: Rc::new(RefCell::new(HashSet::new())), - window_requests_tx, - draw_tx: draw_tx_, - _marker: std::marker::PhantomData, - }; - - // Spawn x11 thread to receive Device events. - #[cfg(feature = "x11")] - let run_device_thread = if window_target.is_x11() { - let (device_tx, device_rx) = glib::MainContext::channel(glib::Priority::default()); - let user_event_tx = user_event_tx.clone(); - let run_device_thread = Rc::new(AtomicBool::new(true)); - let run = run_device_thread.clone(); - device::spawn(device_tx); - device_rx.attach(Some(&context), move |event| { - if let Err(e) = user_event_tx.send(Event::DeviceEvent { - device_id: DEVICE_ID, - event, - }) { - log::warn!("Fail to send device event to event channel: {}", e); - } - if run.load(Ordering::Relaxed) { - glib::ControlFlow::Continue - } else { - glib::ControlFlow::Break - } - }); - Some(run_device_thread) - } else { - None - }; - #[cfg(not(feature = "x11"))] - let run_device_thread = None; - - let mut taskbar = TaskbarIndicator::new(); - let is_wayland = window_target.is_wayland(); - - // Receive portal events - #[cfg(feature = "dbus")] - { - let tx_requests_clone = window_target.window_requests_tx.clone(); - if let Err(e) = super::portal::receive_theme_changed(tx_requests_clone) { - log::debug!("Unable to receive theme changed events: {e}"); - } - } - - // Window Request - window_requests_rx.attach(Some(&context), move |(id, request)| { - if let Some(window) = app_.window_by_id(id.0) { - match request { - WindowRequest::Title(title) => window.set_title(&title), - WindowRequest::Position((x, y)) => window.move_(x, y), - WindowRequest::Size((w, h)) => window.resize(w, h), - WindowRequest::SizeConstraints(constraints) => { - util::set_size_constraints(&window, constraints); - } - WindowRequest::Visible(visible) => { - if visible { - window.show_all(); - } else { - window.hide(); - } - } - WindowRequest::Focus => { - window.present_with_time(gdk::ffi::GDK_CURRENT_TIME as _); - } - WindowRequest::Resizable(resizable) => window.set_resizable(resizable), - WindowRequest::Closable(closable) => window.set_deletable(closable), - WindowRequest::Minimized(minimized) => { - if minimized { - window.iconify(); - } else { - window.deiconify(); - } - } - WindowRequest::Maximized(maximized, resizable) => { - if maximized { - let maximize_process = util::WindowMaximizeProcess::new(window.clone(), resizable); - glib::idle_add_local_full(glib::Priority::DEFAULT_IDLE, move || { - let mut maximize_process = maximize_process.borrow_mut(); - maximize_process.next_step() - }); - } else { - window.unmaximize(); - } - } - WindowRequest::DragWindow => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - let (_, x, y) = cursor.position(); - window.begin_move_drag(1, x, y, 0); - } - } - WindowRequest::DragResizeWindow(direction) => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - let (_, x, y) = cursor.position(); - window.begin_resize_drag( - direction.to_gtk_edge(), - 1, - x, - y, - gtk::gdk::ffi::GDK_CURRENT_TIME as _, - ); - } - } - WindowRequest::Fullscreen(fullscreen) => match fullscreen { - Some(f) => { - if let Fullscreen::Borderless(m) = f { - if let Some(monitor) = m { - let display = window.display(); - let monitor = monitor.inner; - let monitors = display.n_monitors(); - for i in 0..monitors { - let m = display.monitor(i).unwrap(); - if m == monitor.monitor { - let screen = display.default_screen(); - window.fullscreen_on_monitor(&screen, i); - } - } - } else { - window.fullscreen(); - } - } - } - None => window.unfullscreen(), - }, - WindowRequest::Decorations(decorations) => window.set_decorated(decorations), - WindowRequest::AlwaysOnBottom(always_on_bottom) => { - window.set_keep_below(always_on_bottom) - } - WindowRequest::AlwaysOnTop(always_on_top) => window.set_keep_above(always_on_top), - WindowRequest::WindowIcon(window_icon) => { - if let Some(icon) = window_icon { - window.set_icon(Some(&icon.inner.into())); - } - } - WindowRequest::UserAttention(request_type) => { - window.set_urgency_hint(request_type.is_some()) - } - WindowRequest::SetSkipTaskbar(skip) => { - window.set_skip_taskbar_hint(skip); - window.set_skip_pager_hint(skip) - } - WindowRequest::BackgroundColor(css_provider, color) => { - unsafe { window.set_data("background_color", color) }; - - let style_context = window.style_context(); - style_context.remove_provider(&css_provider); - - if let Some(color) = color { - let theme = format!( - r#" - window {{ - background-color: rgba({},{},{},{}); - }} - "#, - color.0, - color.1, - color.2, - color.3 as f64 / 255.0 - ); - let _ = css_provider.load_from_data(theme.as_bytes()); - style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); - }; - } - WindowRequest::SetVisibleOnAllWorkspaces(visible) => { - if visible { - window.stick(); - } else { - window.unstick(); - } - } - WindowRequest::CursorIcon(cursor) => { - if let Some(gdk_window) = window.window() { - let display = window.display(); - match cursor { - Some(cr) => { - gdk_window.set_cursor(Cursor::from_name(&display, cr.to_str()).as_ref()) - } - None => gdk_window - .set_cursor(Cursor::for_display(&display, CursorType::BlankCursor).as_ref()), - } - }; - } - WindowRequest::CursorPosition((x, y)) => { - if let Some(cursor) = window - .display() - .default_seat() - .and_then(|seat| seat.pointer()) - { - if let Some(screen) = GtkWindowExt::screen(&window) { - cursor.warp(&screen, x, y); - } - } - } - WindowRequest::CursorIgnoreEvents(ignore) => { - if ignore { - let empty_region = Region::create_rectangle(&RectangleInt::new(0, 0, 1, 1)); - window - .window() - .unwrap() - .input_shape_combine_region(&empty_region, 0, 0); - } else { - window.input_shape_combine_region(None) - }; - } - WindowRequest::ProgressBarState(_) => unreachable!(), - WindowRequest::BadgeCount(_, _) => unreachable!(), - WindowRequest::SetTheme(_) => unreachable!(), - WindowRequest::WireUpEvents { - transparent, - fullscreen, - cursor_moved, - } => { - window.add_events( - EventMask::POINTER_MOTION_MASK - | EventMask::BUTTON1_MOTION_MASK - | EventMask::BUTTON_PRESS_MASK - | EventMask::TOUCH_MASK - | EventMask::STRUCTURE_MASK - | EventMask::FOCUS_CHANGE_MASK - | EventMask::SCROLL_MASK, - ); - - let fullscreen = Rc::new(AtomicBool::new(fullscreen)); - let fullscreen_ = fullscreen.clone(); - window.connect_window_state_event(move |_window, event| { - let state = event.changed_mask(); - if state.contains(WindowState::FULLSCREEN) { - fullscreen_.store( - event.new_window_state().contains(WindowState::FULLSCREEN), - Ordering::Relaxed, - ); - } - glib::Propagation::Proceed - }); - - // Allow resizing unmaximized non-fullscreen undecorated window - let fullscreen_ = fullscreen.clone(); - window.connect_motion_notify_event(move |window, event| { - if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { - if let Some(window) = window.window() { - let (cx, cy) = event.root(); - let (left, top) = window.position(); - let (w, h) = (window.width(), window.height()); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ); - - let edge = match &edge { - Some(e) if !fullscreen_.load(Ordering::Relaxed) => e.to_cursor_str(), - _ => "default", - }; - window.set_cursor(Cursor::from_name(&window.display(), edge).as_ref()); - } - } - glib::Propagation::Proceed - }); - window.connect_button_press_event(move |window, event| { - const LMB: u32 = 1; - if (is_wayland || !window.is_decorated()) - && window.is_resizable() - && !window.is_maximized() - && event.button() == LMB - { - let (cx, cy) = event.root(); - let (left, top) = window.position(); - let (w, h) = window.size(); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ) - .map(|d| d.to_gtk_edge()) - // we return `WindowEdge::__Unknown` to be ignored later. - // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. - .unwrap_or(WindowEdge::__Unknown(8)); - // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. - match edge { - WindowEdge::__Unknown(_) => (), - _ => { - // FIXME: calling `window.begin_resize_drag` uses the default cursor, it should show a resizing cursor instead - window.begin_resize_drag(edge, LMB as i32, cx as i32, cy as i32, event.time()) - } - } - } - - glib::Propagation::Proceed - }); - window.connect_touch_event(move |window, event| { - if !window.is_decorated() && window.is_resizable() && !window.is_maximized() { - if let Some(window) = window.window() { - if let Some((cx, cy)) = event.root_coords() { - if let Some(device) = event.device() { - let (left, top) = window.position(); - let (w, h) = (window.width(), window.height()); - let (right, bottom) = (left + w, top + h); - let border = window.scale_factor() * 5; - let edge = crate::window::hit_test( - (left, top, right, bottom), - cx as _, - cy as _, - border, - border, - ) - .map(|d| d.to_gtk_edge()) - // we return `WindowEdge::__Unknown` to be ignored later. - // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum. - .unwrap_or(WindowEdge::__Unknown(8)); - - // Ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges. - match edge { - WindowEdge::__Unknown(_) => (), - _ => window.begin_resize_drag_for_device( - edge, - &device, - 0, - cx as i32, - cy as i32, - event.time(), - ), - } - } - } - } - } - - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_delete_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CloseRequested, - }) { - log::warn!("Failed to send window close event to event channel: {}", e); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_configure_event(move |window, event| { - let scale_factor = window.scale_factor(); - - let (x, y) = window - .window() - .map(|w| w.root_origin()) - .unwrap_or_else(|| event.position()); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Moved( - LogicalPosition::new(x, y).to_physical(scale_factor as f64), - ), - }) { - log::warn!("Failed to send window moved event to event channel: {}", e); - } - - let (w, h) = event.size(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized( - LogicalSize::new(w, h).to_physical(scale_factor as f64), - ), - }) { - log::warn!( - "Failed to send window resized event to event channel: {}", - e - ); - } - false - }); - - let tx_clone = event_tx.clone(); - window.connect_focus_in_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(true), - }) { - log::warn!( - "Failed to send window focus-in event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_focus_out_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(false), - }) { - log::warn!( - "Failed to send window focus-out event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_destroy(move |_| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Destroyed, - }) { - log::warn!( - "Failed to send window destroyed event to event channel: {}", - e - ); - } - }); - - let tx_clone = event_tx.clone(); - window.connect_enter_notify_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorEntered { - device_id: DEVICE_ID, - }, - }) { - log::warn!( - "Failed to send cursor entered event to event channel: {}", - e - ); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_motion_notify_event(move |window, motion| { - if cursor_moved { - if let Some(cursor) = motion.device() { - let scale_factor = window.scale_factor(); - let (_, x, y) = cursor.window_at_position(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - position: LogicalPosition::new(x, y).to_physical(scale_factor as f64), - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!("Failed to send cursor moved event to event channel: {}", e); - } - } - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_leave_notify_event(move |_, _| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorLeft { - device_id: DEVICE_ID, - }, - }) { - log::warn!("Failed to send cursor left event to event channel: {}", e); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_button_press_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), - }, - state: ElementState::Pressed, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input pressed event to event channel: {}", - e - ); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_button_release_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), - }, - state: ElementState::Released, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input released event to event channel: {}", - e - ); - } - glib::Propagation::Stop - }); - - let tx_clone = event_tx.clone(); - window.connect_scroll_event(move |_, event| { - let (x, y) = event.delta(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta: MouseScrollDelta::LineDelta(-x as f32, -y as f32), - phase: match event.direction() { - ScrollDirection::Smooth => TouchPhase::Moved, - _ => TouchPhase::Ended, - }, - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!("Failed to send scroll event to event channel: {}", e); - } - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - let keyboard_handler = Rc::new(move |event_key: EventKey, element_state| { - // if we have a modifier lets send it - let mut mods = keyboard::get_modifiers(event_key.clone()); - if !mods.is_empty() { - // if we release the modifier tell the world - if ElementState::Released == element_state { - mods = ModifiersState::empty(); - } - - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(mods), - }) { - log::warn!( - "Failed to send modifiers changed event to event channel: {}", - e - ); - } else { - // stop here we don't want to send the key event - // as we emit the `ModifiersChanged` - return glib::ControlFlow::Continue; - } - } - - // todo: implement repeat? - let event = keyboard::make_key_event(&event_key, false, None, element_state); - - if let Some(event) = event { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event, - is_synthetic: false, - }, - }) { - log::warn!("Failed to send keyboard event to event channel: {}", e); - } - } - glib::ControlFlow::Continue - }); - - let tx_clone = event_tx.clone(); - // TODO Add actual IME from system - let ime = gtk::IMContextSimple::default(); - ime.set_client_window(window.window().as_ref()); - ime.focus_in(); - ime.connect_commit(move |_, s| { - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ReceivedImeText(s.to_string()), - }) { - log::warn!( - "Failed to send received IME text event to event channel: {}", - e - ); - } - }); - - let handler = keyboard_handler.clone(); - window.connect_key_press_event(move |_, event_key| { - handler(event_key.to_owned(), ElementState::Pressed); - ime.filter_keypress(event_key); - - glib::Propagation::Proceed - }); - - let handler = keyboard_handler.clone(); - window.connect_key_release_event(move |_, event_key| { - handler(event_key.to_owned(), ElementState::Released); - glib::Propagation::Proceed - }); - - let tx_clone = event_tx.clone(); - window.connect_window_state_event(move |window, event| { - let state = event.changed_mask(); - if state.contains(WindowState::ICONIFIED) || state.contains(WindowState::MAXIMIZED) { - let scale_factor = window.scale_factor(); - - let (x, y) = window.position(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Moved( - LogicalPosition::new(x, y).to_physical(scale_factor as f64), - ), - }) { - log::warn!("Failed to send window moved event to event channel: {}", e); - } - - let (w, h) = window.size(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Resized( - LogicalSize::new(w, h).to_physical(scale_factor as f64), - ), - }) { - log::warn!( - "Failed to send window resized event to event channel: {}", - e - ); - } - } - glib::Propagation::Proceed - }); - - // Receive draw events of the window. - let draw_clone = draw_tx.clone(); - window.connect_draw(move |window, cr| { - if let Err(e) = draw_clone.send(id) { - log::warn!("Failed to send redraw event to event channel: {}", e); - } - - if transparent { - let background_color = unsafe { - window - .data::>("background_color") - .and_then(|c| *c.as_ref()) - }; - - let rgba = background_color - .map(|(r, g, b, a)| (r as f64, g as f64, b as f64, a as f64 / 255.0)) - .unwrap_or((0., 0., 0., 0.)); - - let rect = window - .child() - .map(|c| c.allocation()) - .unwrap_or_else(|| window.allocation()); - - cr.rectangle( - rect.x() as _, - rect.y() as _, - rect.width() as _, - rect.height() as _, - ); - cr.set_source_rgba(rgba.0, rgba.1, rgba.2, rgba.3); - cr.set_operator(cairo::Operator::Source); - let _ = cr.fill(); - cr.set_operator(cairo::Operator::Over); - } - - glib::Propagation::Proceed - }); - } - } - } else if id == WindowId::dummy() { - match request { - WindowRequest::ProgressBarState(state) => { - taskbar.update(state); - } - WindowRequest::BadgeCount(count, desktop_filename) => { - taskbar.update_count(count, desktop_filename); - } - WindowRequest::SetTheme(theme) => { - if let Some(settings) = Settings::default() { - settings.set_gtk_application_prefer_dark_theme(theme == Some(Theme::Dark)); - if let Err(e) = event_tx.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ThemeChanged(theme.unwrap_or_default()), - }) { - log::warn!( - "Failed to send window theme changed event to event channel: {}", - e - ); - } - } - } - _ => unreachable!(), - } - } - glib::ControlFlow::Continue - }); - - // Create event loop itself. - let event_loop = Self { - window_target: RootELW { - p: window_target, - _marker: std::marker::PhantomData, - }, - user_event_tx, - events: event_rx, - draws: draw_rx, - run_device_thread, - }; - - Ok(event_loop) - } - - #[inline] - pub fn run(mut self, callback: F) -> ! - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow) + 'static, - { - let exit_code = self.run_return(callback); - process::exit(exit_code) - } - - /// This is the core event loop logic. It basically loops on `gtk_main_iteration` and processes one - /// event along with that iteration. Depends on current control flow and what it should do, an - /// event state is defined. The whole state flow chart runs like following: - /// - /// ```ignore - /// Poll/Wait/WaitUntil - /// +-------------------------------------------------------------------------+ - /// | | - /// | Receiving event from event channel | Receiving event from draw channel - /// | +-------+ | +---+ - /// v v | | v | - /// +----------+ Poll/Wait/WaitUntil +------------+ Poll/Wait/WaitUntil +-----------+ | - /// | NewStart | ---------------------> | EventQueue | ---------------------> | DrawQueue | | - /// +----------+ +------------+ +-----------+ | - /// |ExitWithCode |ExitWithCode ExitWithCode| | | - /// +------------------------------------+------------------------------------+ +---+ - /// | - /// v - /// +---------------+ - /// | LoopDestroyed | - /// +---------------+ - /// ``` - /// - /// There are a dew notibale event will sent to callback when state is transisted: - /// - On any state moves to `LoopDestroyed`, a `LoopDestroyed` event is sent. - /// - On `NewStart` to `EventQueue`, a `NewEvents` with corresponding `StartCause` depends on - /// current control flow is sent. - /// - On `EventQueue` to `DrawQueue`, a `MainEventsCleared` event is sent. - /// - On `DrawQueue` back to `NewStart`, a `RedrawEventsCleared` event is sent. - pub(crate) fn run_return(&mut self, mut callback: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - enum EventState { - NewStart, - EventQueue, - DrawQueue, - } - - let context = MainContext::default(); - let run_device_thread = self.run_device_thread.clone(); - - context - .with_thread_default(|| { - let mut control_flow = ControlFlow::default(); - let window_target = &self.window_target; - let events = &self.events; - let draws = &self.draws; - - window_target.p.app.activate(); - - // If this is a secondary (remote) GIO instance, the activate signal - // was forwarded to the primary instance via D-Bus. Exit immediately so - // the primary can handle focus (e.g. bring its window to front). - // Without this, the secondary hangs forever waiting for a StartCause::Init - // event that never arrives (connect_activate only fires on the primary). - if window_target.p.app.is_remote() { - return 0; - } - - let mut state = EventState::NewStart; - let exit_code = loop { - let mut blocking = false; - match state { - EventState::NewStart => match control_flow { - ControlFlow::ExitWithCode(code) => { - callback(Event::LoopDestroyed, window_target, &mut control_flow); - break code; - } - ControlFlow::Wait => { - if !events.is_empty() { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }), - window_target, - &mut control_flow, - ); - state = EventState::EventQueue; - } else { - blocking = true; - } - } - ControlFlow::WaitUntil(requested_resume) => { - let start = Instant::now(); - if start >= requested_resume { - callback( - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume, - }), - window_target, - &mut control_flow, - ); - state = EventState::EventQueue; - } else if !events.is_empty() { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - }), - window_target, - &mut control_flow, - ); - state = EventState::EventQueue; - } else { - blocking = true; - } - } - _ => { - callback( - Event::NewEvents(StartCause::Poll), - window_target, - &mut control_flow, - ); - state = EventState::EventQueue; - } - }, - EventState::EventQueue => match control_flow { - ControlFlow::ExitWithCode(code) => { - callback(Event::LoopDestroyed, window_target, &mut control_flow); - break code; - } - _ => match events.try_recv() { - Ok(event) => match event { - Event::LoopDestroyed => control_flow = ControlFlow::ExitWithCode(1), - _ => callback(event, window_target, &mut control_flow), - }, - Err(_) => { - callback(Event::MainEventsCleared, window_target, &mut control_flow); - state = EventState::DrawQueue; - } - }, - }, - EventState::DrawQueue => match control_flow { - ControlFlow::ExitWithCode(code) => { - callback(Event::LoopDestroyed, window_target, &mut control_flow); - break code; - } - _ => { - if let Ok(id) = draws.try_recv() { - callback( - Event::RedrawRequested(RootWindowId(id)), - window_target, - &mut control_flow, - ); - } - callback(Event::RedrawEventsCleared, window_target, &mut control_flow); - state = EventState::NewStart; - } - }, - } - gtk::main_iteration_do(blocking); - }; - if let Some(run_device_thread) = run_device_thread { - run_device_thread.store(false, Ordering::Relaxed); - } - exit_code - }) - .unwrap_or(1) - } - - #[inline] - pub fn window_target(&self) -> &RootELW { - &self.window_target - } - - /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop. - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - user_event_tx: self.user_event_tx.clone(), - } - } -} - -/// Used to send custom events to `EventLoop`. -#[derive(Debug)] -pub struct EventLoopProxy { - user_event_tx: crossbeam_channel::Sender>, -} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - Self { - user_event_tx: self.user_event_tx.clone(), - } - } -} - -impl EventLoopProxy { - /// Send an event to the `EventLoop` from which this proxy was created. This emits a - /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this - /// function. - /// - /// Returns an `Err` if the associated `EventLoop` no longer exists. - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self - .user_event_tx - .send(Event::UserEvent(event)) - .map_err(|SendError(event)| { - if let Event::UserEvent(error) = event { - EventLoopClosed(error) - } else { - unreachable!(); - } - })?; - - let context = MainContext::default(); - context.wakeup(); - - Ok(()) - } -} - -fn assert_is_main_thread(suggested_method: &str) { - assert!( - is_main_thread(), - "Initializing the event loop outside of the main thread is a significant \ - cross-platform compatibility hazard. If you really, absolutely need to create an \ - EventLoop on a different thread, please use the `EventLoopExtUnix::{suggested_method}` function." - ); -} - -#[cfg(target_os = "linux")] -fn is_main_thread() -> bool { - use libc::{c_long, getpid, syscall, SYS_gettid}; - - unsafe { syscall(SYS_gettid) == getpid() as c_long } -} - -#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] -fn is_main_thread() -> bool { - use libc::pthread_main_np; - - unsafe { pthread_main_np() == 1 } -} - -#[cfg(target_os = "netbsd")] -fn is_main_thread() -> bool { - std::thread::current().name() == Some("main") -} - -impl CursorIcon { - fn to_str(&self) -> &str { - match self { - CursorIcon::Crosshair => "crosshair", - CursorIcon::Hand => "pointer", - CursorIcon::Arrow => "arrow", - CursorIcon::Move => "move", - CursorIcon::Text => "text", - CursorIcon::Wait => "wait", - CursorIcon::Help => "help", - CursorIcon::Progress => "progress", - CursorIcon::NotAllowed => "not-allowed", - CursorIcon::ContextMenu => "context-menu", - CursorIcon::Cell => "cell", - CursorIcon::VerticalText => "vertical-text", - CursorIcon::Alias => "alias", - CursorIcon::Copy => "copy", - CursorIcon::NoDrop => "no-drop", - CursorIcon::Grab => "grab", - CursorIcon::Grabbing => "grabbing", - CursorIcon::AllScroll => "all-scroll", - CursorIcon::ZoomIn => "zoom-in", - CursorIcon::ZoomOut => "zoom-out", - CursorIcon::EResize => "e-resize", - CursorIcon::NResize => "n-resize", - CursorIcon::NeResize => "ne-resize", - CursorIcon::NwResize => "nw-resize", - CursorIcon::SResize => "s-resize", - CursorIcon::SeResize => "se-resize", - CursorIcon::SwResize => "sw-resize", - CursorIcon::WResize => "w-resize", - CursorIcon::EwResize => "ew-resize", - CursorIcon::NsResize => "ns-resize", - CursorIcon::NeswResize => "nesw-resize", - CursorIcon::NwseResize => "nwse-resize", - CursorIcon::ColResize => "col-resize", - CursorIcon::RowResize => "row-resize", - CursorIcon::Default => "default", - } - } -} - -impl ResizeDirection { - fn to_cursor_str(&self) -> &str { - match self { - ResizeDirection::East => "e-resize", - ResizeDirection::North => "n-resize", - ResizeDirection::NorthEast => "ne-resize", - ResizeDirection::NorthWest => "nw-resize", - ResizeDirection::South => "s-resize", - ResizeDirection::SouthEast => "se-resize", - ResizeDirection::SouthWest => "sw-resize", - ResizeDirection::West => "w-resize", - } - } - - fn to_gtk_edge(&self) -> WindowEdge { - match self { - ResizeDirection::East => WindowEdge::East, - ResizeDirection::North => WindowEdge::North, - ResizeDirection::NorthEast => WindowEdge::NorthEast, - ResizeDirection::NorthWest => WindowEdge::NorthWest, - ResizeDirection::South => WindowEdge::South, - ResizeDirection::SouthEast => WindowEdge::SouthEast, - ResizeDirection::SouthWest => WindowEdge::SouthWest, - ResizeDirection::West => WindowEdge::West, - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs deleted file mode 100644 index bc1c18df1..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use gtk::gdk_pixbuf::{Colorspace, Pixbuf}; - -use crate::window::BadIcon; - -/// An icon used for the window titlebar, taskbar, etc. -#[derive(Debug, Clone)] -pub struct PlatformIcon { - raw: Vec, - width: i32, - height: i32, - row_stride: i32, -} - -impl From for Pixbuf { - fn from(icon: PlatformIcon) -> Self { - Pixbuf::from_mut_slice( - icon.raw, - Colorspace::Rgb, - true, - 8, - icon.width, - icon.height, - icon.row_stride, - ) - } -} - -impl PlatformIcon { - /// Creates an `Icon` from 32bpp RGBA data. - /// - /// The length of `rgba` must be divisible by 4, and `width * height` must equal - /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - let row_stride = - Pixbuf::calculate_rowstride(Colorspace::Rgb, true, 8, width as i32, height as i32); - Ok(Self { - raw: rgba, - width: width as i32, - height: height as i32, - row_stride, - }) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs deleted file mode 100644 index b9d7f781b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use super::KeyEventExtra; -use crate::{ - event::{ElementState, KeyEvent}, - keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, -}; -use gtk::{ - gdk::{self, keys::constants::*, EventKey}, - glib, -}; -use once_cell::sync::Lazy; -use std::{ - collections::HashSet, - ffi::c_void, - os::raw::{c_int, c_uint}, - ptr, slice, - sync::Mutex, -}; - -pub type RawKey = gdk::keys::Key; - -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); - -fn insert_or_get_key_str(string: String) -> &'static str { - let mut string_set = KEY_STRINGS.lock().unwrap(); - if let Some(contained) = string_set.get(string.as_str()) { - return contained; - } - let static_str = Box::leak(string.into_boxed_str()); - string_set.insert(static_str); - static_str -} - -#[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] -pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { - match gdk_key { - Escape => Some(Key::Escape), - BackSpace => Some(Key::Backspace), - Tab | ISO_Left_Tab => Some(Key::Tab), - Return => Some(Key::Enter), - Control_L | Control_R => Some(Key::Control), - Alt_L | Alt_R => Some(Key::Alt), - Shift_L | Shift_R => Some(Key::Shift), - // TODO: investigate mapping. Map Meta_[LR]? - Super_L | Super_R => Some(Key::Super), - Caps_Lock => Some(Key::CapsLock), - F1 => Some(Key::F1), - F2 => Some(Key::F2), - F3 => Some(Key::F3), - F4 => Some(Key::F4), - F5 => Some(Key::F5), - F6 => Some(Key::F6), - F7 => Some(Key::F7), - F8 => Some(Key::F8), - F9 => Some(Key::F9), - F10 => Some(Key::F10), - F11 => Some(Key::F11), - F12 => Some(Key::F12), - F13 => Some(Key::F13), - F14 => Some(Key::F14), - F15 => Some(Key::F15), - F16 => Some(Key::F16), - F17 => Some(Key::F17), - F18 => Some(Key::F18), - F19 => Some(Key::F19), - F20 => Some(Key::F20), - F21 => Some(Key::F21), - F22 => Some(Key::F22), - F23 => Some(Key::F23), - F24 => Some(Key::F24), - - Print => Some(Key::PrintScreen), - Scroll_Lock => Some(Key::ScrollLock), - // Pause/Break not audio. - Pause => Some(Key::Pause), - - Insert => Some(Key::Insert), - Delete => Some(Key::Delete), - Home => Some(Key::Home), - End => Some(Key::End), - Page_Up => Some(Key::PageUp), - Page_Down => Some(Key::PageDown), - Num_Lock => Some(Key::NumLock), - - Up => Some(Key::ArrowUp), - Down => Some(Key::ArrowDown), - Left => Some(Key::ArrowLeft), - Right => Some(Key::ArrowRight), - Clear => Some(Key::Clear), - - Menu => Some(Key::ContextMenu), - WakeUp => Some(Key::WakeUp), - Launch0 => Some(Key::LaunchApplication1), - Launch1 => Some(Key::LaunchApplication2), - ISO_Level3_Shift => Some(Key::AltGraph), - - KP_Begin => Some(Key::Clear), - KP_Delete => Some(Key::Delete), - KP_Down => Some(Key::ArrowDown), - KP_End => Some(Key::End), - KP_Enter => Some(Key::Enter), - KP_F1 => Some(Key::F1), - KP_F2 => Some(Key::F2), - KP_F3 => Some(Key::F3), - KP_F4 => Some(Key::F4), - KP_Home => Some(Key::Home), - KP_Insert => Some(Key::Insert), - KP_Left => Some(Key::ArrowLeft), - KP_Page_Down => Some(Key::PageDown), - KP_Page_Up => Some(Key::PageUp), - KP_Right => Some(Key::ArrowRight), - // KP_Separator? What does it map to? - KP_Tab => Some(Key::Tab), - KP_Up => Some(Key::ArrowUp), - // TODO: more mappings (media etc) - _ => None, - } -} - -#[allow(clippy::just_underscores_and_digits, non_upper_case_globals)] -pub(crate) fn raw_key_to_location(raw: RawKey) -> KeyLocation { - match raw { - Control_L | Shift_L | Alt_L | Super_L | Meta_L => KeyLocation::Left, - Control_R | Shift_R | Alt_R | Super_R | Meta_R => KeyLocation::Right, - KP_0 | KP_1 | KP_2 | KP_3 | KP_4 | KP_5 | KP_6 | KP_7 | KP_8 | KP_9 | KP_Add | KP_Begin - | KP_Decimal | KP_Delete | KP_Divide | KP_Down | KP_End | KP_Enter | KP_Equal | KP_F1 - | KP_F2 | KP_F3 | KP_F4 | KP_Home | KP_Insert | KP_Left | KP_Multiply | KP_Page_Down - | KP_Page_Up | KP_Right | KP_Separator | KP_Space | KP_Subtract | KP_Tab | KP_Up => { - KeyLocation::Numpad - } - _ => KeyLocation::Standard, - } -} - -const MODIFIER_MAP: &[(Key<'static>, ModifiersState)] = &[ - (Key::Shift, ModifiersState::SHIFT), - (Key::Alt, ModifiersState::ALT), - (Key::Control, ModifiersState::CONTROL), - (Key::Super, ModifiersState::SUPER), -]; - -// we use the EventKey to extract the modifier mainly because -// we need to have the modifier before the second key is entered to follow -// other os' logic -- this way we can emit the new `ModifiersState` before -// we receive the next key, if needed the developer can update his local state. -pub(crate) fn get_modifiers(key: EventKey) -> ModifiersState { - // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. - let scancode = key.hardware_keycode(); - // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. - let keyval = key.keyval(); - // unicode value - let unicode = keyval.to_unicode(); - // translate to tao::keyboard::Key - let key_from_code = raw_key_to_key(keyval).unwrap_or_else(|| { - if let Some(key) = unicode { - if key >= ' ' && key != '\x7f' { - Key::Character(insert_or_get_key_str(key.to_string())) - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - }); - // start with empty state - let mut result = ModifiersState::empty(); - // loop trough our modifier map - for (gdk_mod, modifier) in MODIFIER_MAP { - if key_from_code == *gdk_mod { - result |= *modifier; - } - } - result -} - -pub(crate) fn make_key_event( - key: &EventKey, - is_repeat: bool, - key_override: Option, - state: ElementState, -) -> Option { - // a keycode (scancode in Windows) is a code that refers to a physical keyboard key. - let scancode = key.hardware_keycode(); - // a keyval (keysym in X) is a "logical" key name, such as GDK_Enter, GDK_a, GDK_space, etc. - let keyval_without_modifiers = key.keyval(); - let keyval_with_modifiers = - hardware_keycode_to_keyval(scancode).unwrap_or(keyval_without_modifiers); - // get unicode value, with and without modifiers - let text_without_modifiers = keyval_with_modifiers.to_unicode(); - let text_with_modifiers = keyval_without_modifiers.to_unicode(); - // get physical key from the scancode (keycode) - let physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); - - // extract key without modifier - let key_without_modifiers = raw_key_to_key(keyval_with_modifiers).unwrap_or_else(|| { - if let Some(key) = text_without_modifiers { - if key >= ' ' && key != '\x7f' { - Key::Character(insert_or_get_key_str(key.to_string())) - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - }); - - // extract the logical key - let logical_key = raw_key_to_key(keyval_without_modifiers).unwrap_or_else(|| { - if let Some(key) = text_with_modifiers { - if key >= ' ' && key != '\x7f' { - Key::Character(insert_or_get_key_str(key.to_string())) - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - } else { - Key::Unidentified(NativeKeyCode::Gtk(scancode)) - } - }); - - // make sure we have a valid key - if !matches!(key_without_modifiers, Key::Unidentified(_)) { - let location = raw_key_to_location(keyval_with_modifiers); - let text_with_all_modifiers = - text_without_modifiers.map(|text| insert_or_get_key_str(text.to_string())); - return Some(KeyEvent { - location, - logical_key, - physical_key, - repeat: is_repeat, - state, - text: text_with_all_modifiers, - platform_specific: KeyEventExtra { - text_with_all_modifiers, - key_without_modifiers, - }, - }); - } else { - #[cfg(debug_assertions)] - eprintln!("Couldn't get key from code: {physical_key:?}"); - } - None -} - -/// Map a hardware keycode to a keyval by performing a lookup in the keymap and finding the -/// keyval with the lowest group and level -fn hardware_keycode_to_keyval(keycode: u16) -> Option { - use glib::translate::FromGlib; - unsafe { - let keymap = gdk::ffi::gdk_keymap_get_default(); - - let mut nkeys = 0; - let mut keys: *mut gdk::ffi::GdkKeymapKey = ptr::null_mut(); - let mut keyvals: *mut c_uint = ptr::null_mut(); - - // call into gdk to retrieve the keyvals and keymap keys - gdk::ffi::gdk_keymap_get_entries_for_keycode( - keymap, - c_uint::from(keycode), - &mut keys as *mut *mut gdk::ffi::GdkKeymapKey, - &mut keyvals as *mut *mut c_uint, - &mut nkeys as *mut c_int, - ); - - if nkeys > 0 { - let keyvals_slice = slice::from_raw_parts(keyvals, nkeys as usize); - let keys_slice = slice::from_raw_parts(keys, nkeys as usize); - - let resolved_keyval = keys_slice.iter().enumerate().find_map(|(id, gdk_keymap)| { - if gdk_keymap.group == 0 && gdk_keymap.level == 0 { - Some(RawKey::from_glib(keyvals_slice[id])) - } else { - None - } - }); - - // notify glib to free the allocated arrays - glib::ffi::g_free(keyvals as *mut c_void); - glib::ffi::g_free(keys as *mut c_void); - - return resolved_keyval; - } - } - None -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs deleted file mode 100644 index e63e6c951..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::keyboard::{KeyCode, NativeKeyCode}; - -pub fn keycode_to_scancode(code: KeyCode) -> Option { - // See `from_scancode` for more info - match code { - KeyCode::Backquote => Some(0x0031), - KeyCode::Backslash => Some(0x0033), - KeyCode::Backspace => Some(0x0016), - KeyCode::BracketLeft => Some(0x0022), - KeyCode::BracketRight => Some(0x001B), - KeyCode::Comma => Some(0x003B), - KeyCode::Digit0 => Some(0x0013), - KeyCode::Digit1 => Some(0x000A), - KeyCode::Digit2 => Some(0x000B), - KeyCode::Digit3 => Some(0x000C), - KeyCode::Digit4 => Some(0x000D), - KeyCode::Digit5 => Some(0x000E), - KeyCode::Digit6 => Some(0x000F), - KeyCode::Digit7 => Some(0x0010), - KeyCode::Digit8 => Some(0x0011), - KeyCode::Digit9 => Some(0x0012), - KeyCode::Equal => Some(0x0015), - KeyCode::IntlBackslash => Some(0x005E), - KeyCode::IntlRo => Some(0x0061), - KeyCode::IntlYen => Some(0x0084), - KeyCode::KeyA => Some(0x0026), - KeyCode::KeyB => Some(0x0038), - KeyCode::KeyC => Some(0x0036), - KeyCode::KeyD => Some(0x0028), - KeyCode::KeyE => Some(0x001A), - KeyCode::KeyF => Some(0x0029), - KeyCode::KeyG => Some(0x002A), - KeyCode::KeyH => Some(0x002B), - KeyCode::KeyI => Some(0x001F), - KeyCode::KeyJ => Some(0x002C), - KeyCode::KeyK => Some(0x002D), - KeyCode::KeyL => Some(0x002E), - KeyCode::KeyM => Some(0x002E), - KeyCode::KeyN => Some(0x0039), - KeyCode::KeyO => Some(0x0020), - KeyCode::KeyP => Some(0x0021), - KeyCode::KeyQ => Some(0x0018), - KeyCode::KeyR => Some(0x001B), - KeyCode::KeyS => Some(0x0027), - KeyCode::KeyT => Some(0x001C), - KeyCode::KeyU => Some(0x001E), - KeyCode::KeyV => Some(0x0037), - KeyCode::KeyW => Some(0x0019), - KeyCode::KeyX => Some(0x0035), - KeyCode::KeyY => Some(0x001D), - KeyCode::KeyZ => Some(0x0034), - KeyCode::Minus => Some(0x0014), - KeyCode::Period => Some(0x003C), - KeyCode::Quote => Some(0x0030), - KeyCode::Semicolon => Some(0x002F), - KeyCode::Slash => Some(0x003D), - KeyCode::AltLeft => Some(0x0040), - KeyCode::AltRight => Some(0x006C), - KeyCode::CapsLock => Some(0x0042), - KeyCode::ContextMenu => Some(0x0087), - KeyCode::ControlLeft => Some(0x0025), - KeyCode::ControlRight => Some(0x0069), - KeyCode::Enter => Some(0x0024), - KeyCode::SuperLeft => Some(0x0085), - KeyCode::SuperRight => Some(0x0086), - KeyCode::ShiftLeft => Some(0x0032), - KeyCode::ShiftRight => Some(0x003E), - KeyCode::Space => Some(0x0041), - KeyCode::Tab => Some(0x0017), - KeyCode::Convert => Some(0x0064), - KeyCode::Lang1 => Some(0x0082), - KeyCode::Lang2 => Some(0x0083), - KeyCode::KanaMode => Some(0x0065), - KeyCode::NonConvert => Some(0x0066), - KeyCode::Delete => Some(0x0077), - KeyCode::End => Some(0x0073), - KeyCode::Home => Some(0x006E), - KeyCode::Insert => Some(0x0076), - KeyCode::PageDown => Some(0x0075), - KeyCode::PageUp => Some(0x0070), - KeyCode::ArrowDown => Some(0x0074), - KeyCode::ArrowLeft => Some(0x0071), - KeyCode::ArrowRight => Some(0x0072), - KeyCode::ArrowUp => Some(0x006F), - KeyCode::NumLock => Some(0x004D), - KeyCode::Numpad0 => Some(0x005A), - KeyCode::Numpad1 => Some(0x0057), - KeyCode::Numpad2 => Some(0x0058), - KeyCode::Numpad3 => Some(0x0059), - KeyCode::Numpad4 => Some(0x0058), - KeyCode::Numpad5 => Some(0x0053), - KeyCode::Numpad6 => Some(0x0054), - KeyCode::Numpad7 => Some(0x0055), - KeyCode::Numpad8 => Some(0x0050), - KeyCode::Numpad9 => Some(0x0051), - KeyCode::NumpadAdd => Some(0x0056), - KeyCode::NumpadComma => Some(0x0081), - KeyCode::NumpadDecimal => Some(0x005B), - KeyCode::NumpadDivide => Some(0x006A), - KeyCode::NumpadEnter => Some(0x0068), - KeyCode::NumpadEqual => Some(0x007D), - KeyCode::NumpadMultiply => Some(0x003F), - KeyCode::NumpadSubtract => Some(0x0052), - KeyCode::Escape => Some(0x0009), - KeyCode::F1 => Some(0x0043), - KeyCode::F2 => Some(0x0044), - KeyCode::F3 => Some(0x0045), - KeyCode::F4 => Some(0x0046), - KeyCode::F5 => Some(0x0047), - KeyCode::F6 => Some(0x0048), - KeyCode::F7 => Some(0x0049), - KeyCode::F8 => Some(0x004A), - KeyCode::F9 => Some(0x004B), - KeyCode::F10 => Some(0x004C), - KeyCode::F11 => Some(0x005F), - KeyCode::F12 => Some(0x0060), - KeyCode::F13 => Some(0x00BF), - KeyCode::F14 => Some(0x00C0), - KeyCode::F15 => Some(0x00C1), - KeyCode::F16 => Some(0x00C2), - KeyCode::F17 => Some(0x00C3), - KeyCode::F18 => Some(0x00C4), - KeyCode::F19 => Some(0x00C5), - KeyCode::F20 => Some(0x00C6), - KeyCode::F21 => Some(0x00C7), - KeyCode::F22 => Some(0x00C8), - KeyCode::F23 => Some(0x00C9), - KeyCode::F24 => Some(0x00CA), - KeyCode::PrintScreen => Some(0x006B), - KeyCode::ScrollLock => Some(0x004E), - KeyCode::Pause => Some(0x007F), - KeyCode::BrowserBack => Some(0x00A6), - KeyCode::BrowserFavorites => Some(0x00A4), - KeyCode::BrowserForward => Some(0x00A7), - KeyCode::BrowserHome => Some(0x00B4), - KeyCode::BrowserRefresh => Some(0x00B5), - KeyCode::BrowserSearch => Some(0x00E1), - KeyCode::BrowserStop => Some(0x0088), - - KeyCode::LaunchApp1 => Some(0x0098), - KeyCode::LaunchApp2 => Some(0x0094), - KeyCode::LaunchMail => Some(0x00A3), - KeyCode::MediaPlayPause => Some(0x00AC), - KeyCode::MediaSelect => Some(0x00B3), - KeyCode::MediaStop => Some(0x00AE), - KeyCode::MediaTrackNext => Some(0x00AB), - KeyCode::MediaTrackPrevious => Some(0x00AD), - KeyCode::AudioVolumeDown => Some(0x007A), - KeyCode::AudioVolumeMute => Some(0x0079), - KeyCode::AudioVolumeUp => Some(0x007B), - KeyCode::Unidentified(NativeKeyCode::Gtk(scancode)) => Some(scancode as u32), - _ => None, - } -} - -pub fn keycode_from_scancode(scancode: u32) -> KeyCode { - // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html - // and: https://www.w3.org/TR/uievents-code/ - // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source - match scancode { - 0x0009 => KeyCode::Escape, - 0x000A => KeyCode::Digit1, - 0x000B => KeyCode::Digit2, - 0x000C => KeyCode::Digit3, - 0x000D => KeyCode::Digit4, - 0x000E => KeyCode::Digit5, - 0x000F => KeyCode::Digit6, - 0x0010 => KeyCode::Digit7, - 0x0011 => KeyCode::Digit8, - 0x0012 => KeyCode::Digit9, - 0x0013 => KeyCode::Digit0, - 0x0014 => KeyCode::Minus, - 0x0015 => KeyCode::Equal, - 0x0016 => KeyCode::Backspace, - 0x0017 => KeyCode::Tab, - 0x0018 => KeyCode::KeyQ, - 0x0019 => KeyCode::KeyW, - 0x001A => KeyCode::KeyE, - 0x001B => KeyCode::KeyR, - 0x001C => KeyCode::KeyT, - 0x001D => KeyCode::KeyY, - 0x001E => KeyCode::KeyU, - 0x001F => KeyCode::KeyI, - 0x0020 => KeyCode::KeyO, - 0x0021 => KeyCode::KeyP, - 0x0022 => KeyCode::BracketLeft, - 0x0023 => KeyCode::BracketRight, - 0x0024 => KeyCode::Enter, - 0x0025 => KeyCode::ControlLeft, - 0x0026 => KeyCode::KeyA, - 0x0027 => KeyCode::KeyS, - 0x0028 => KeyCode::KeyD, - 0x0029 => KeyCode::KeyF, - 0x002A => KeyCode::KeyG, - 0x002B => KeyCode::KeyH, - 0x002C => KeyCode::KeyJ, - 0x002D => KeyCode::KeyK, - 0x002E => KeyCode::KeyL, - 0x002F => KeyCode::Semicolon, - 0x0030 => KeyCode::Quote, - 0x0031 => KeyCode::Backquote, - 0x0032 => KeyCode::ShiftLeft, - 0x0033 => KeyCode::Backslash, - 0x0034 => KeyCode::KeyZ, - 0x0035 => KeyCode::KeyX, - 0x0036 => KeyCode::KeyC, - 0x0037 => KeyCode::KeyV, - 0x0038 => KeyCode::KeyB, - 0x0039 => KeyCode::KeyN, - 0x003A => KeyCode::KeyM, - 0x003B => KeyCode::Comma, - 0x003C => KeyCode::Period, - 0x003D => KeyCode::Slash, - 0x003E => KeyCode::ShiftRight, - 0x003F => KeyCode::NumpadMultiply, - 0x0040 => KeyCode::AltLeft, - 0x0041 => KeyCode::Space, - 0x0042 => KeyCode::CapsLock, - 0x0043 => KeyCode::F1, - 0x0044 => KeyCode::F2, - 0x0045 => KeyCode::F3, - 0x0046 => KeyCode::F4, - 0x0047 => KeyCode::F5, - 0x0048 => KeyCode::F6, - 0x0049 => KeyCode::F7, - 0x004A => KeyCode::F8, - 0x004B => KeyCode::F9, - 0x004C => KeyCode::F10, - 0x004D => KeyCode::NumLock, - 0x004E => KeyCode::ScrollLock, - 0x004F => KeyCode::Numpad7, - 0x0050 => KeyCode::Numpad8, - 0x0051 => KeyCode::Numpad9, - 0x0052 => KeyCode::NumpadSubtract, - 0x0053 => KeyCode::Numpad4, - 0x0054 => KeyCode::Numpad5, - 0x0055 => KeyCode::Numpad6, - 0x0056 => KeyCode::NumpadAdd, - 0x0057 => KeyCode::Numpad1, - 0x0058 => KeyCode::Numpad2, - 0x0059 => KeyCode::Numpad3, - 0x005A => KeyCode::Numpad0, - 0x005B => KeyCode::NumpadDecimal, - 0x005E => KeyCode::IntlBackslash, - 0x005F => KeyCode::F11, - 0x0060 => KeyCode::F12, - 0x0061 => KeyCode::IntlRo, - 0x0064 => KeyCode::Convert, - 0x0065 => KeyCode::KanaMode, - 0x0066 => KeyCode::NonConvert, - 0x0068 => KeyCode::NumpadEnter, - 0x0069 => KeyCode::ControlRight, - 0x006A => KeyCode::NumpadDivide, - 0x006B => KeyCode::PrintScreen, - 0x006C => KeyCode::AltRight, - 0x006E => KeyCode::Home, - 0x006F => KeyCode::ArrowUp, - 0x0070 => KeyCode::PageUp, - 0x0071 => KeyCode::ArrowLeft, - 0x0072 => KeyCode::ArrowRight, - 0x0073 => KeyCode::End, - 0x0074 => KeyCode::ArrowDown, - 0x0075 => KeyCode::PageDown, - 0x0076 => KeyCode::Insert, - 0x0077 => KeyCode::Delete, - 0x0079 => KeyCode::AudioVolumeMute, - 0x007A => KeyCode::AudioVolumeDown, - 0x007B => KeyCode::AudioVolumeUp, - 0x007D => KeyCode::NumpadEqual, - 0x007F => KeyCode::Pause, - 0x0081 => KeyCode::NumpadComma, - 0x0082 => KeyCode::Lang1, - 0x0083 => KeyCode::Lang2, - 0x0084 => KeyCode::IntlYen, - 0x0085 => KeyCode::SuperLeft, - 0x0086 => KeyCode::SuperRight, - 0x0087 => KeyCode::ContextMenu, - 0x0088 => KeyCode::BrowserStop, - 0x0089 => KeyCode::Again, - 0x008A => KeyCode::Props, - 0x008B => KeyCode::Undo, - 0x008C => KeyCode::Select, - 0x008D => KeyCode::Copy, - 0x008E => KeyCode::Open, - 0x008F => KeyCode::Paste, - 0x0090 => KeyCode::Find, - 0x0091 => KeyCode::Cut, - 0x0092 => KeyCode::Help, - 0x0094 => KeyCode::LaunchApp2, - 0x0097 => KeyCode::WakeUp, - 0x0098 => KeyCode::LaunchApp1, - // key to right of volume controls on T430s produces 0x9C - // but no documentation of what it should map to :/ - 0x00A3 => KeyCode::LaunchMail, - 0x00A4 => KeyCode::BrowserFavorites, - 0x00A6 => KeyCode::BrowserBack, - 0x00A7 => KeyCode::BrowserForward, - 0x00A9 => KeyCode::Eject, - 0x00AB => KeyCode::MediaTrackNext, - 0x00AC => KeyCode::MediaPlayPause, - 0x00AD => KeyCode::MediaTrackPrevious, - 0x00AE => KeyCode::MediaStop, - 0x00B3 => KeyCode::MediaSelect, - 0x00B4 => KeyCode::BrowserHome, - 0x00B5 => KeyCode::BrowserRefresh, - 0x00BF => KeyCode::F13, - 0x00C0 => KeyCode::F14, - 0x00C1 => KeyCode::F15, - 0x00C2 => KeyCode::F16, - 0x00C3 => KeyCode::F17, - 0x00C4 => KeyCode::F18, - 0x00C5 => KeyCode::F19, - 0x00C6 => KeyCode::F20, - 0x00C7 => KeyCode::F21, - 0x00C8 => KeyCode::F22, - 0x00C9 => KeyCode::F23, - 0x00CA => KeyCode::F24, - 0x00E1 => KeyCode::BrowserSearch, - _ => KeyCode::Unidentified(NativeKeyCode::Gtk(scancode as u16)), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs deleted file mode 100644 index 0473f67e5..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "x11")] -mod device; -mod event_loop; -mod icon; -mod keyboard; -mod keycode; -mod monitor; -#[cfg(feature = "dbus")] -mod portal; -mod util; -mod window; - -pub mod taskbar; -pub mod wayland; -#[cfg(feature = "x11")] -pub mod x11; - -pub use self::keycode::{keycode_from_scancode, keycode_to_scancode}; -pub(crate) use event_loop::PlatformSpecificEventLoopAttributes; -pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; -pub use icon::PlatformIcon; -pub use monitor::{MonitorHandle, VideoMode}; -pub use window::{Window, WindowId}; - -use crate::{event::DeviceId as RootDeviceId, keyboard::Key}; - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct KeyEventExtra { - pub text_with_all_modifiers: Option<&'static str>, - pub key_without_modifiers: Key<'static>, -} - -#[non_exhaustive] -#[derive(Clone, Default)] -pub enum Parent { - #[default] - None, - ChildOf(gtk::Window), -} - -#[derive(Clone)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub parent: Parent, - pub skip_taskbar: bool, - pub auto_transparent: bool, - pub double_buffered: bool, - pub app_paintable: bool, - pub rgba_visual: bool, - pub cursor_moved: bool, - pub default_vbox: bool, -} - -impl Default for PlatformSpecificWindowBuilderAttributes { - fn default() -> Self { - Self { - parent: Default::default(), - skip_taskbar: Default::default(), - auto_transparent: true, - double_buffered: true, - app_paintable: false, - rgba_visual: false, - cursor_moved: true, - default_vbox: true, - } - } -} - -unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} -unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} - -#[derive(Debug, Clone)] -pub struct OsError; - -impl std::fmt::Display for OsError { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - Ok(()) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(usize); - -impl DeviceId { - pub unsafe fn dummy() -> Self { - Self(0) - } -} - -// FIXME: currently we use a dummy device id, find if we can get device id from gtk -pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs deleted file mode 100644 index ceeea1265..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use gtk::gdk::{self, prelude::MonitorExt, Display}; - -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct MonitorHandle { - pub(crate) monitor: gdk::Monitor, -} - -impl MonitorHandle { - pub fn new(display: &gdk::Display, number: i32) -> Self { - let monitor = display.monitor(number).unwrap(); - Self { monitor } - } - - #[inline] - pub fn name(&self) -> Option { - self.monitor.model().map(|s| s.as_str().to_string()) - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - let rect = self.monitor.geometry(); - LogicalSize { - width: rect.width() as u32, - height: rect.height() as u32, - } - .to_physical(self.scale_factor()) - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - let rect = self.monitor.geometry(); - LogicalPosition { - x: rect.x(), - y: rect.y(), - } - .to_physical(self.scale_factor()) - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - self.monitor.scale_factor() as f64 - } - - #[inline] - pub fn video_modes(&self) -> Box> { - Box::new(Vec::new().into_iter()) - } -} - -unsafe impl Send for MonitorHandle {} -unsafe impl Sync for MonitorHandle {} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct VideoMode; - -impl VideoMode { - #[inline] - pub fn size(&self) -> PhysicalSize { - panic!("VideoMode is unsupported on Linux.") - } - - #[inline] - pub fn bit_depth(&self) -> u16 { - panic!("VideoMode is unsupported on Linux.") - } - - #[inline] - pub fn refresh_rate(&self) -> u16 { - panic!("VideoMode is unsupported on Linux.") - } - - #[inline] - pub fn monitor(&self) -> RootMonitorHandle { - panic!("VideoMode is unsupported on Linux.") - } -} - -pub fn from_point(display: &Display, x: f64, y: f64) -> Option { - if let Some(monitor) = display.monitor_at_point(x as i32, y as i32) { - (0..display.n_monitors()) - .map(|i| (i, display.monitor(i).unwrap())) - .find(|cur| cur.1.geometry() == monitor.geometry()) - .map(|x| MonitorHandle::new(display, x.0)) - } else { - None - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs deleted file mode 100644 index c0df6a43d..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs +++ /dev/null @@ -1,78 +0,0 @@ -#[cfg(feature = "dbus")] -use dbus::{ - arg::Variant, - blocking::{Connection, SyncConnection}, - message::MatchRule, - Error, -}; -use gtk::glib::{ControlFlow, MainContext, Priority, Sender}; -use log::warn; -use std::{thread, time::Duration}; - -use crate::{ - platform_impl::{platform::window::WindowRequest, WindowId}, - window::Theme, -}; - -pub fn theme() -> Result { - let conn = Connection::new_session()?; - let proxy = conn.with_proxy( - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - Duration::from_secs(5), - ); - - let result: (Variant>,) = proxy.method_call( - "org.freedesktop.portal.Settings", - "Read", - ("org.freedesktop.appearance", "color-scheme"), - )?; - - Ok(color_scheme_to_theme(result.0 .0 .0)) -} - -pub fn receive_theme_changed(window_tx: Sender<(WindowId, WindowRequest)>) -> Result<(), Error> { - let conn = SyncConnection::new_session()?; - let match_rule = MatchRule::new_signal("org.freedesktop.portal.Settings", "SettingChanged"); - let (tx, rx) = MainContext::channel(Priority::DEFAULT); - - conn.add_match(match_rule, move |_: (), _, msg| { - let mut iter = msg.iter_init(); - if let (Ok("org.freedesktop.appearance"), Ok("color-scheme"), Ok(value)) = ( - iter.read::<&str>(), // Namespace - iter.read::<&str>(), // Key - iter.read::>(), // Value - ) { - if let Err(e) = tx.send(color_scheme_to_theme(value.0)) { - warn!("Failed to send theme change via channel: {}", e); - } - } - true - })?; - - rx.attach(None, move |theme| { - if let Err(e) = window_tx.send((WindowId::dummy(), WindowRequest::SetTheme(Some(theme)))) { - warn!("Failed to send theme change request: {}", e); - ControlFlow::Break - } else { - ControlFlow::Continue - } - }); - - thread::spawn(move || loop { - if let Err(e) = conn.process(Duration::from_secs(5)) { - warn!("D-Bus message processing error: {}", e); - break; - } - }); - - Ok(()) -} - -fn color_scheme_to_theme(color_scheme: u32) -> Theme { - match color_scheme { - 1 => Theme::Dark, // Prefer Dark - 2 => Theme::Light, // Prefer Light - _ => Theme::Light, // No Preference, default to Light - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs deleted file mode 100644 index ae7b98584..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::ffi::{c_char, CString}; - -use dlopen2::wrapper::{Container, WrapperApi}; - -use crate::window::{ProgressBarState, ProgressState}; - -#[derive(WrapperApi)] -struct UnityLib { - unity_launcher_entry_get_for_desktop_id: unsafe extern "C" fn(id: *const c_char) -> *const isize, - unity_inspector_get_default: unsafe extern "C" fn() -> *const isize, - unity_inspector_get_unity_running: unsafe extern "C" fn(inspector: *const isize) -> i32, - unity_launcher_entry_set_progress: unsafe extern "C" fn(entry: *const isize, value: f64) -> i32, - unity_launcher_entry_set_progress_visible: - unsafe extern "C" fn(entry: *const isize, value: i32) -> i32, - unity_launcher_entry_set_count: unsafe extern "C" fn(entry: *const isize, value: i64) -> i32, - unity_launcher_entry_set_count_visible: - unsafe extern "C" fn(entry: *const isize, value: bool) -> bool, -} - -pub struct TaskbarIndicator { - desktop_filename: Option, - desktop_filename_c_str: Option, - - unity_lib: Option>, - attempted_load: bool, - - unity_inspector: Option<*const isize>, - unity_entry: Option<*const isize>, -} - -impl TaskbarIndicator { - pub fn new() -> Self { - Self { - desktop_filename: None, - desktop_filename_c_str: None, - - unity_lib: None, - attempted_load: false, - - unity_inspector: None, - unity_entry: None, - } - } - - fn ensure_lib_load(&mut self) { - if self.attempted_load { - return; - } - - self.attempted_load = true; - - self.unity_lib = unsafe { - Container::load("libunity.so.4") - .or_else(|_| Container::load("libunity.so.6")) - .or_else(|_| Container::load("libunity.so.9")) - .ok() - }; - - if let Some(unity_lib) = &self.unity_lib { - let handle = unsafe { unity_lib.unity_inspector_get_default() }; - if !handle.is_null() { - self.unity_inspector = Some(handle); - } - } - } - - fn ensure_entry_load(&mut self) { - if let Some(unity_lib) = &self.unity_lib { - if let Some(id) = &self.desktop_filename_c_str { - let handle = unsafe { unity_lib.unity_launcher_entry_get_for_desktop_id(id.as_ptr()) }; - if !handle.is_null() { - self.unity_entry = Some(handle); - } - } - } - } - - fn is_unity_running(&self) -> bool { - if let Some(inspector) = self.unity_inspector { - if let Some(unity_lib) = &self.unity_lib { - return unsafe { unity_lib.unity_inspector_get_unity_running(inspector) } == 1; - } - } - - false - } - - pub fn update(&mut self, progress: ProgressBarState) { - if let Some(uri) = progress.desktop_filename { - self.desktop_filename = Some(uri); - } - - self.ensure_lib_load(); - - if !self.is_unity_running() { - return; - } - - if let Some(uri) = &self.desktop_filename { - self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); - } - - if self.unity_entry.is_none() { - self.ensure_entry_load(); - } - if let Some(unity_lib) = &self.unity_lib { - if let Some(unity_entry) = &self.unity_entry { - if let Some(progress) = progress.progress { - let progress = if progress > 100 { 100 } else { progress }; - let progress = progress as f64 / 100.0; - unsafe { (unity_lib.unity_launcher_entry_set_progress)(*unity_entry, progress) }; - } - - if let Some(state) = progress.state { - let is_visible = !matches!(state, ProgressState::None); - unsafe { - (unity_lib.unity_launcher_entry_set_progress_visible)( - *unity_entry, - if is_visible { 1 } else { 0 }, - ) - }; - } - } - } - } - - pub fn update_count(&mut self, count: Option, desktop_filename: Option) { - if let Some(uri) = desktop_filename { - self.desktop_filename = Some(uri); - } - - self.ensure_lib_load(); - - if !self.is_unity_running() { - return; - } - - if let Some(uri) = &self.desktop_filename { - self.desktop_filename_c_str = Some(CString::new(uri.as_str()).unwrap_or_default()); - } - - if self.unity_entry.is_none() { - self.ensure_entry_load(); - } - - if let Some(unity_lib) = &self.unity_lib { - if let Some(unity_entry) = &self.unity_entry { - // Sets count - if let Some(count) = count { - unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, count) }; - unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, true) }; - } - // removes the count - else { - unsafe { (unity_lib.unity_launcher_entry_set_count)(*unity_entry, 0) }; - unsafe { (unity_lib.unity_launcher_entry_set_count_visible)(*unity_entry, false) }; - } - } - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs deleted file mode 100644 index 30cd8523b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, - error::ExternalError, - window::WindowSizeConstraints, -}; -use gtk::{ - gdk::{ - self, - prelude::{DeviceExt, SeatExt}, - Display, - }, - glib::{self}, - traits::{GtkWindowExt, WidgetExt}, -}; -use std::{cell::RefCell, rc::Rc}; - -#[inline] -pub fn cursor_position(is_wayland: bool) -> Result, ExternalError> { - if is_wayland { - Ok((0, 0).into()) - } else { - Display::default() - .map(|d| { - ( - d.default_seat().and_then(|s| s.pointer()), - d.default_group(), - ) - }) - .map(|(p, g)| { - p.map(|p| { - let (_, x, y) = p.position_double(); - LogicalPosition::new(x, y).to_physical(g.scale_factor() as _) - }) - }) - .map(|p| p.ok_or(ExternalError::Os(os_error!(super::OsError)))) - .ok_or(ExternalError::Os(os_error!(super::OsError)))? - } -} - -pub fn set_size_constraints( - window: &W, - constraints: WindowSizeConstraints, -) { - let mut geom_mask = gdk::WindowHints::empty(); - if constraints.has_min() { - geom_mask |= gdk::WindowHints::MIN_SIZE; - } - if constraints.has_max() { - geom_mask |= gdk::WindowHints::MAX_SIZE; - } - - let scale_factor = window.scale_factor() as f64; - - let min_size: LogicalSize = constraints.min_size_logical(scale_factor); - let max_size: LogicalSize = constraints.max_size_logical(scale_factor); - - let picky_none: Option<>k::Window> = None; - window.set_geometry_hints( - picky_none, - Some(&gdk::Geometry::new( - min_size.width, - min_size.height, - max_size.width, - max_size.height, - 0, - 0, - 0, - 0, - 0f64, - 0f64, - gdk::Gravity::Center, - )), - geom_mask, - ) -} - -pub struct WindowMaximizeProcess { - window: W, - resizable: bool, - step: u8, -} - -impl WindowMaximizeProcess { - pub fn new(window: W, resizable: bool) -> Rc> { - Rc::new(RefCell::new(Self { - window, - resizable, - step: 0, - })) - } - - pub fn next_step(&mut self) -> glib::ControlFlow { - match self.step { - 0 => { - self.window.set_resizable(true); - self.step += 1; - glib::ControlFlow::Continue - } - 1 => { - self.window.maximize(); - self.step += 1; - glib::ControlFlow::Continue - } - 2 => { - self.window.set_resizable(self.resizable); - glib::ControlFlow::Break - } - _ => glib::ControlFlow::Break, - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs deleted file mode 100644 index 480eb4c85..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; - -pub struct WlHeader; - -impl WlHeader { - pub fn setup(window: &ApplicationWindow, title: &str) { - let header = HeaderBar::builder() - .show_close_button(true) - .decoration_layout("menu:minimize,maximize,close") - .title(title) - .build(); - - let event_box = EventBox::new(); - event_box.set_above_child(true); - event_box.set_visible(true); - event_box.set_can_focus(false); - event_box.add(&header); - - window.set_titlebar(Some(&event_box)); - Self::connect_resize_window(&header, window); - } - - fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { - let header_weak = header.downgrade(); - window.connect_resizable_notify(move |window| { - if let Some(header) = header_weak.upgrade() { - let is_resizable = window.is_resizable(); - header.set_decoration_layout(if !is_resizable { - Some("menu:minimize,close") - } else { - Some("menu:minimize,maximize,close") - }); - } - }); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs deleted file mode 100644 index a200d2603..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub mod header; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs deleted file mode 100644 index fca4c308c..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs +++ /dev/null @@ -1,1082 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - cell::RefCell, - collections::VecDeque, - rc::Rc, - sync::atomic::{AtomicBool, AtomicI32, Ordering}, -}; - -use gtk::{ - gdk::WindowState, - glib::{self, translate::ToGlibPtr}, - prelude::*, - CssProvider, Settings, -}; - -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::wayland::header::WlHeader, - window::{ - CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, - WindowAttributes, WindowSizeConstraints, RGBA, - }, -}; - -use super::{ - event_loop::EventLoopWindowTarget, - monitor::{self, MonitorHandle}, - util, Parent, PlatformSpecificWindowBuilderAttributes, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(pub(crate) u32); - -impl WindowId { - pub fn dummy() -> Self { - WindowId(u32::MAX) - } -} - -pub struct Window { - /// Window id. - pub(crate) window_id: WindowId, - /// Gtk application window. - pub(crate) window: gtk::ApplicationWindow, - pub(crate) default_vbox: Option, - /// Window requests sender - pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>, - scale_factor: Rc, - inner_position: Rc<(AtomicI32, AtomicI32)>, - outer_position: Rc<(AtomicI32, AtomicI32)>, - outer_size: Rc<(AtomicI32, AtomicI32)>, - inner_size: Rc<(AtomicI32, AtomicI32)>, - maximized: Rc, - is_always_on_top: Rc, - minimized: Rc, - fullscreen: RefCell>, - inner_size_constraints: RefCell, - /// Draw event Sender - draw_tx: crossbeam_channel::Sender, - preferred_theme: RefCell>, - css_provider: CssProvider, -} - -impl Window { - pub(crate) fn new( - event_loop_window_target: &EventLoopWindowTarget, - attributes: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let app = &event_loop_window_target.app; - let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); - let draw_tx = event_loop_window_target.draw_tx.clone(); - let is_wayland = event_loop_window_target.is_wayland(); - - let mut window_builder = gtk::ApplicationWindow::builder() - .application(app) - .accept_focus(attributes.focusable && attributes.focused); - if let Parent::ChildOf(parent) = pl_attribs.parent { - window_builder = window_builder.transient_for(&parent); - } - - let window = window_builder.build(); - - if is_wayland { - WlHeader::setup(&window, &attributes.title); - } - - let window_id = WindowId(window.id()); - event_loop_window_target - .windows - .borrow_mut() - .insert(window_id); - - // Set Width/Height & Resizable - let win_scale_factor = window.scale_factor(); - let (width, height) = attributes - .inner_size - .map(|size| size.to_logical::(win_scale_factor as f64).into()) - .unwrap_or((800, 600)); - window.set_default_size(1, 1); - window.resize(width, height); - - if attributes.maximized { - let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); - glib::idle_add_local_full(glib::Priority::HIGH_IDLE, move || { - let mut maximize_process = maximize_process.borrow_mut(); - maximize_process.next_step() - }); - } else { - window.set_resizable(attributes.resizable); - } - - window.set_deletable(attributes.closable); - - // Set Min/Max Size - util::set_size_constraints(&window, attributes.inner_size_constraints); - - // Set Position - if let Some(position) = attributes.position { - let (x, y): (i32, i32) = position.to_logical::(win_scale_factor as f64).into(); - window.move_(x, y); - } - - // Set GDK Visual - if pl_attribs.rgba_visual || attributes.transparent { - if let Some(screen) = GtkWindowExt::screen(&window) { - if let Some(visual) = screen.rgba_visual() { - window.set_visual(Some(&visual)); - } - } - } - - if pl_attribs.app_paintable || attributes.transparent { - // Set a few attributes to make the window can be painted. - // See Gtk drawing model for more info: - // https://docs.gtk.org/gtk3/drawing-model.html - window.set_app_paintable(true); - } - - if !pl_attribs.double_buffered { - let widget = window.upcast_ref::(); - if !event_loop_window_target.is_wayland() { - unsafe { - gtk::ffi::gtk_widget_set_double_buffered(widget.to_glib_none().0, 0); - } - } - } - - let default_vbox = if pl_attribs.default_vbox { - let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0); - window.add(&box_); - Some(box_) - } else { - None - }; - - // Rest attributes - window.set_title(&attributes.title); - if let Some(Fullscreen::Borderless(m)) = &attributes.fullscreen { - if let Some(monitor) = m { - let display = window.display(); - let monitor = &monitor.inner; - let monitors = display.n_monitors(); - for i in 0..monitors { - let m = display.monitor(i).unwrap(); - if m == monitor.monitor { - let screen = display.default_screen(); - window.fullscreen_on_monitor(&screen, i); - } - } - } else { - window.fullscreen(); - } - } - window.set_visible(attributes.visible); - window.set_decorated(attributes.decorations); - - if attributes.always_on_bottom { - window.set_keep_below(attributes.always_on_bottom); - } - - if attributes.always_on_top { - window.set_keep_above(attributes.always_on_top); - } - - if attributes.visible_on_all_workspaces { - window.stick(); - } - - // Set initial `preferred_theme` value to current portal color-scheme - #[cfg(feature = "dbus")] - let preferred_theme = super::portal::theme().ok(); - #[cfg(not(feature = "dbus"))] - let preferred_theme = None; - - if let Some(theme) = preferred_theme { - if let Some(settings) = Settings::default() { - settings.set_gtk_application_prefer_dark_theme(theme == Theme::Dark); - } - } - - if attributes.visible { - window.show_all(); - } else { - window.hide(); - } - - // restore accept-focus after the window has been drawn - // if the window was initially created without focus and is supposed to be focusable - if attributes.focusable && !attributes.focused { - let signal_id = Rc::new(RefCell::new(None)); - let signal_id_ = signal_id.clone(); - let id = window.connect_draw(move |window, _| { - if let Some(id) = signal_id_.take() { - window.set_accept_focus(true); - window.disconnect(id); - } - glib::Propagation::Proceed - }); - signal_id.borrow_mut().replace(id); - } - - // Check if we should paint the transparent background ourselves. - let mut transparent = false; - if attributes.transparent && pl_attribs.auto_transparent { - transparent = true; - } - let cursor_moved = pl_attribs.cursor_moved; - if let Err(e) = window_requests_tx.send(( - window_id, - WindowRequest::WireUpEvents { - transparent, - fullscreen: attributes.fullscreen.is_some(), - cursor_moved, - }, - )) { - log::warn!("Fail to send wire up events request: {}", e); - } - - let ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) = Self::setup_signals(&window, Some(&attributes)); - - if let Some(icon) = attributes.window_icon { - window.set_icon(Some(&icon.inner.into())); - } - - let win = Self { - window_id, - window, - default_vbox, - window_requests_tx, - draw_tx, - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - fullscreen: RefCell::new(attributes.fullscreen), - inner_size_constraints: RefCell::new(attributes.inner_size_constraints), - preferred_theme: RefCell::new(preferred_theme), - css_provider: CssProvider::new(), - }; - - let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); - win.set_background_color(attributes.background_color); - - Ok(win) - } - - fn setup_signals( - window: >k::ApplicationWindow, - attributes: Option<&WindowAttributes>, - ) -> ( - Rc, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc<(AtomicI32, AtomicI32)>, - Rc, - Rc, - Rc, - ) { - let win_scale_factor = window.scale_factor(); - - let w_pos = window.position(); - let inner_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_pos.0.into(), w_pos.1.into())); - let inner_position_clone = inner_position.clone(); - - let o_pos = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); - let outer_position: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_pos.0.into(), o_pos.1.into())); - let outer_position_clone = outer_position.clone(); - - let w_size = window.size(); - let inner_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_size.0.into(), w_size.1.into())); - let inner_size_clone = inner_size.clone(); - - let o_size = window.window().map(|w| w.root_origin()).unwrap_or(w_pos); - let outer_size: Rc<(AtomicI32, AtomicI32)> = Rc::new((o_size.0.into(), o_size.1.into())); - let outer_size_clone = outer_size.clone(); - - window.connect_configure_event(move |window, event| { - let (x, y) = event.position(); - inner_position_clone.0.store(x, Ordering::Release); - inner_position_clone.1.store(y, Ordering::Release); - - let (w, h) = event.size(); - inner_size_clone.0.store(w as i32, Ordering::Release); - inner_size_clone.1.store(h as i32, Ordering::Release); - - let (x, y, w, h) = window - .window() - .map(|w| { - let rect = w.frame_extents(); - (rect.x(), rect.y(), rect.width(), rect.height()) - }) - .unwrap_or((x, y, w as i32, h as i32)); - - outer_position_clone.0.store(x, Ordering::Release); - outer_position_clone.1.store(y, Ordering::Release); - - outer_size_clone.0.store(w, Ordering::Release); - outer_size_clone.1.store(h, Ordering::Release); - - false - }); - - let w_max = window.is_maximized(); - let maximized: Rc = Rc::new(w_max.into()); - let max_clone = maximized.clone(); - let minimized = Rc::new(AtomicBool::new(false)); - let minimized_clone = minimized.clone(); - let is_always_on_top = Rc::new(AtomicBool::new( - attributes.map(|a| a.always_on_top).unwrap_or(false), - )); - let is_always_on_top_clone = is_always_on_top.clone(); - - window.connect_window_state_event(move |_, event| { - let state = event.new_window_state(); - max_clone.store(state.contains(WindowState::MAXIMIZED), Ordering::Release); - minimized_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release); - is_always_on_top_clone.store(state.contains(WindowState::ABOVE), Ordering::Release); - glib::Propagation::Proceed - }); - - let scale_factor: Rc = Rc::new(win_scale_factor.into()); - let scale_factor_clone = scale_factor.clone(); - window.connect_scale_factor_notify(move |window| { - scale_factor_clone.store(window.scale_factor(), Ordering::Release); - }); - - ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) - } - - pub(crate) fn new_from_gtk_window( - event_loop_window_target: &EventLoopWindowTarget, - window: gtk::ApplicationWindow, - ) -> Result { - let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); - let draw_tx = event_loop_window_target.draw_tx.clone(); - - let window_id = WindowId(window.id()); - event_loop_window_target - .windows - .borrow_mut() - .insert(window_id); - - let ( - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - ) = Self::setup_signals(&window, None); - - let win = Self { - window_id, - window, - default_vbox: None, - window_requests_tx, - draw_tx, - scale_factor, - outer_position, - inner_position, - outer_size, - inner_size, - maximized, - minimized, - is_always_on_top, - fullscreen: RefCell::new(None), - inner_size_constraints: RefCell::new(WindowSizeConstraints::default()), - preferred_theme: RefCell::new(None), - css_provider: CssProvider::new(), - }; - - Ok(win) - } - - pub fn id(&self) -> WindowId { - self.window_id - } - - pub fn scale_factor(&self) -> f64 { - self.scale_factor.load(Ordering::Acquire) as f64 - } - - pub fn request_redraw(&self) { - if let Err(e) = self.draw_tx.send(self.window_id) { - log::warn!("Failed to send redraw event to event channel: {}", e); - } - } - - pub fn inner_position(&self) -> Result, NotSupportedError> { - let (x, y) = &*self.inner_position; - Ok( - LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), - ) - } - - pub fn outer_position(&self) -> Result, NotSupportedError> { - let (x, y) = &*self.outer_position; - Ok( - LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire)) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64), - ) - } - - pub fn set_outer_position>(&self, position: P) { - let (x, y): (i32, i32) = position - .into() - .to_logical::(self.scale_factor()) - .into(); - - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Position((x, y)))) - { - log::warn!("Fail to send position request: {}", e); - } - } - - pub fn set_background_color(&self, color: Option) { - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::BackgroundColor(self.css_provider.clone(), color), - )) { - log::warn!("Fail to send size request: {}", e); - } - } - - pub fn inner_size(&self) -> PhysicalSize { - let (width, height) = &*self.inner_size; - - LogicalSize::new( - width.load(Ordering::Acquire) as u32, - height.load(Ordering::Acquire) as u32, - ) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64) - } - - pub fn set_inner_size>(&self, size: S) { - let (width, height) = size.into().to_logical::(self.scale_factor()).into(); - - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Size((width, height)))) - { - log::warn!("Fail to send size request: {}", e); - } - } - - pub fn outer_size(&self) -> PhysicalSize { - let (width, height) = &*self.outer_size; - - LogicalSize::new( - width.load(Ordering::Acquire) as u32, - height.load(Ordering::Acquire) as u32, - ) - .to_physical(self.scale_factor.load(Ordering::Acquire) as f64) - } - - fn set_size_constraints(&self, constraints: WindowSizeConstraints) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::SizeConstraints(constraints))) - { - log::warn!("Fail to send size constraint request: {}", e); - } - } - - pub fn set_min_inner_size(&self, size: Option) { - let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); - size_constraints.min_width = width; - size_constraints.min_height = height; - self.set_size_constraints(*size_constraints) - } - - pub fn set_max_inner_size(&self, size: Option) { - let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); - size_constraints.max_width = width; - size_constraints.max_height = height; - self.set_size_constraints(*size_constraints) - } - - pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - *self.inner_size_constraints.borrow_mut() = constraints; - self.set_size_constraints(constraints) - } - - pub fn set_title(&self, title: &str) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Title(title.to_string()))) - { - log::warn!("Fail to send title request: {}", e); - } - } - - pub fn title(&self) -> String { - self - .window - .title() - .map(|t| t.as_str().to_string()) - .unwrap_or_default() - } - - pub fn set_visible(&self, visible: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Visible(visible))) - { - log::warn!("Fail to send visible request: {}", e); - } - } - - pub fn set_focus(&self) { - if !self.minimized.load(Ordering::Acquire) && self.window.get_visible() { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Focus)) - { - log::warn!("Fail to send visible request: {}", e); - } - } - } - - pub fn set_focusable(&self, focusable: bool) { - self.window.set_accept_focus(focusable); - } - - pub fn is_focused(&self) -> bool { - self.window.is_active() - } - - pub fn set_resizable(&self, resizable: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Resizable(resizable))) - { - log::warn!("Fail to send resizable request: {}", e); - } - } - - pub fn set_minimizable(&self, _minimizable: bool) {} - - pub fn set_maximizable(&self, _maximizable: bool) {} - - pub fn set_closable(&self, closable: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Closable(closable))) - { - log::warn!("Fail to send closable request: {}", e); - } - } - - pub fn set_minimized(&self, minimized: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Minimized(minimized))) - { - log::warn!("Fail to send minimized request: {}", e); - } - } - - pub fn set_maximized(&self, maximized: bool) { - let resizable = self.is_resizable(); - - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::Maximized(maximized, resizable), - )) { - log::warn!("Fail to send maximized request: {}", e); - } - } - - pub fn is_always_on_top(&self) -> bool { - self.is_always_on_top.load(Ordering::Acquire) - } - - pub fn is_maximized(&self) -> bool { - self.maximized.load(Ordering::Acquire) - } - - pub fn is_minimized(&self) -> bool { - self.minimized.load(Ordering::Acquire) - } - - pub fn is_resizable(&self) -> bool { - self.window.is_resizable() - } - - pub fn is_minimizable(&self) -> bool { - true - } - - pub fn is_maximizable(&self) -> bool { - true - } - pub fn is_closable(&self) -> bool { - self.window.is_deletable() - } - - pub fn is_decorated(&self) -> bool { - self.window.is_decorated() - } - - #[inline] - pub fn is_visible(&self) -> bool { - self.window.is_visible() - } - - pub fn drag_window(&self) -> Result<(), ExternalError> { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::DragWindow)) - { - log::warn!("Fail to send drag window request: {}", e); - } - Ok(()) - } - - pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::DragResizeWindow(direction))) - { - log::warn!("Fail to send drag window request: {}", e); - } - Ok(()) - } - - pub fn set_fullscreen(&self, fullscreen: Option) { - self.fullscreen.replace(fullscreen.clone()); - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Fullscreen(fullscreen))) - { - log::warn!("Fail to send fullscreen request: {}", e); - } - } - - pub fn fullscreen(&self) -> Option { - self.fullscreen.borrow().clone() - } - - pub fn set_decorations(&self, decorations: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::Decorations(decorations))) - { - log::warn!("Fail to send decorations request: {}", e); - } - } - - pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::AlwaysOnBottom(always_on_bottom), - )) { - log::warn!("Fail to send always on bottom request: {}", e); - } - } - - pub fn set_always_on_top(&self, always_on_top: bool) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::AlwaysOnTop(always_on_top))) - { - log::warn!("Fail to send always on top request: {}", e); - } - } - - pub fn set_window_icon(&self, window_icon: Option) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::WindowIcon(window_icon))) - { - log::warn!("Fail to send window icon request: {}", e); - } - } - - pub fn set_ime_position>(&self, _position: P) { - //TODO - } - - pub fn request_user_attention(&self, request_type: Option) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::UserAttention(request_type))) - { - log::warn!("Fail to send user attention request: {}", e); - } - } - - pub fn set_visible_on_all_workspaces(&self, visible: bool) { - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::SetVisibleOnAllWorkspaces(visible), - )) { - log::warn!("Fail to send visible on all workspaces request: {}", e); - } - } - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::CursorIcon(Some(cursor)))) - { - log::warn!("Fail to send cursor icon request: {}", e); - } - } - - pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { - let inner_pos = self.inner_position().unwrap_or_default(); - let (x, y): (i32, i32) = position - .into() - .to_logical::(self.scale_factor()) - .into(); - - if let Err(e) = self.window_requests_tx.send(( - self.window_id, - WindowRequest::CursorPosition((x + inner_pos.x, y + inner_pos.y)), - )) { - log::warn!("Fail to send cursor position request: {}", e); - } - - Ok(()) - } - - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - Ok(()) - } - - pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::CursorIgnoreEvents(ignore))) - { - log::warn!("Fail to send cursor position request: {}", e); - } - - Ok(()) - } - - pub fn set_cursor_visible(&self, visible: bool) { - let cursor = if visible { - Some(CursorIcon::Default) - } else { - None - }; - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::CursorIcon(cursor))) - { - log::warn!("Fail to send cursor visibility request: {}", e); - } - } - - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position(self.is_wayland()) - } - - pub fn current_monitor(&self) -> Option { - let display = self.window.display(); - // `.window()` returns `None` if the window is invisible; - // we fallback to the primary monitor - let monitor = self - .window - .window() - .and_then(|window| display.monitor_at_window(&window)) - .or_else(|| display.primary_monitor()); - - monitor.map(|monitor| RootMonitorHandle { - inner: MonitorHandle { monitor }, - }) - } - - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut handles = VecDeque::new(); - let display = self.window.display(); - let numbers = display.n_monitors(); - - for i in 0..numbers { - let monitor = MonitorHandle::new(&display, i); - handles.push_back(monitor); - } - - handles - } - - pub fn primary_monitor(&self) -> Option { - let display = self.window.display(); - display.primary_monitor().map(|monitor| { - let handle = MonitorHandle { monitor }; - RootMonitorHandle { inner: handle } - }) - } - - #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - let display = &self.window.display(); - monitor::from_point(display, x, y).map(|inner| RootMonitorHandle { inner }) - } - - fn is_wayland(&self) -> bool { - self.window.display().backend().is_wayland() - } - - #[cfg(feature = "rwh_04")] - #[inline] - pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { - if self.is_wayland() { - let mut window_handle = rwh_04::WaylandHandle::empty(); - if let Some(window) = self.window.window() { - window_handle.surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; - } - - rwh_04::RawWindowHandle::Wayland(window_handle) - } else { - let mut window_handle = rwh_04::XlibHandle::empty(); - unsafe { - if let Some(window) = self.window.window() { - window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); - } - } - rwh_04::RawWindowHandle::Xlib(window_handle) - } - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { - if self.is_wayland() { - let mut window_handle = rwh_05::WaylandWindowHandle::empty(); - if let Some(window) = self.window.window() { - window_handle.surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; - } - - rwh_05::RawWindowHandle::Wayland(window_handle) - } else { - let mut window_handle = rwh_05::XlibWindowHandle::empty(); - unsafe { - if let Some(window) = self.window.window() { - window_handle.window = gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _); - } - } - rwh_05::RawWindowHandle::Xlib(window_handle) - } - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - if self.is_wayland() { - let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); - display_handle.display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) - }; - rwh_05::RawDisplayHandle::Wayland(display_handle) - } else { - let mut display_handle = rwh_05::XlibDisplayHandle::empty(); - unsafe { - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - display_handle.display = display as _; - display_handle.screen = (xlib.XDefaultScreen)(display) as _; - } - } - - rwh_05::RawDisplayHandle::Xlib(display_handle) - } - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_window_handle_rwh_06(&self) -> Result { - if let Some(window) = self.window.window() { - if self.is_wayland() { - let surface = - unsafe { gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _) }; - let surface = unsafe { std::ptr::NonNull::new_unchecked(surface) }; - let window_handle = rwh_06::WaylandWindowHandle::new(surface); - Ok(rwh_06::RawWindowHandle::Wayland(window_handle)) - } else { - #[cfg(feature = "x11")] - { - let xid = unsafe { gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _) }; - let window_handle = rwh_06::XlibWindowHandle::new(xid); - Ok(rwh_06::RawWindowHandle::Xlib(window_handle)) - } - #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) - } - } else { - Err(rwh_06::HandleError::Unavailable) - } - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - if self.is_wayland() { - let display = unsafe { - gdk_wayland_sys::gdk_wayland_display_get_wl_display(self.window.display().as_ptr() as *mut _) - }; - let display = unsafe { std::ptr::NonNull::new_unchecked(display) }; - let display_handle = rwh_06::WaylandDisplayHandle::new(display); - Ok(rwh_06::RawDisplayHandle::Wayland(display_handle)) - } else { - #[cfg(feature = "x11")] - if let Ok(xlib) = x11_dl::xlib::Xlib::open() { - unsafe { - let display = (xlib.XOpenDisplay)(std::ptr::null()); - let screen = (xlib.XDefaultScreen)(display) as _; - let display = std::ptr::NonNull::new_unchecked(display as _); - let display_handle = rwh_06::XlibDisplayHandle::new(Some(display), screen); - Ok(rwh_06::RawDisplayHandle::Xlib(display_handle)) - } - } else { - Err(rwh_06::HandleError::Unavailable) - } - #[cfg(not(feature = "x11"))] - Err(rwh_06::HandleError::Unavailable) - } - } - - pub fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { - if let Err(e) = self - .window_requests_tx - .send((self.window_id, WindowRequest::SetSkipTaskbar(skip))) - { - log::warn!("Fail to send skip taskbar request: {}", e); - } - - Ok(()) - } - - pub fn set_progress_bar(&self, progress: ProgressBarState) { - if let Err(e) = self - .window_requests_tx - .send((WindowId::dummy(), WindowRequest::ProgressBarState(progress))) - { - log::warn!("Fail to send update progress bar request: {}", e); - } - } - - pub fn set_badge_count(&self, count: Option, desktop_filename: Option) { - if let Err(e) = self.window_requests_tx.send(( - WindowId::dummy(), - WindowRequest::BadgeCount(count, desktop_filename), - )) { - log::warn!("Fail to send update badge count request: {}", e); - } - } - - pub fn theme(&self) -> Theme { - if let Some(theme) = *self.preferred_theme.borrow() { - return theme; - } - - #[cfg(feature = "dbus")] - if let Ok(portal_theme) = super::portal::theme() { - return portal_theme; - } - - Theme::Light - } - - pub fn set_theme(&self, theme: Option) { - *self.preferred_theme.borrow_mut() = theme; - if let Err(e) = self - .window_requests_tx - .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) - { - log::warn!("Fail to send set theme request: {e}"); - } - } -} - -// We need GtkWindow to initialize WebView, so we have to keep it in the field. -// It is called on any method. -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -#[non_exhaustive] -pub enum WindowRequest { - Title(String), - Position((i32, i32)), - Size((i32, i32)), - SizeConstraints(WindowSizeConstraints), - Visible(bool), - Focus, - Resizable(bool), - Closable(bool), - Minimized(bool), - Maximized(bool, bool), - DragWindow, - DragResizeWindow(ResizeDirection), - Fullscreen(Option), - Decorations(bool), - AlwaysOnBottom(bool), - AlwaysOnTop(bool), - WindowIcon(Option), - UserAttention(Option), - SetSkipTaskbar(bool), - CursorIcon(Option), - CursorPosition((i32, i32)), - CursorIgnoreEvents(bool), - WireUpEvents { - transparent: bool, - fullscreen: bool, - cursor_moved: bool, - }, - SetVisibleOnAllWorkspaces(bool), - ProgressBarState(ProgressBarState), - BadgeCount(Option, Option), - SetTheme(Option), - BackgroundColor(CssProvider, Option), -} - -impl Drop for Window { - fn drop(&mut self) { - unsafe { - self.window.destroy(); - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs deleted file mode 100644 index 5d5a2cd72..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub use x11_dl::{ - error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, xrandr::*, - xrender::*, -}; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs deleted file mode 100644 index 0d5524d57..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub mod ffi; -pub mod xdisplay; - -pub use xdisplay::XConnection; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs deleted file mode 100644 index b8f40b317..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; - -use libc; -use parking_lot::Mutex; - -use crate::window::CursorIcon; - -use super::ffi; - -/// A connection to an X server. -pub struct XConnection { - pub xlib: ffi::Xlib, - /// Exposes XRandR functions from version < 1.5 - pub xrandr: ffi::Xrandr_2_2_0, - /// Exposes XRandR functions from version = 1.5 - pub xrandr_1_5: Option, - pub xcursor: ffi::Xcursor, - pub xinput2: ffi::XInput2, - pub xlib_xcb: ffi::Xlib_xcb, - pub xrender: ffi::Xrender, - pub display: *mut ffi::Display, - pub x11_fd: c_int, - pub latest_error: Mutex>, - pub cursor_cache: Mutex, ffi::Cursor>>, -} - -unsafe impl Send for XConnection {} -unsafe impl Sync for XConnection {} - -pub type XErrorHandler = - Option libc::c_int>; - -impl XConnection { - pub fn new(error_handler: XErrorHandler) -> Result { - // opening the libraries - let xlib = ffi::Xlib::open()?; - let xcursor = ffi::Xcursor::open()?; - let xrandr = ffi::Xrandr_2_2_0::open()?; - let xrandr_1_5 = ffi::Xrandr::open().ok(); - let xinput2 = ffi::XInput2::open()?; - let xlib_xcb = ffi::Xlib_xcb::open()?; - let xrender = ffi::Xrender::open()?; - - unsafe { (xlib.XInitThreads)() }; - unsafe { (xlib.XSetErrorHandler)(error_handler) }; - - // calling XOpenDisplay - let display = unsafe { - let display = (xlib.XOpenDisplay)(ptr::null()); - if display.is_null() { - return Err(XNotSupported::XOpenDisplayFailed); - } - display - }; - - // Get X11 socket file descriptor - let fd = unsafe { (xlib.XConnectionNumber)(display) }; - - Ok(XConnection { - xlib, - xrandr, - xrandr_1_5, - xcursor, - xinput2, - xlib_xcb, - xrender, - display, - x11_fd: fd, - latest_error: Mutex::new(None), - cursor_cache: Default::default(), - }) - } - - /// Checks whether an error has been triggered by the previous function calls. - #[inline] - pub fn check_errors(&self) -> Result<(), XError> { - let error = self.latest_error.lock().take(); - if let Some(error) = error { - Err(error) - } else { - Ok(()) - } - } - - /// Ignores any previous error. - #[inline] - pub fn ignore_error(&self) { - *self.latest_error.lock() = None; - } -} - -impl fmt::Debug for XConnection { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display.fmt(f) - } -} - -impl Drop for XConnection { - #[inline] - fn drop(&mut self) { - unsafe { (self.xlib.XCloseDisplay)(self.display) }; - } -} - -/// Error triggered by xlib. -#[derive(Debug, Clone)] -pub struct XError { - pub description: String, - pub error_code: u8, - pub request_code: u8, - pub minor_code: u8, -} - -impl Error for XError {} - -impl fmt::Display for XError { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - formatter, - "X error: {} (code: {}, request code: {}, minor code: {})", - self.description, self.error_code, self.request_code, self.minor_code - ) - } -} - -/// Error returned if this system doesn't have XLib or can't create an X connection. -#[derive(Clone, Debug)] -pub enum XNotSupported { - /// Failed to load one or several shared libraries. - LibraryOpenError(ffi::OpenError), - /// Connecting to the X server with `XOpenDisplay` failed. - XOpenDisplayFailed, // TODO: add better message -} - -impl From for XNotSupported { - #[inline] - fn from(err: ffi::OpenError) -> XNotSupported { - XNotSupported::LibraryOpenError(err) - } -} - -impl XNotSupported { - fn description(&self) -> &'static str { - match self { - XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", - XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", - } - } -} - -impl Error for XNotSupported { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - XNotSupported::LibraryOpenError(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for XNotSupported { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - formatter.write_str(self.description()) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs deleted file mode 100644 index a83363eef..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::VecDeque, ffi::CStr}; - -use objc2::runtime::{AnyClass as Class, ClassBuilder as ClassDecl, Sel}; -use objc2_app_kit::{self as appkit, NSApplication, NSEvent, NSEventType}; -use once_cell::sync::Lazy; - -use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; -use crate::event::{DeviceEvent, ElementState, Event}; - -pub struct AppClass(pub *const Class); -unsafe impl Send for AppClass {} -unsafe impl Sync for AppClass {} - -pub static APP_CLASS: Lazy = Lazy::new(|| unsafe { - let superclass = class!(NSApplication); - let mut decl = - ClassDecl::new(CStr::from_bytes_with_nul(b"TaoApp\0").unwrap(), superclass).unwrap(); - - decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _)); - - AppClass(decl.register()) -}); - -// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. -// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) -// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) -extern "C" fn send_event(this: &NSApplication, _sel: Sel, event: &NSEvent) { - unsafe { - // For posterity, there are some undocumented event types - // (https://github.com/servo/cocoa-rs/issues/155) - // but that doesn't really matter here. - let event_type = event.r#type(); - let modifier_flags = event.modifierFlags(); - if event_type == appkit::NSKeyUp - && util::has_flag(modifier_flags, appkit::NSEventModifierFlags::Command) - { - if let Some(key_window) = this.keyWindow() { - key_window.sendEvent(event); - } else { - log::debug!("skip sending CMD keyEvent - app has no keyWindow"); - } - } else { - maybe_dispatch_device_event(event); - let superclass = util::superclass(this); - let _: () = msg_send![super(this, superclass), sendEvent: event]; - } - } -} - -unsafe fn maybe_dispatch_device_event(event: &NSEvent) { - let event_type = event.r#type(); - match event_type { - NSEventType::MouseMoved - | NSEventType::LeftMouseDragged - | NSEventType::OtherMouseDragged - | NSEventType::RightMouseDragged => { - let mut events = VecDeque::with_capacity(3); - - let delta_x = event.deltaX() as f64; - let delta_y = event.deltaY() as f64; - - if delta_x != 0.0 { - events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::Motion { - axis: 0, - value: delta_x, - }, - })); - } - - if delta_y != 0.0 { - events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::Motion { - axis: 1, - value: delta_y, - }, - })); - } - - if delta_x != 0.0 || delta_y != 0.0 { - events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseMotion { - delta: (delta_x, delta_y), - }, - })); - } - - AppState::queue_events(events); - } - NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { - let mut events = VecDeque::with_capacity(1); - - events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::Button { - button: event.buttonNumber() as u32, - state: ElementState::Pressed, - }, - })); - - AppState::queue_events(events); - } - NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { - let mut events = VecDeque::with_capacity(1); - - events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::Button { - button: event.buttonNumber() as u32, - state: ElementState::Released, - }, - })); - - AppState::queue_events(events); - } - _ => (), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs deleted file mode 100644 index 5107814c9..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - platform::macos::ActivationPolicy, - platform_impl::platform::{ - app_state::AppState, - ffi::{id, BOOL, YES}, - }, -}; - -use objc2::runtime::{ - AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel, -}; -use objc2_foundation::{ - NSArray, NSError, NSString, NSUserActivity, NSUserActivityTypeBrowsingWeb, NSURL, -}; -use once_cell::sync::Lazy; -use std::{ - cell::{RefCell, RefMut}, - ffi::{CStr, CString}, - os::raw::c_void, - sync::Mutex, - time::Instant, -}; - -const AUX_DELEGATE_STATE_NAME: &str = "auxState"; - -pub struct AuxDelegateState { - /// We store this value in order to be able to defer setting the activation policy until - /// after the app has finished launching. If the activation policy is set earlier, the - /// menubar is initially unresponsive on macOS 10.15 for example. - pub activation_policy: ActivationPolicy, - - /// Whether the application is visible in the dock. - pub dock_visibility: bool, - pub last_dock_show: Mutex>, - - pub activate_ignoring_other_apps: bool, -} - -pub struct AppDelegateClass(pub *const Class); -unsafe impl Send for AppDelegateClass {} -unsafe impl Sync for AppDelegateClass {} - -pub static APP_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { - let superclass = class!(NSResponder); - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoAppDelegateParent\0").unwrap(), - superclass, - ) - .unwrap(); - - decl.add_class_method(sel!(new), new as extern "C" fn(_, _) -> _); - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); - - decl.add_method( - sel!(applicationDidFinishLaunching:), - did_finish_launching as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(applicationWillTerminate:), - application_will_terminate as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(application:openURLs:), - application_open_urls as extern "C" fn(_, _, _, _), - ); - decl.add_method( - sel!(application:willContinueUserActivityWithType:), - application_will_continue_user_activity_with_type as extern "C" fn(_, _, _, _) -> _, - ); - decl.add_method( - sel!(application:continueUserActivity:restorationHandler:), - application_continue_user_activity as extern "C" fn(_, _, _, _, _) -> _, - ); - decl.add_method( - sel!(applicationShouldHandleReopen:hasVisibleWindows:), - application_should_handle_reopen as extern "C" fn(_, _, _, _) -> _, - ); - decl.add_method( - sel!(applicationSupportsSecureRestorableState:), - application_supports_secure_restorable_state as extern "C" fn(_, _, _) -> _, - ); - decl.add_ivar::<*mut c_void>(&CString::new(AUX_DELEGATE_STATE_NAME).unwrap()); - - AppDelegateClass(decl.register()) -}); - -/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS -#[allow(deprecated)] // TODO: Use define_class! -pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> { - let ptr: *mut c_void = *this.get_ivar(AUX_DELEGATE_STATE_NAME); - // Watch out that this needs to be the correct type - (*(ptr as *mut RefCell)).borrow_mut() -} - -extern "C" fn new(class: &Class, _: Sel) -> id { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let this: id = msg_send![class, alloc]; - let this: id = msg_send![this, init]; - *(*this).get_mut_ivar(AUX_DELEGATE_STATE_NAME) = - Box::into_raw(Box::new(RefCell::new(AuxDelegateState { - activation_policy: ActivationPolicy::Regular, - activate_ignoring_other_apps: true, - dock_visibility: true, - last_dock_show: Mutex::new(None), - }))) as *mut c_void; - this - } -} - -extern "C" fn dealloc(this: &Object, _: Sel) { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME)); - // As soon as the box is constructed it is immediately dropped, releasing the underlying - // memory - drop(Box::from_raw(state_ptr as *mut RefCell)); - } -} - -extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) { - trace!("Triggered `applicationDidFinishLaunching`"); - AppState::launched(this); - trace!("Completed `applicationDidFinishLaunching`"); -} - -extern "C" fn application_will_terminate(_: &Object, _: Sel, _: id) { - trace!("Triggered `applicationWillTerminate`"); - AppState::exit(); - trace!("Completed `applicationWillTerminate`"); -} - -extern "C" fn application_open_urls(_: &Object, _: Sel, _: id, urls: &NSArray) { - trace!("Trigger `application:openURLs:`"); - - let urls = unsafe { - (0..urls.count()) - .flat_map(|i| url::Url::parse(&urls.objectAtIndex(i).absoluteString().unwrap().to_string())) - .collect::>() - }; - trace!("Get `application:openURLs:` URLs: {:?}", urls); - AppState::open_urls(urls); - trace!("Completed `application:openURLs:`"); -} - -extern "C" fn application_will_continue_user_activity_with_type( - _: &Object, - _: Sel, - _: id, - user_activity_type: &NSString, -) -> Bool { - trace!("Trigger `application:willContinueUserActivityWithType:`"); - let result = unsafe { Bool::new(user_activity_type == NSUserActivityTypeBrowsingWeb) }; - trace!("Completed `application:willContinueUserActivityWithType:`"); - result -} - -extern "C" fn application_continue_user_activity( - _: &Object, - _: Sel, - _: id, - user_activity: &NSUserActivity, - _restoration_handler: &block2::Block, -) -> Bool { - trace!("Trigger `application:continueUserActivity:restorationHandler:`"); - let url = unsafe { - if user_activity - .activityType() - .isEqualToString(NSUserActivityTypeBrowsingWeb) - { - match user_activity - .webpageURL() - .and_then(|url| url.absoluteString()) - .and_then(|s| Some(s.to_string())) - { - None => { - error!( - "`application:continueUserActivity:restorationHandler:`: restore webbrowsing activity but url is empty" - ); - return Bool::new(false); - } - Some(url_string) => match url::Url::parse(&url_string) { - Ok(url) => url, - Err(err) => { - error!( - "`application:continueUserActivity:restorationHandler:`: failed to parse url {err}" - ); - return Bool::new(false); - } - }, - } - } else { - return Bool::new(false); - } - }; - - AppState::open_urls(vec![url]); - trace!("Completed `application:continueUserActivity:restorationHandler:`"); - return Bool::new(true); -} - -extern "C" fn application_should_handle_reopen( - _: &Object, - _: Sel, - _: id, - has_visible_windows: BOOL, -) -> BOOL { - trace!("Triggered `applicationShouldHandleReopen`"); - AppState::reopen(has_visible_windows.as_bool()); - trace!("Completed `applicationShouldHandleReopen`"); - has_visible_windows -} - -extern "C" fn application_supports_secure_restorable_state(_: &Object, _: Sel, _: id) -> BOOL { - trace!("Triggered `applicationSupportsSecureRestorableState`"); - trace!("Completed `applicationSupportsSecureRestorableState`"); - YES -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs deleted file mode 100644 index 03dff9132..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - cell::{RefCell, RefMut}, - collections::VecDeque, - fmt::{self, Debug}, - hint::unreachable_unchecked, - mem, - rc::{Rc, Weak}, - sync::{ - atomic::{AtomicBool, Ordering}, - Mutex, MutexGuard, - }, - time::Instant, -}; - -use objc2::{msg_send, rc::Retained, runtime::AnyObject as Object}; -use objc2_app_kit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}; -use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSSize}; -use once_cell::sync::Lazy; - -use crate::{ - dpi::LogicalSize, - event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, - platform::macos::ActivationPolicy, - platform_impl::{ - get_aux_state_mut, - platform::{ - event::{EventProxy, EventWrapper}, - event_loop::{post_dummy_event, PanicInfo}, - ffi::{id, nil}, - observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker}, - util::{self, Never}, - window::get_window_id, - }, - }, - window::WindowId, -}; - -use super::set_dock_visibility; - -static HANDLER: Lazy = Lazy::new(|| Default::default()); - -impl<'a, Never> Event<'a, Never> { - fn userify(self) -> Event<'a, T> { - self - .map_nonuser_event() - // `Never` can't be constructed, so the `UserEvent` variant can't - // be present here. - .unwrap_or_else(|_| unsafe { unreachable_unchecked() }) - } -} - -pub trait EventHandler: Debug { - // Not sure probably it should accept Event<'static, Never> - fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); - fn handle_user_events(&mut self, control_flow: &mut ControlFlow); -} - -struct EventLoopHandler { - callback: Weak, &RootWindowTarget, &mut ControlFlow)>>, - window_target: Rc>, -} - -impl EventLoopHandler { - fn with_callback(&mut self, f: F) - where - F: FnOnce( - &mut EventLoopHandler, - RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow)>, - ), - { - if let Some(callback) = self.callback.upgrade() { - let callback = callback.borrow_mut(); - (f)(self, callback); - } else { - panic!( - "Tried to dispatch an event, but the event loop that \ - owned the event handler callback seems to be destroyed" - ); - } - } -} - -impl Debug for EventLoopHandler { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("EventLoopHandler") - .field("window_target", &self.window_target) - .finish() - } -} - -impl EventHandler for EventLoopHandler { - fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { - self.with_callback(|this, mut callback| { - if let ControlFlow::ExitWithCode(code) = *control_flow { - let dummy = &mut ControlFlow::ExitWithCode(code); - (callback)(event.userify(), &this.window_target, dummy); - } else { - (callback)(event.userify(), &this.window_target, control_flow); - } - }); - } - - fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { - self.with_callback(|this, mut callback| { - for event in this.window_target.p.receiver.try_iter() { - if let ControlFlow::ExitWithCode(code) = *control_flow { - let dummy = &mut ControlFlow::ExitWithCode(code); - (callback)(Event::UserEvent(event), &this.window_target, dummy); - } else { - (callback)(Event::UserEvent(event), &this.window_target, control_flow); - } - } - }); - } -} - -#[derive(Default)] -struct Handler { - ready: AtomicBool, - in_callback: AtomicBool, - control_flow: Mutex, - control_flow_prev: Mutex, - start_time: Mutex>, - callback: Mutex>>, - pending_events: Mutex>, - pending_redraw: Mutex>, - waker: Mutex, -} - -unsafe impl Send for Handler {} -unsafe impl Sync for Handler {} - -impl Handler { - fn events(&self) -> MutexGuard<'_, VecDeque> { - self.pending_events.lock().unwrap() - } - - fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec> { - self.pending_redraw.lock().unwrap() - } - - fn waker(&self) -> MutexGuard<'_, EventLoopWaker> { - self.waker.lock().unwrap() - } - - fn is_ready(&self) -> bool { - self.ready.load(Ordering::Acquire) - } - - fn set_ready(&self) { - self.ready.store(true, Ordering::Release); - } - - fn should_exit(&self) -> bool { - matches!( - *self.control_flow.lock().unwrap(), - ControlFlow::ExitWithCode(_) - ) - } - - fn get_control_flow_and_update_prev(&self) -> ControlFlow { - let control_flow = self.control_flow.lock().unwrap(); - *self.control_flow_prev.lock().unwrap() = *control_flow; - *control_flow - } - - fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { - let old = *self.control_flow_prev.lock().unwrap(); - let new = *self.control_flow.lock().unwrap(); - (old, new) - } - - fn get_start_time(&self) -> Option { - *self.start_time.lock().unwrap() - } - - fn update_start_time(&self) { - *self.start_time.lock().unwrap() = Some(Instant::now()); - } - - fn take_events(&self) -> VecDeque { - mem::take(&mut *self.events()) - } - - fn should_redraw(&self) -> Vec { - mem::take(&mut *self.redraw()) - } - - fn get_in_callback(&self) -> bool { - self.in_callback.load(Ordering::Acquire) - } - - fn set_in_callback(&self, in_callback: bool) { - self.in_callback.store(in_callback, Ordering::Release); - } - - fn handle_nonuser_event(&self, wrapper: EventWrapper) { - if let Some(ref mut callback) = *self.callback.lock().unwrap() { - match wrapper { - EventWrapper::StaticEvent(event) => { - callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()) - } - EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback), - } - } - } - - fn handle_user_events(&self) { - if let Some(ref mut callback) = *self.callback.lock().unwrap() { - callback.handle_user_events(&mut self.control_flow.lock().unwrap()); - } - } - - fn handle_scale_factor_changed_event( - &self, - callback: &mut Box, - ns_window: &NSWindow, - suggested_size: LogicalSize, - scale_factor: f64, - ) { - let mut size = suggested_size.to_physical(scale_factor); - let old_size = size; - let event = Event::WindowEvent { - window_id: WindowId(get_window_id(ns_window)), - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut size, - }, - }; - - callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()); - - if old_size != size { - let logical_size = size.to_logical(scale_factor); - let size = NSSize::new(logical_size.width, logical_size.height); - NSWindow::setContentSize(ns_window, size); - } - } - - fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { - match proxy { - EventProxy::DpiChangedProxy { - ns_window, - suggested_size, - scale_factor, - } => { - self.handle_scale_factor_changed_event(callback, &ns_window, suggested_size, scale_factor) - } - } - } -} - -pub enum AppState {} - -impl AppState { - pub fn set_callback( - callback: Weak, &RootWindowTarget, &mut ControlFlow)>>, - window_target: Rc>, - ) { - *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { - callback, - window_target, - })); - } - - pub fn exit() -> i32 { - HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); - HANDLER.set_in_callback(false); - HANDLER.callback.lock().unwrap().take(); - if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 { - code - } else { - 0 - } - } - - pub fn launched(app_delegate: &Object) { - apply_activation_policy(app_delegate); - - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let ns_app = NSApp(mtm); - window_activation_hack(&ns_app); - let ignore = get_aux_state_mut(app_delegate).activate_ignoring_other_apps; - #[allow(deprecated)] - ns_app.activateIgnoringOtherApps(ignore); - - let dock_visible = get_aux_state_mut(app_delegate).dock_visibility; - if !dock_visible { - set_dock_visibility(app_delegate, dock_visible); - } - }; - HANDLER.set_ready(); - HANDLER.waker().start(); - HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( - StartCause::Init, - ))); - HANDLER.set_in_callback(false); - } - - pub fn open_urls(urls: Vec) { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); - } - - pub fn reopen(has_visible_windows: bool) { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Reopen { - has_visible_windows, - })); - } - - pub fn wakeup(panic_info: Weak) { - let panic_info = panic_info - .upgrade() - .expect("The panic info must exist here. This failure indicates a developer error."); - // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { - return; - } - let start = HANDLER.get_start_time().unwrap(); - let cause = match HANDLER.get_control_flow_and_update_prev() { - ControlFlow::Poll => StartCause::Poll, - ControlFlow::Wait => StartCause::WaitCancelled { - start, - requested_resume: None, - }, - ControlFlow::WaitUntil(requested_resume) => { - if Instant::now() >= requested_resume { - StartCause::ResumeTimeReached { - start, - requested_resume, - } - } else { - StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - } - } - } - ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), - }; - HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); - HANDLER.set_in_callback(false); - } - - // This is called from multiple threads at present - pub fn queue_redraw(window_id: WindowId) { - let mut pending_redraw = HANDLER.redraw(); - if !pending_redraw.contains(&window_id) { - pending_redraw.push(window_id); - } - unsafe { - let rl = CFRunLoopGetMain(); - CFRunLoopWakeUp(rl); - } - } - - pub fn handle_redraw(window_id: WindowId) { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); - } - - pub fn queue_event(wrapper: EventWrapper) { - if !util::is_main_thread() { - panic!("Event queued from different thread: {:#?}", wrapper); - } - HANDLER.events().push_back(wrapper); - } - - pub fn queue_events(mut wrappers: VecDeque) { - if !util::is_main_thread() { - panic!("Events queued from different thread: {:#?}", wrappers); - } - HANDLER.events().append(&mut wrappers); - } - - pub fn cleared(panic_info: Weak) { - let panic_info = panic_info - .upgrade() - .expect("The panic info must exist here. This failure indicates a developer error."); - // Return when in callback due to https://github.com/rust-windowing/winit/issues/1779 - if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() { - return; - } - HANDLER.set_in_callback(true); - HANDLER.handle_user_events(); - for event in HANDLER.take_events() { - HANDLER.handle_nonuser_event(event); - } - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); - for window_id in HANDLER.should_redraw() { - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id))); - } - HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); - HANDLER.set_in_callback(false); - if HANDLER.should_exit() { - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let app = NSApp(mtm); - let _pool = NSAutoreleasePool::new(); - let () = msg_send![&app, stop: nil]; - // To stop event loop immediately, we need to post some event here. - post_dummy_event(&app); - }; - } - HANDLER.update_start_time(); - match HANDLER.get_old_and_new_control_flow() { - (ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (), - (old, new) if old == new => (), - (_, ControlFlow::Wait) => HANDLER.waker().stop(), - (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), - (_, ControlFlow::Poll) => HANDLER.waker().start(), - } - } -} - -/// A hack to make activation of multiple windows work when creating them before -/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`. -/// -/// Alternative to this would be the user calling `window.set_visible(true)` in -/// `StartCause::Init`. -/// -/// If this becomes too bothersome to maintain, it can probably be removed -/// without too much damage. -unsafe fn window_activation_hack(ns_app: &NSApplication) { - // Get the application's windows - // TODO: Proper ordering of the windows - let ns_windows: id = msg_send![ns_app, windows]; - let ns_enumerator: id = msg_send![ns_windows, objectEnumerator]; - loop { - // Enumerate over the windows - let ns_window: Option> = msg_send![ns_enumerator, nextObject]; - if ns_window.is_none() { - break; - } - let ns_window = ns_window.unwrap(); - // And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new` - // This way we preserve the user's desired initial visiblity status - // TODO: Also filter on the type/"level" of the window, and maybe other things? - if ns_window.isVisible() { - trace!("Activating visible window"); - ns_window.makeKeyAndOrderFront(None); - } else { - trace!("Skipping activating invisible window"); - } - } -} -fn apply_activation_policy(app_delegate: &Object) { - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let ns_app = NSApp(mtm); - // We need to delay setting the activation policy and activating the app - // until `applicationDidFinishLaunching` has been called. Otherwise the - // menu bar won't be interactable. - let act_pol = get_aux_state_mut(app_delegate).activation_policy; - ns_app.setActivationPolicy(match act_pol { - ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, - ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, - ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, - }); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs deleted file mode 100644 index 5c8933b98..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs +++ /dev/null @@ -1,13 +0,0 @@ -use super::ffi::id; -use objc2_app_kit::NSApp; -use objc2_foundation::{MainThreadMarker, NSString}; - -pub fn set_badge_label(label: Option) { - // SAFETY: TODO - let mtm = unsafe { MainThreadMarker::new_unchecked() }; - unsafe { - let label = label.map(|label| NSString::from_str(&label)); - let dock_tile: id = msg_send![&NSApp(mtm), dockTile]; - let _: () = msg_send![dock_tile, setBadgeLabel: label.as_deref()]; - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs deleted file mode 100644 index 31a1f25df..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs +++ /dev/null @@ -1,91 +0,0 @@ -#![allow(non_snake_case, non_upper_case_globals)] - -use std::{ - sync::Mutex, - time::{Duration, Instant}, -}; - -use objc2::{runtime::AnyObject, MainThreadMarker}; -use objc2_app_kit::NSApplication; - -use super::get_aux_state_mut; - -const DOCK_SHOW_TIMEOUT: Duration = Duration::from_secs(1); - -#[link(name = "ApplicationServices", kind = "framework")] -extern "C" { - fn TransformProcessType(psn: *const ProcessSerialNumber, transformState: i32) -> i32; -} - -#[repr(C)] -struct ProcessSerialNumber { - highLongOfPSN: u32, - lowLongOfPSN: u32, -} - -/// https://developer.apple.com/documentation/applicationservices/1501096-anonymous/kcurrentprocess?language=objc -pub const kCurrentProcess: u32 = 2; -/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtouielementapplication?language=objc -pub const kProcessTransformToUIElementApplication: i32 = 4; -/// https://developer.apple.com/documentation/applicationservices/1501117-anonymous/kprocesstransformtoforegroundapplication?language=objc -pub const kProcessTransformToForegroundApplication: i32 = 1; - -pub fn set_dock_visibility(app_delegate: &AnyObject, visible: bool) { - let last_dock_show = unsafe { &get_aux_state_mut(app_delegate).last_dock_show }; - if visible { - set_dock_show(last_dock_show); - } else { - set_dock_hide(last_dock_show); - } -} - -fn set_dock_hide(last_dock_show: &Mutex>) { - // Transforming application state from UIElement to Foreground is an - // asynchronous operation, and unfortunately there is currently no way to know - // when it is finished. - // So if we call DockHide => DockShow => DockHide => DockShow in a very short - // time, we would trigger a bug of macOS that, there would be multiple dock - // icons of the app left in system. - // To work around this, we make sure DockHide does nothing if it is called - // immediately after DockShow. After some experiments, 1 second seems to be - // a proper interval. - let now = Instant::now(); - let last_dock_show = last_dock_show.lock().unwrap(); - if let Some(last_dock_show_time) = *last_dock_show { - if now.duration_since(last_dock_show_time) < DOCK_SHOW_TIMEOUT { - return; - } - } - - unsafe { - // TODO: Safety. - let mtm = MainThreadMarker::new_unchecked(); - let app = NSApplication::sharedApplication(mtm); - let windows = app.windows(); - - for window in windows { - window.setCanHide(false); - } - - let psn = ProcessSerialNumber { - highLongOfPSN: 0, - lowLongOfPSN: kCurrentProcess, - }; - TransformProcessType(&psn, kProcessTransformToUIElementApplication); - } -} - -fn set_dock_show(last_dock_show: &Mutex>) { - let now = Instant::now(); - let mut last_dock_show = last_dock_show.lock().unwrap(); - *last_dock_show = Some(now); - - unsafe { - let psn = ProcessSerialNumber { - highLongOfPSN: 0, - lowLongOfPSN: kCurrentProcess, - }; - - TransformProcessType(&psn, kProcessTransformToForegroundApplication); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs deleted file mode 100644 index 6b1ce97f5..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::HashSet, ffi::c_void, os::raw::c_ushort, sync::Mutex}; - -use objc2::{msg_send, rc::Retained}; -use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSWindow}; -use once_cell::sync::Lazy; - -use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; -use objc2_foundation::NSString; - -use crate::{ - dpi::LogicalSize, - event::{ElementState, Event, KeyEvent}, - keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, - platform_impl::platform::{ffi, util::Never}, -}; - -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); - -fn insert_or_get_key_str(string: String) -> &'static str { - let mut string_set = KEY_STRINGS.lock().unwrap(); - if let Some(contained) = string_set.get(string.as_str()) { - return contained; - } - let static_str = Box::leak(string.into_boxed_str()); - string_set.insert(static_str); - static_str -} - -#[non_exhaustive] -#[derive(Debug)] -pub enum EventWrapper { - StaticEvent(Event<'static, Never>), - EventProxy(EventProxy), -} - -#[non_exhaustive] -#[derive(Debug, PartialEq)] -pub enum EventProxy { - #[non_exhaustive] - DpiChangedProxy { - ns_window: Retained, - suggested_size: LogicalSize, - scale_factor: f64, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct KeyEventExtra { - pub text_with_all_modifiers: Option<&'static str>, - pub key_without_modifiers: Key<'static>, -} - -pub fn get_modifierless_char(scancode: u16) -> Key<'static> { - let mut string = [0; 16]; - let input_source; - let layout; - unsafe { - input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); - if input_source.is_null() { - log::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); - return Key::Unidentified(NativeKeyCode::MacOS(scancode)); - } - let layout_data = - ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); - if layout_data.is_null() { - CFRelease(input_source as *mut c_void); - log::error!("`TISGetInputSourceProperty` returned null ptr"); - return Key::Unidentified(NativeKeyCode::MacOS(scancode)); - } - layout = CFDataGetBytePtr(layout_data) as *const ffi::UCKeyboardLayout; - } - let keyboard_type = unsafe { ffi::LMGetKbdType() }; - - let mut result_len = 0; - let mut dead_keys = 0; - let modifiers = 0; - let translate_result = unsafe { - ffi::UCKeyTranslate( - layout, - scancode, - ffi::kUCKeyActionDisplay, - modifiers, - keyboard_type as u32, - ffi::kUCKeyTranslateNoDeadKeysMask, - &mut dead_keys, - string.len() as ffi::UniCharCount, - &mut result_len, - string.as_mut_ptr(), - ) - }; - unsafe { - CFRelease(input_source as *mut c_void); - } - if translate_result != 0 { - log::error!( - "`UCKeyTranslate` returned with the non-zero value: {}", - translate_result - ); - return Key::Unidentified(NativeKeyCode::MacOS(scancode)); - } - if result_len == 0 { - log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length."); - return Key::Unidentified(NativeKeyCode::MacOS(scancode)); - } - let chars = String::from_utf16_lossy(&string[0..result_len as usize]); - Key::Character(insert_or_get_key_str(chars)) -} - -fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key<'static> { - let characters: Retained = unsafe { msg_send![ns_event, charactersIgnoringModifiers] }; - let string = characters.to_string(); - if string.is_empty() { - // Probably a dead key - let first_char = modifierless_chars.chars().next(); - return Key::Dead(first_char); - } - Key::Character(insert_or_get_key_str(string)) -} - -#[allow(clippy::unnecessary_unwrap)] -pub fn create_key_event( - ns_event: &NSEvent, - is_press: bool, - is_repeat: bool, - in_ime: bool, - key_override: Option, -) -> KeyEvent { - use ElementState::{Pressed, Released}; - let state = if is_press { Pressed } else { Released }; - - let scancode = get_scancode(ns_event); - let mut physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); - - let text_with_all_modifiers: Option<&'static str> = { - if key_override.is_some() { - None - } else { - let characters: Retained = unsafe { msg_send![ns_event, characters] }; - let characters = characters.to_string(); - if characters.is_empty() { - None - } else { - if matches!(physical_key, KeyCode::Unidentified(_)) { - // The key may be one of the funky function keys - physical_key = extra_function_key_to_code(scancode, &characters); - } - Some(insert_or_get_key_str(characters)) - } - } - }; - let key_from_code = code_to_key(physical_key, scancode); - let logical_key; - let key_without_modifiers; - if !matches!(key_from_code, Key::Unidentified(_)) { - logical_key = key_from_code.clone(); - key_without_modifiers = key_from_code; - } else { - //#[cfg(debug_assertions)] println!("Couldn't get key from code: {:?}", physical_key); - key_without_modifiers = get_modifierless_char(scancode); - - let modifiers = unsafe { NSEvent::modifierFlags(ns_event) }; - let has_alt = modifiers.contains(NSEventModifierFlags::Option); - let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); - if has_alt || has_ctrl || text_with_all_modifiers.is_none() || !is_press { - let modifierless_chars = match key_without_modifiers.clone() { - Key::Character(ch) => ch, - _ => "", - }; - logical_key = get_logical_key_char(ns_event, modifierless_chars); - } else { - logical_key = Key::Character(text_with_all_modifiers.unwrap()); - } - } - let text = if in_ime || !is_press { - None - } else { - logical_key.to_text() - }; - KeyEvent { - location: code_to_location(physical_key), - logical_key, - physical_key, - repeat: is_repeat, - state, - text, - platform_specific: KeyEventExtra { - text_with_all_modifiers, - key_without_modifiers, - }, - } -} - -pub fn code_to_key(code: KeyCode, scancode: u16) -> Key<'static> { - match code { - KeyCode::Enter => Key::Enter, - KeyCode::Tab => Key::Tab, - KeyCode::Space => Key::Space, - KeyCode::Backspace => Key::Backspace, - KeyCode::Escape => Key::Escape, - KeyCode::SuperRight => Key::Super, - KeyCode::SuperLeft => Key::Super, - KeyCode::ShiftLeft => Key::Shift, - KeyCode::AltLeft => Key::Alt, - KeyCode::ControlLeft => Key::Control, - KeyCode::ShiftRight => Key::Shift, - KeyCode::AltRight => Key::Alt, - KeyCode::ControlRight => Key::Control, - - KeyCode::NumLock => Key::NumLock, - KeyCode::AudioVolumeUp => Key::AudioVolumeUp, - KeyCode::AudioVolumeDown => Key::AudioVolumeDown, - - // Other numpad keys all generate text on macOS (if I understand correctly) - KeyCode::NumpadEnter => Key::Enter, - - KeyCode::F1 => Key::F1, - KeyCode::F2 => Key::F2, - KeyCode::F3 => Key::F3, - KeyCode::F4 => Key::F4, - KeyCode::F5 => Key::F5, - KeyCode::F6 => Key::F6, - KeyCode::F7 => Key::F7, - KeyCode::F8 => Key::F8, - KeyCode::F9 => Key::F9, - KeyCode::F10 => Key::F10, - KeyCode::F11 => Key::F11, - KeyCode::F12 => Key::F12, - KeyCode::F13 => Key::F13, - KeyCode::F14 => Key::F14, - KeyCode::F15 => Key::F15, - KeyCode::F16 => Key::F16, - KeyCode::F17 => Key::F17, - KeyCode::F18 => Key::F18, - KeyCode::F19 => Key::F19, - KeyCode::F20 => Key::F20, - - KeyCode::Insert => Key::Insert, - KeyCode::Home => Key::Home, - KeyCode::PageUp => Key::PageUp, - KeyCode::Delete => Key::Delete, - KeyCode::End => Key::End, - KeyCode::PageDown => Key::PageDown, - KeyCode::ArrowLeft => Key::ArrowLeft, - KeyCode::ArrowRight => Key::ArrowRight, - KeyCode::ArrowDown => Key::ArrowDown, - KeyCode::ArrowUp => Key::ArrowUp, - _ => Key::Unidentified(NativeKeyCode::MacOS(scancode)), - } -} - -fn code_to_location(code: KeyCode) -> KeyLocation { - match code { - KeyCode::SuperRight => KeyLocation::Right, - KeyCode::SuperLeft => KeyLocation::Left, - KeyCode::ShiftLeft => KeyLocation::Left, - KeyCode::AltLeft => KeyLocation::Left, - KeyCode::ControlLeft => KeyLocation::Left, - KeyCode::ShiftRight => KeyLocation::Right, - KeyCode::AltRight => KeyLocation::Right, - KeyCode::ControlRight => KeyLocation::Right, - - KeyCode::NumLock => KeyLocation::Numpad, - KeyCode::NumpadDecimal => KeyLocation::Numpad, - KeyCode::NumpadMultiply => KeyLocation::Numpad, - KeyCode::NumpadAdd => KeyLocation::Numpad, - KeyCode::NumpadDivide => KeyLocation::Numpad, - KeyCode::NumpadEnter => KeyLocation::Numpad, - KeyCode::NumpadSubtract => KeyLocation::Numpad, - KeyCode::NumpadEqual => KeyLocation::Numpad, - KeyCode::Numpad0 => KeyLocation::Numpad, - KeyCode::Numpad1 => KeyLocation::Numpad, - KeyCode::Numpad2 => KeyLocation::Numpad, - KeyCode::Numpad3 => KeyLocation::Numpad, - KeyCode::Numpad4 => KeyLocation::Numpad, - KeyCode::Numpad5 => KeyLocation::Numpad, - KeyCode::Numpad6 => KeyLocation::Numpad, - KeyCode::Numpad7 => KeyLocation::Numpad, - KeyCode::Numpad8 => KeyLocation::Numpad, - KeyCode::Numpad9 => KeyLocation::Numpad, - - _ => KeyLocation::Standard, - } -} - -// While F1-F20 have scancodes we can match on, we have to check against UTF-16 -// constants for the rest. -// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ -pub fn extra_function_key_to_code(scancode: u16, string: &str) -> KeyCode { - if let Some(ch) = string.encode_utf16().next() { - match ch { - 0xf718 => KeyCode::F21, - 0xf719 => KeyCode::F22, - 0xf71a => KeyCode::F23, - 0xf71b => KeyCode::F24, - _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)), - } - } else { - KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)) - } -} - -pub fn event_mods(event: &NSEvent) -> ModifiersState { - let flags = unsafe { NSEvent::modifierFlags(event) }; - let mut m = ModifiersState::empty(); - m.set( - ModifiersState::SHIFT, - flags.contains(NSEventModifierFlags::Shift), - ); - m.set( - ModifiersState::CONTROL, - flags.contains(NSEventModifierFlags::Control), - ); - m.set( - ModifiersState::ALT, - flags.contains(NSEventModifierFlags::Option), - ); - m.set( - ModifiersState::SUPER, - flags.contains(NSEventModifierFlags::Command), - ); - m -} - -pub fn get_scancode(event: &NSEvent) -> c_ushort { - // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, - // and there is no easy way to navtively retrieve the layout-dependent character. - // In tao, we use keycode to refer to the key's character, and so this function aligns - // AppKit's terminology with ours. - unsafe { msg_send![event, keyCode] } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs deleted file mode 100644 index 8179ea91f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - any::Any, - cell::{Cell, RefCell}, - collections::VecDeque, - marker::PhantomData, - mem, - os::raw::c_void, - panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}, - process, ptr, - rc::{Rc, Weak}, -}; - -use crossbeam_channel::{self as channel, Receiver, Sender}; -use objc2::{msg_send, rc::Retained}; -use objc2_app_kit::{NSApp, NSApplication, NSEventModifierFlags, NSEventSubtype, NSEventType}; -use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSInteger, NSPoint, NSTimeInterval}; - -use crate::{ - dpi::PhysicalPosition, - error::ExternalError, - event::Event, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::{ - platform::{ - app::APP_CLASS, - app_delegate::APP_DELEGATE_CLASS, - app_state::AppState, - ffi::{id, nil, YES}, - monitor::{self, MonitorHandle}, - observer::*, - util::{self, IdRef}, - }, - set_badge_label, set_progress_indicator, - }, - window::{ProgressBarState, Theme}, -}; - -use super::window::set_ns_theme; - -#[derive(Default)] -pub struct PanicInfo { - inner: Cell>>, -} - -// WARNING: -// As long as this struct is used through its `impl`, it is UnwindSafe. -// (If `get_mut` is called on `inner`, unwind safety may get broken.) -impl UnwindSafe for PanicInfo {} -impl RefUnwindSafe for PanicInfo {} -impl PanicInfo { - pub fn is_panicking(&self) -> bool { - let inner = self.inner.take(); - let result = inner.is_some(); - self.inner.set(inner); - result - } - /// Overwrites the curret state if the current state is not panicking - pub fn set_panic(&self, p: Box) { - if !self.is_panicking() { - self.inner.set(Some(p)); - } - } - pub fn take(&self) -> Option> { - self.inner.take() - } -} - -#[derive(Clone)] -pub struct EventLoopWindowTarget { - pub sender: Sender, // this is only here to be cloned elsewhere - pub receiver: Receiver, -} - -impl Default for EventLoopWindowTarget { - fn default() -> Self { - let (sender, receiver) = channel::unbounded(); - EventLoopWindowTarget { sender, receiver } - } -} - -impl EventLoopWindowTarget { - #[inline] - pub fn available_monitors(&self) -> VecDeque { - monitor::available_monitors() - } - - #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - monitor::from_point(x, y) - } - - #[inline] - pub fn primary_monitor(&self) -> Option { - let monitor = monitor::primary_monitor(); - Some(RootMonitorHandle { inner: monitor }) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::AppKit( - rwh_06::AppKitDisplayHandle::new(), - )) - } - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position() - } - - #[inline] - pub fn set_progress_bar(&self, progress: ProgressBarState) { - set_progress_indicator(progress); - } - - #[inline] - pub fn set_badge_count(&self, count: Option, _desktop_filename: Option) { - set_badge_label(count.map(|c| c.to_string())); - } - - #[inline] - pub fn set_badge_label(&self, label: Option) { - set_badge_label(label); - } - - #[inline] - pub fn set_theme(&self, theme: Option) { - set_ns_theme(theme) - } -} - -pub struct EventLoop { - pub(crate) delegate: IdRef, - - window_target: Rc>, - panic_info: Rc, - - /// We make sure that the callback closure is dropped during a panic - /// by making the event loop own it. - /// - /// Every other reference should be a Weak reference which is only upgraded - /// into a strong reference in order to call the callback but then the - /// strong reference should be dropped as soon as possible. - _callback: Option, &RootWindowTarget, &mut ControlFlow)>>>, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] -pub(crate) struct PlatformSpecificEventLoopAttributes {} - -impl EventLoop { - pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { - let panic_info: Rc = Default::default(); - setup_control_flow_observers(Rc::downgrade(&panic_info)); - - let delegate = unsafe { - if !util::is_main_thread() { - panic!("On macOS, `EventLoop` must be created on the main thread!"); - } - - // This must be done before `NSApp()` (equivalent to sending - // `sharedApplication`) is called anywhere else, or we'll end up - // with the wrong `NSApplication` class and the wrong thread could - // be marked as main. - let app: id = msg_send![APP_CLASS.0, sharedApplication]; - - let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); - let _pool = NSAutoreleasePool::new(); - let _: () = msg_send![app, setDelegate:*delegate]; - delegate - }; - - EventLoop { - delegate, - window_target: Rc::new(RootWindowTarget { - p: Default::default(), - _marker: PhantomData, - }), - panic_info, - _callback: None, - } - } - - pub fn window_target(&self) -> &RootWindowTarget { - &self.window_target - } - - pub fn run(mut self, callback: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), - { - let exit_code = self.run_return(callback); - process::exit(exit_code); - } - - pub fn run_return(&mut self, callback: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), - { - // This transmute is always safe, in case it was reached through `run`, since our - // lifetime will be already 'static. In other cases caller should ensure that all data - // they passed to callback will actually outlive it, some apps just can't move - // everything to event loop, so this is something that they should care about. - let callback = unsafe { - mem::transmute::< - Rc, &RootWindowTarget, &mut ControlFlow)>>, - Rc, &RootWindowTarget, &mut ControlFlow)>>, - >(Rc::new(RefCell::new(callback))) - }; - - self._callback = Some(Rc::clone(&callback)); - - let mtm = MainThreadMarker::new().unwrap(); - - let exit_code = unsafe { - let _pool = NSAutoreleasePool::new(); - let app = NSApp(mtm); - - // A bit of juggling with the callback references to make sure - // that `self.callback` is the only owner of the callback. - let weak_cb: Weak<_> = Rc::downgrade(&callback); - mem::drop(callback); - - AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); - let () = msg_send![&app, run]; - - if let Some(panic) = self.panic_info.take() { - drop(self._callback.take()); - resume_unwind(panic); - } - AppState::exit() - }; - drop(self._callback.take()); - - exit_code - } - - pub fn create_proxy(&self) -> Proxy { - Proxy::new(self.window_target.p.sender.clone()) - } -} - -#[inline] -pub unsafe fn post_dummy_event(target: &NSApplication) { - let event_class = class!(NSEvent); - let dummy_event: id = msg_send![ - event_class, - otherEventWithType: NSEventType::ApplicationDefined, - location: NSPoint::new(0.0, 0.0), - modifierFlags: NSEventModifierFlags::empty(), - timestamp: 0 as NSTimeInterval, - windowNumber: 0 as NSInteger, - context: nil, - subtype: NSEventSubtype::WindowExposed, - data1: 0 as NSInteger, - data2: 0 as NSInteger, - ]; - let () = msg_send![target, postEvent: dummy_event, atStart: YES]; -} - -/// Catches panics that happen inside `f` and when a panic -/// happens, stops the `sharedApplication` -#[inline] -pub fn stop_app_on_panic R + UnwindSafe, R>( - panic_info: Weak, - f: F, -) -> Option { - match catch_unwind(f) { - Ok(r) => Some(r), - Err(e) => { - // It's important that we set the panic before requesting a `stop` - // because some callback are still called during the `stop` message - // and we need to know in those callbacks if the application is currently - // panicking - { - let panic_info = panic_info.upgrade().unwrap(); - panic_info.set_panic(e); - } - unsafe { - let app_class = class!(NSApplication); - let app: Retained = msg_send![app_class, sharedApplication]; - let () = msg_send![&app, stop: nil]; - - // Posting a dummy event to get `stop` to take effect immediately. - // See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752 - post_dummy_event(&app); - } - None - } - } -} - -pub struct Proxy { - sender: Sender, - source: CFRunLoopSourceRef, -} - -unsafe impl Send for Proxy {} -unsafe impl Sync for Proxy {} - -impl Drop for Proxy { - fn drop(&mut self) { - unsafe { - CFRelease(self.source as _); - } - } -} - -impl Clone for Proxy { - fn clone(&self) -> Self { - Proxy::new(self.sender.clone()) - } -} - -impl Proxy { - fn new(sender: Sender) -> Self { - unsafe { - // just wake up the eventloop - extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} - - // adding a Source to the main CFRunLoop lets us wake it up and - // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain(); - let mut context: CFRunLoopSourceContext = mem::zeroed(); - context.perform = Some(event_loop_proxy_handler); - let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); - CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); - CFRunLoopWakeUp(rl); - - Proxy { sender, source } - } - } - - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self - .sender - .send(event) - .map_err(|channel::SendError(x)| EventLoopClosed(x))?; - unsafe { - // let the main thread know there's a new event - CFRunLoopSourceSignal(self.source); - let rl = CFRunLoopGetMain(); - CFRunLoopWakeUp(rl); - } - Ok(()) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs deleted file mode 100644 index 3b4a36421..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -// TODO: Upstream these - -#![allow( - dead_code, - non_snake_case, - non_upper_case_globals, - clippy::enum_variant_names -)] - -use std::{ffi::c_void, ptr}; - -use core_foundation::{ - array::CFArrayRef, data::CFDataRef, dictionary::CFDictionaryRef, string::CFStringRef, - uuid::CFUUIDRef, -}; -use core_graphics::{ - base::CGError, - display::{boolean_t, CGDirectDisplayID, CGDisplayConfigRef}, - geometry::{CGPoint, CGRect}, -}; -use objc2::{ - encode::{Encode, Encoding}, - runtime::{AnyObject, Bool}, -}; -use objc2_foundation::NSInteger; - -#[allow(non_camel_case_types)] -pub type id = *mut AnyObject; -pub const nil: id = ptr::null_mut(); - -#[allow(non_camel_case_types)] -pub type BOOL = Bool; -#[allow(deprecated)] -pub const YES: Bool = Bool::YES; -#[allow(deprecated)] -pub const NO: Bool = Bool::NO; - -pub const NSNotFound: NSInteger = NSInteger::MAX; - -pub const kCGBaseWindowLevelKey: NSInteger = 0; -pub const kCGMinimumWindowLevelKey: NSInteger = 1; -pub const kCGDesktopWindowLevelKey: NSInteger = 2; -pub const kCGBackstopMenuLevelKey: NSInteger = 3; -pub const kCGNormalWindowLevelKey: NSInteger = 4; -pub const kCGFloatingWindowLevelKey: NSInteger = 5; -pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6; -pub const kCGDockWindowLevelKey: NSInteger = 7; -pub const kCGMainMenuWindowLevelKey: NSInteger = 8; -pub const kCGStatusWindowLevelKey: NSInteger = 9; -pub const kCGModalPanelWindowLevelKey: NSInteger = 10; -pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11; -pub const kCGDraggingWindowLevelKey: NSInteger = 12; -pub const kCGScreenSaverWindowLevelKey: NSInteger = 13; -pub const kCGMaximumWindowLevelKey: NSInteger = 14; -pub const kCGOverlayWindowLevelKey: NSInteger = 15; -pub const kCGHelpWindowLevelKey: NSInteger = 16; -pub const kCGUtilityWindowLevelKey: NSInteger = 17; -pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; -pub const kCGCursorWindowLevelKey: NSInteger = 19; -pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; - -#[derive(Debug, Clone, Copy)] -#[repr(isize)] -pub enum NSWindowLevel { - BelowNormalWindowLevel = (kCGBaseWindowLevelKey - 1) as _, - NSNormalWindowLevel = kCGBaseWindowLevelKey as _, - NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, - NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _, - NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _, - NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _, - NSStatusWindowLevel = kCGStatusWindowLevelKey as _, - NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, - NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, -} - -unsafe impl Encode for NSWindowLevel { - const ENCODING: Encoding = isize::ENCODING; -} - -pub type CGDisplayFadeInterval = f32; -pub type CGDisplayReservationInterval = f32; -pub type CGDisplayBlendFraction = f32; - -pub const kCGDisplayBlendNormal: f32 = 0.0; -pub const kCGDisplayBlendSolidColor: f32 = 1.0; - -pub type CGDisplayFadeReservationToken = u32; -pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; - -pub type Boolean = u8; -pub const FALSE: Boolean = 0; -pub const TRUE: Boolean = 1; - -pub const kCGErrorSuccess: i32 = 0; -pub const kCGErrorFailure: i32 = 1000; -pub const kCGErrorIllegalArgument: i32 = 1001; -pub const kCGErrorInvalidConnection: i32 = 1002; -pub const kCGErrorInvalidContext: i32 = 1003; -pub const kCGErrorCannotComplete: i32 = 1004; -pub const kCGErrorNotImplemented: i32 = 1006; -pub const kCGErrorRangeCheck: i32 = 1007; -pub const kCGErrorTypeCheck: i32 = 1008; -pub const kCGErrorInvalidOperation: i32 = 1010; -pub const kCGErrorNoneAvailable: i32 = 1011; - -pub const IO1BitIndexedPixels: &str = "P"; -pub const IO2BitIndexedPixels: &str = "PP"; -pub const IO4BitIndexedPixels: &str = "PPPP"; -pub const IO8BitIndexedPixels: &str = "PPPPPPPP"; -pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; -pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; - -pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; -pub const kIO64BitDirectPixels: &str = "-16R16G16B16"; - -pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16"; -pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32"; - -pub const IOYUV422Pixels: &str = "Y4U2V2"; -pub const IO8BitOverlayPixels: &str = "O8"; - -pub type CGWindowLevel = i32; -pub type CGDisplayModeRef = *mut libc::c_void; - -// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework. -// However, that framework was only introduced "publicly" in macOS 10.13. -// -// Since we want to support older versions, we can't link to `ColorSync` -// directly. Fortunately, it has always been available as a subframework of -// `ApplicationServices`, see: -// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG -#[link(name = "ApplicationServices", kind = "framework")] -extern "C" { - pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; -} - -#[link(name = "CoreGraphics", kind = "framework")] -extern "C" { - pub fn CGRestorePermanentDisplayConfiguration(); - pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError; - pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError; - pub fn CGConfigureDisplayFadeEffect( - config: CGDisplayConfigRef, - fadeOutSeconds: CGDisplayFadeInterval, - fadeInSeconds: CGDisplayFadeInterval, - fadeRed: f32, - fadeGreen: f32, - fadeBlue: f32, - ) -> CGError; - pub fn CGAcquireDisplayFadeReservation( - seconds: CGDisplayReservationInterval, - token: *mut CGDisplayFadeReservationToken, - ) -> CGError; - pub fn CGDisplayFade( - token: CGDisplayFadeReservationToken, - duration: CGDisplayFadeInterval, - startBlend: CGDisplayBlendFraction, - endBlend: CGDisplayBlendFraction, - redBlend: f32, - greenBlend: f32, - blueBlend: f32, - synchronous: Boolean, - ) -> CGError; - pub fn CGRectContainsPoint(rect: CGRect, point: CGPoint) -> boolean_t; - pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; - pub fn CGShieldingWindowLevel() -> CGWindowLevel; - pub fn CGDisplaySetDisplayMode( - display: CGDirectDisplayID, - mode: CGDisplayModeRef, - options: CFDictionaryRef, - ) -> CGError; - pub fn CGDisplayCopyAllDisplayModes( - display: CGDirectDisplayID, - options: CFDictionaryRef, - ) -> CFArrayRef; - pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize; - pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize; - pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64; - pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef; - pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); - pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); -} - -#[repr(transparent)] -pub struct TISInputSource(std::ffi::c_void); -pub type TISInputSourceRef = *mut TISInputSource; - -#[repr(transparent)] -pub struct UCKeyboardLayout(std::ffi::c_void); - -pub type OptionBits = u32; -pub type UniCharCount = std::os::raw::c_ulong; -pub type UniChar = u16; -pub type OSStatus = i32; - -#[allow(non_upper_case_globals)] -pub const kUCKeyActionDisplay: u16 = 3; -#[allow(non_upper_case_globals)] -pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; - -#[link(name = "Carbon", kind = "framework")] -extern "C" { - pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; - - #[allow(non_snake_case)] - pub fn TISGetInputSourceProperty( - inputSource: TISInputSourceRef, - propertyKey: CFStringRef, - ) -> CFDataRef; - - pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; - - pub fn LMGetKbdType() -> u8; - - #[allow(non_snake_case)] - pub fn UCKeyTranslate( - keyLayoutPtr: *const UCKeyboardLayout, - virtualKeyCode: u16, - keyAction: u16, - modifierKeyState: u32, - keyboardType: u32, - keyTranslateOptions: OptionBits, - deadKeyState: *mut u32, - maxStringLength: UniCharCount, - actualStringLength: *mut UniCharCount, - unicodeString: *mut UniChar, - ) -> OSStatus; -} - -mod core_video { - use super::*; - - #[link(name = "CoreVideo", kind = "framework")] - extern "C" {} - - // CVBase.h - - pub type CVTimeFlags = i32; // int32_t - pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0; - - #[repr(C)] - #[derive(Debug, Clone)] - pub struct CVTime { - pub time_value: i64, // int64_t - pub time_scale: i32, // int32_t - pub flags: i32, // int32_t - } - - // CVReturn.h - - pub type CVReturn = i32; // int32_t - pub const kCVReturnSuccess: CVReturn = 0; - - // CVDisplayLink.h - - pub type CVDisplayLinkRef = *mut c_void; - - extern "C" { - pub fn CVDisplayLinkCreateWithCGDisplay( - displayID: CGDirectDisplayID, - displayLinkOut: *mut CVDisplayLinkRef, - ) -> CVReturn; - pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink: CVDisplayLinkRef) - -> CVTime; - pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef); - } -} - -pub use core_video::*; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs deleted file mode 100644 index c5c7439d0..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::icon::{BadIcon, RgbaIcon}; - -#[derive(Debug, Clone)] -pub struct PlatformIcon(RgbaIcon); - -impl PlatformIcon { - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - Ok(PlatformIcon(RgbaIcon::from_rgba(rgba, width, height)?)) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs deleted file mode 100644 index 4418cfcce..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::keyboard::{KeyCode, NativeKeyCode}; - -pub fn keycode_to_scancode(code: KeyCode) -> Option { - match code { - KeyCode::KeyA => Some(0x00), - KeyCode::KeyS => Some(0x01), - KeyCode::KeyD => Some(0x02), - KeyCode::KeyF => Some(0x03), - KeyCode::KeyH => Some(0x04), - KeyCode::KeyG => Some(0x05), - KeyCode::KeyZ => Some(0x06), - KeyCode::KeyX => Some(0x07), - KeyCode::KeyC => Some(0x08), - KeyCode::KeyV => Some(0x09), - KeyCode::KeyB => Some(0x0b), - KeyCode::KeyQ => Some(0x0c), - KeyCode::KeyW => Some(0x0d), - KeyCode::KeyE => Some(0x0e), - KeyCode::KeyR => Some(0x0f), - KeyCode::KeyY => Some(0x10), - KeyCode::KeyT => Some(0x11), - KeyCode::Digit1 => Some(0x12), - KeyCode::Digit2 => Some(0x13), - KeyCode::Digit3 => Some(0x14), - KeyCode::Digit4 => Some(0x15), - KeyCode::Digit6 => Some(0x16), - KeyCode::Digit5 => Some(0x17), - KeyCode::Equal => Some(0x18), - KeyCode::Digit9 => Some(0x19), - KeyCode::Digit7 => Some(0x1a), - KeyCode::Minus => Some(0x1b), - KeyCode::Digit8 => Some(0x1c), - KeyCode::Digit0 => Some(0x1d), - KeyCode::BracketRight => Some(0x1e), - KeyCode::KeyO => Some(0x1f), - KeyCode::KeyU => Some(0x20), - KeyCode::BracketLeft => Some(0x21), - KeyCode::KeyI => Some(0x22), - KeyCode::KeyP => Some(0x23), - KeyCode::Enter => Some(0x24), - KeyCode::KeyL => Some(0x25), - KeyCode::KeyJ => Some(0x26), - KeyCode::Quote => Some(0x27), - KeyCode::KeyK => Some(0x28), - KeyCode::Semicolon => Some(0x29), - KeyCode::Backslash => Some(0x2a), - KeyCode::Comma => Some(0x2b), - KeyCode::Slash => Some(0x2c), - KeyCode::KeyN => Some(0x2d), - KeyCode::KeyM => Some(0x2e), - KeyCode::Period => Some(0x2f), - KeyCode::Tab => Some(0x30), - KeyCode::Space => Some(0x31), - KeyCode::Backquote => Some(0x32), - KeyCode::Backspace => Some(0x33), - KeyCode::Escape => Some(0x35), - KeyCode::SuperRight => Some(0x36), - KeyCode::SuperLeft => Some(0x37), - KeyCode::ShiftLeft => Some(0x38), - KeyCode::AltLeft => Some(0x3a), - KeyCode::ControlLeft => Some(0x3b), - KeyCode::ShiftRight => Some(0x3c), - KeyCode::AltRight => Some(0x3d), - KeyCode::ControlRight => Some(0x3e), - KeyCode::F17 => Some(0x40), - KeyCode::NumpadDecimal => Some(0x41), - KeyCode::NumpadMultiply => Some(0x43), - KeyCode::NumpadAdd => Some(0x45), - KeyCode::NumLock => Some(0x47), - KeyCode::AudioVolumeUp => Some(0x49), - KeyCode::AudioVolumeDown => Some(0x4a), - KeyCode::NumpadDivide => Some(0x4b), - KeyCode::NumpadEnter => Some(0x4c), - KeyCode::NumpadSubtract => Some(0x4e), - KeyCode::F18 => Some(0x4f), - KeyCode::F19 => Some(0x50), - KeyCode::NumpadEqual => Some(0x51), - KeyCode::Numpad0 => Some(0x52), - KeyCode::Numpad1 => Some(0x53), - KeyCode::Numpad2 => Some(0x54), - KeyCode::Numpad3 => Some(0x55), - KeyCode::Numpad4 => Some(0x56), - KeyCode::Numpad5 => Some(0x57), - KeyCode::Numpad6 => Some(0x58), - KeyCode::Numpad7 => Some(0x59), - KeyCode::F20 => Some(0x5a), - KeyCode::Numpad8 => Some(0x5b), - KeyCode::Numpad9 => Some(0x5c), - KeyCode::IntlYen => Some(0x5d), - KeyCode::F5 => Some(0x60), - KeyCode::F6 => Some(0x61), - KeyCode::F7 => Some(0x62), - KeyCode::F3 => Some(0x63), - KeyCode::F8 => Some(0x64), - KeyCode::F9 => Some(0x65), - KeyCode::F11 => Some(0x67), - KeyCode::F13 => Some(0x69), - KeyCode::F16 => Some(0x6a), - KeyCode::F14 => Some(0x6b), - KeyCode::F10 => Some(0x6d), - KeyCode::F12 => Some(0x6f), - KeyCode::F15 => Some(0x71), - KeyCode::Insert => Some(0x72), - KeyCode::Home => Some(0x73), - KeyCode::PageUp => Some(0x74), - KeyCode::Delete => Some(0x75), - KeyCode::F4 => Some(0x76), - KeyCode::End => Some(0x77), - KeyCode::F2 => Some(0x78), - KeyCode::PageDown => Some(0x79), - KeyCode::F1 => Some(0x7a), - KeyCode::ArrowLeft => Some(0x7b), - KeyCode::ArrowRight => Some(0x7c), - KeyCode::ArrowDown => Some(0x7d), - KeyCode::ArrowUp => Some(0x7e), - _ => None, - } -} - -pub fn keycode_from_scancode(scancode: u32) -> KeyCode { - match scancode { - 0x00 => KeyCode::KeyA, - 0x01 => KeyCode::KeyS, - 0x02 => KeyCode::KeyD, - 0x03 => KeyCode::KeyF, - 0x04 => KeyCode::KeyH, - 0x05 => KeyCode::KeyG, - 0x06 => KeyCode::KeyZ, - 0x07 => KeyCode::KeyX, - 0x08 => KeyCode::KeyC, - 0x09 => KeyCode::KeyV, - //0x0a => World 1, - 0x0b => KeyCode::KeyB, - 0x0c => KeyCode::KeyQ, - 0x0d => KeyCode::KeyW, - 0x0e => KeyCode::KeyE, - 0x0f => KeyCode::KeyR, - 0x10 => KeyCode::KeyY, - 0x11 => KeyCode::KeyT, - 0x12 => KeyCode::Digit1, - 0x13 => KeyCode::Digit2, - 0x14 => KeyCode::Digit3, - 0x15 => KeyCode::Digit4, - 0x16 => KeyCode::Digit6, - 0x17 => KeyCode::Digit5, - 0x18 => KeyCode::Equal, - 0x19 => KeyCode::Digit9, - 0x1a => KeyCode::Digit7, - 0x1b => KeyCode::Minus, - 0x1c => KeyCode::Digit8, - 0x1d => KeyCode::Digit0, - 0x1e => KeyCode::BracketRight, - 0x1f => KeyCode::KeyO, - 0x20 => KeyCode::KeyU, - 0x21 => KeyCode::BracketLeft, - 0x22 => KeyCode::KeyI, - 0x23 => KeyCode::KeyP, - 0x24 => KeyCode::Enter, - 0x25 => KeyCode::KeyL, - 0x26 => KeyCode::KeyJ, - 0x27 => KeyCode::Quote, - 0x28 => KeyCode::KeyK, - 0x29 => KeyCode::Semicolon, - 0x2a => KeyCode::Backslash, - 0x2b => KeyCode::Comma, - 0x2c => KeyCode::Slash, - 0x2d => KeyCode::KeyN, - 0x2e => KeyCode::KeyM, - 0x2f => KeyCode::Period, - 0x30 => KeyCode::Tab, - 0x31 => KeyCode::Space, - 0x32 => KeyCode::Backquote, - 0x33 => KeyCode::Backspace, - //0x34 => unknown, - 0x35 => KeyCode::Escape, - 0x36 => KeyCode::SuperRight, - 0x37 => KeyCode::SuperLeft, - 0x38 => KeyCode::ShiftLeft, - 0x39 => KeyCode::CapsLock, - 0x3a => KeyCode::AltLeft, - 0x3b => KeyCode::ControlLeft, - 0x3c => KeyCode::ShiftRight, - 0x3d => KeyCode::AltRight, - 0x3e => KeyCode::ControlRight, - 0x3f => KeyCode::Fn, - 0x40 => KeyCode::F17, - 0x41 => KeyCode::NumpadDecimal, - //0x42 -> unknown, - 0x43 => KeyCode::NumpadMultiply, - //0x44 => unknown, - 0x45 => KeyCode::NumpadAdd, - //0x46 => unknown, - 0x47 => KeyCode::NumLock, - //0x48 => KeyCode::NumpadClear, - - // TODO: (Artur) for me, kVK_VolumeUp is 0x48 - // macOS 10.11 - // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h - //0x49 => KeyCode::AudioVolumeUp, - 0x49 => KeyCode::AudioVolumeDown, - 0x4b => KeyCode::NumpadDivide, - 0x4c => KeyCode::NumpadEnter, - //0x4d => unknown, - 0x4e => KeyCode::NumpadSubtract, - 0x4f => KeyCode::F18, - 0x50 => KeyCode::F19, - 0x51 => KeyCode::NumpadEqual, - 0x52 => KeyCode::Numpad0, - 0x53 => KeyCode::Numpad1, - 0x54 => KeyCode::Numpad2, - 0x55 => KeyCode::Numpad3, - 0x56 => KeyCode::Numpad4, - 0x57 => KeyCode::Numpad5, - 0x58 => KeyCode::Numpad6, - 0x59 => KeyCode::Numpad7, - 0x5a => KeyCode::F20, - 0x5b => KeyCode::Numpad8, - 0x5c => KeyCode::Numpad9, - 0x5d => KeyCode::IntlYen, - //0x5e => JIS Ro, - //0x5f => unknown, - 0x60 => KeyCode::F5, - 0x61 => KeyCode::F6, - 0x62 => KeyCode::F7, - 0x63 => KeyCode::F3, - 0x64 => KeyCode::F8, - 0x65 => KeyCode::F9, - //0x66 => JIS Eisuu (macOS), - 0x67 => KeyCode::F11, - //0x68 => JIS Kanna (macOS), - 0x69 => KeyCode::F13, - 0x6a => KeyCode::F16, - 0x6b => KeyCode::F14, - //0x6c => unknown, - 0x6d => KeyCode::F10, - //0x6e => unknown, - 0x6f => KeyCode::F12, - //0x70 => unknown, - 0x71 => KeyCode::F15, - 0x72 => KeyCode::Insert, - 0x73 => KeyCode::Home, - 0x74 => KeyCode::PageUp, - 0x75 => KeyCode::Delete, - 0x76 => KeyCode::F4, - 0x77 => KeyCode::End, - 0x78 => KeyCode::F2, - 0x79 => KeyCode::PageDown, - 0x7a => KeyCode::F1, - 0x7b => KeyCode::ArrowLeft, - 0x7c => KeyCode::ArrowRight, - 0x7d => KeyCode::ArrowDown, - 0x7e => KeyCode::ArrowUp, - //0x7f => unknown, - - // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as - // backquote (`) on Windows' US layout. - 0xa => KeyCode::Backquote, - _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode as u16)), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs deleted file mode 100644 index 0c24a5257..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -mod app; -mod app_delegate; -mod app_state; -mod badge; -mod dock; -mod event; -mod event_loop; -mod ffi; -mod icon; -mod keycode; -mod monitor; -mod observer; -mod progress_bar; -mod util; -mod view; -mod window; -mod window_delegate; - -use std::{fmt, ops::Deref, sync::Arc}; - -pub(crate) use self::event_loop::PlatformSpecificEventLoopAttributes; -pub use self::{ - app_delegate::get_aux_state_mut, - event::KeyEventExtra, - event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, - keycode::{keycode_from_scancode, keycode_to_scancode}, - monitor::{MonitorHandle, VideoMode}, - progress_bar::set_progress_indicator, - window::{Id as WindowId, Parent, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, -}; -use crate::{ - error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, -}; -pub(crate) use badge::set_badge_label; -pub(crate) use dock::set_dock_visibility; -pub(crate) use icon::PlatformIcon; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId; - -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId - } -} - -// Constant device ID; to be removed when if backend is updated to report real device IDs. -pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId); - -pub struct Window { - window: Arc, - // We keep this around so that it doesn't get dropped until the window does. - #[allow(dead_code)] - delegate: util::IdRef, -} - -#[non_exhaustive] -#[derive(Debug)] -pub enum OsError { - CGError(core_graphics::base::CGError), - CreationError(&'static str), -} - -unsafe impl Send for Window {} -unsafe impl Sync for Window {} - -impl Deref for Window { - type Target = UnownedWindow; - #[inline] - fn deref(&self) -> &Self::Target { - &self.window - } -} - -impl Window { - pub fn new( - _window_target: &EventLoopWindowTarget, - attributes: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let (window, delegate) = UnownedWindow::new(attributes, pl_attribs)?; - Ok(Window { window, delegate }) - } -} - -impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - OsError::CGError(e) => f.pad(&format!("CGError {}", e)), - OsError::CreationError(e) => f.pad(e), - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs deleted file mode 100644 index 7cdbd3986..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{collections::VecDeque, fmt}; - -use super::ffi::{self, id, nil, CGRectContainsPoint}; -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, -}; -use core_foundation::{ - array::{CFArrayGetCount, CFArrayGetValueAtIndex}, - base::{CFRelease, TCFType}, - string::CFString, -}; -use core_graphics::{ - display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}, - geometry::CGPoint, -}; -use objc2::{msg_send, rc::Retained}; -use objc2_app_kit::NSScreen; -use objc2_foundation::{ns_string, MainThreadMarker, NSString, NSUInteger}; - -#[derive(Clone)] -pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, - pub(crate) monitor: MonitorHandle, - pub(crate) native_mode: NativeDisplayMode, -} - -impl PartialEq for VideoMode { - fn eq(&self, other: &Self) -> bool { - self.size == other.size - && self.bit_depth == other.bit_depth - && self.refresh_rate == other.refresh_rate - && self.monitor == other.monitor - } -} - -impl Eq for VideoMode {} - -impl std::hash::Hash for VideoMode { - fn hash(&self, state: &mut H) { - self.size.hash(state); - self.bit_depth.hash(state); - self.refresh_rate.hash(state); - self.monitor.hash(state); - } -} - -impl std::fmt::Debug for VideoMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("VideoMode") - .field("size", &self.size) - .field("bit_depth", &self.bit_depth) - .field("refresh_rate", &self.refresh_rate) - .field("monitor", &self.monitor) - .finish() - } -} - -pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); - -unsafe impl Send for NativeDisplayMode {} - -impl Drop for NativeDisplayMode { - fn drop(&mut self) { - unsafe { - ffi::CGDisplayModeRelease(self.0); - } - } -} - -impl Clone for NativeDisplayMode { - fn clone(&self) -> Self { - unsafe { - ffi::CGDisplayModeRetain(self.0); - } - NativeDisplayMode(self.0) - } -} - -impl VideoMode { - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - pub fn monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: self.monitor.clone(), - } - } -} - -#[derive(Clone)] -pub struct MonitorHandle(CGDirectDisplayID); - -// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that -// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an -// unique identifier that persists even across system reboots -impl PartialEq for MonitorHandle { - fn eq(&self, other: &Self) -> bool { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0) - == ffi::CGDisplayCreateUUIDFromDisplayID(other.0) - } - } -} - -impl Eq for MonitorHandle {} - -impl PartialOrd for MonitorHandle { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for MonitorHandle { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0) - .cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0)) - } - } -} - -impl std::hash::Hash for MonitorHandle { - fn hash(&self, state: &mut H) { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state); - } - } -} - -pub fn available_monitors() -> VecDeque { - if let Ok(displays) = CGDisplay::active_displays() { - let mut monitors = VecDeque::with_capacity(displays.len()); - for display in displays { - monitors.push_back(MonitorHandle(display)); - } - monitors - } else { - VecDeque::with_capacity(0) - } -} - -pub fn primary_monitor() -> MonitorHandle { - MonitorHandle(CGDisplay::main().id) -} - -// `from_point` get a monitor handle which contains the given point. -pub fn from_point(x: f64, y: f64) -> Option { - unsafe { - for monitor in available_monitors() { - let bound = CGDisplayBounds(monitor.0); - if CGRectContainsPoint(bound, CGPoint::new(x, y)) > 0 { - return Some(monitor); - } - } - } - None -} - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Do this using the proper fmt API - #[derive(Debug)] - #[allow(dead_code)] // none of the member reads in Debug::fmt are detected - struct MonitorHandle { - name: Option, - native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - native_identifier: self.native_identifier(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - pub fn new(id: CGDirectDisplayID) -> Self { - MonitorHandle(id) - } - - pub fn name(&self) -> Option { - let MonitorHandle(display_id) = *self; - let screen_num = CGDisplay::new(display_id).model_number(); - Some(format!("Monitor #{}", screen_num)) - } - - #[inline] - pub fn native_identifier(&self) -> u32 { - self.0 - } - - pub fn size(&self) -> PhysicalSize { - let MonitorHandle(display_id) = *self; - let display = CGDisplay::new(display_id); - let height = display.pixels_high(); - let width = display.pixels_wide(); - PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor()) - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; - PhysicalPosition::from_logical::<_, f64>( - (bounds.origin.x as f64, bounds.origin.y as f64), - self.scale_factor(), - ) - } - - pub fn scale_factor(&self) -> f64 { - let screen = match self.ns_screen() { - Some(screen) => screen, - None => return 1.0, // default to 1.0 when we can't find the screen - }; - NSScreen::backingScaleFactor(&screen) as f64 - } - - pub fn video_modes(&self) -> impl Iterator { - let cv_refresh_rate = unsafe { - let mut display_link = std::ptr::null_mut(); - assert_eq!( - ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link), - ffi::kCVReturnSuccess - ); - let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); - ffi::CVDisplayLinkRelease(display_link); - - // This value is indefinite if an invalid display link was specified - assert!(time.flags & ffi::kCVTimeIsIndefinite == 0); - - time.time_scale as i64 / time.time_value - }; - - let monitor = self.clone(); - - unsafe { - let modes = { - let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); - assert!(!array.is_null(), "failed to get list of display modes"); - let array_count = CFArrayGetCount(array); - let modes: Vec<_> = (0..array_count) - .map(move |i| { - let mode = CFArrayGetValueAtIndex(array, i) as *mut _; - ffi::CGDisplayModeRetain(mode); - mode - }) - .collect(); - CFRelease(array as *const _); - modes - }; - - modes.into_iter().map(move |mode| { - let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; - - // CGDisplayModeGetRefreshRate returns 0.0 for any display that - // isn't a CRT - let refresh_rate = if cg_refresh_rate > 0 { - cg_refresh_rate - } else { - cv_refresh_rate - }; - - let pixel_encoding = - CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode)).to_string(); - let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { - 32 - } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { - 16 - } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { - 30 - } else { - unimplemented!() - }; - - let video_mode = VideoMode { - size: ( - ffi::CGDisplayModeGetPixelWidth(mode) as u32, - ffi::CGDisplayModeGetPixelHeight(mode) as u32, - ), - refresh_rate: refresh_rate as u16, - bit_depth, - monitor: monitor.clone(), - native_mode: NativeDisplayMode(mode), - }; - - RootVideoMode { video_mode } - }) - } - } - - pub(crate) fn ns_screen(&self) -> Option> { - // SAFETY: TODO. - let mtm = unsafe { MainThreadMarker::new_unchecked() }; - unsafe { - let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); - let screens = NSScreen::screens(mtm); - let count: NSUInteger = msg_send![&screens, count]; - let key = ns_string!("NSScreenNumber"); - for i in 0..count { - let screen: Retained = msg_send![&screens, objectAtIndex: i as NSUInteger]; - let device_description = NSScreen::deviceDescription(&screen); - let value: id = msg_send![&device_description, objectForKey: &*key]; - if value != nil { - let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; - let other_uuid = - ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); - if uuid == other_uuid { - return Some(screen); - } - } - } - None - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs deleted file mode 100644 index 02524ea7d..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - self, - os::raw::*, - panic::{AssertUnwindSafe, UnwindSafe}, - ptr, - rc::Weak, - time::Instant, -}; - -use crate::platform_impl::platform::{ - app_state::AppState, - event_loop::{stop_app_on_panic, PanicInfo}, - ffi, -}; - -#[link(name = "CoreFoundation", kind = "framework")] -extern "C" { - pub static kCFRunLoopCommonModes: CFRunLoopMode; - - pub fn CFRunLoopGetMain() -> CFRunLoopRef; - pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); - - pub fn CFRunLoopObserverCreate( - allocator: CFAllocatorRef, - activities: CFOptionFlags, - repeats: ffi::Boolean, - order: CFIndex, - callout: CFRunLoopObserverCallBack, - context: *mut CFRunLoopObserverContext, - ) -> CFRunLoopObserverRef; - pub fn CFRunLoopAddObserver( - rl: CFRunLoopRef, - observer: CFRunLoopObserverRef, - mode: CFRunLoopMode, - ); - - pub fn CFRunLoopTimerCreate( - allocator: CFAllocatorRef, - fireDate: CFAbsoluteTime, - interval: CFTimeInterval, - flags: CFOptionFlags, - order: CFIndex, - callout: CFRunLoopTimerCallBack, - context: *mut CFRunLoopTimerContext, - ) -> CFRunLoopTimerRef; - pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode); - pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime); - pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); - - pub fn CFRunLoopSourceCreate( - allocator: CFAllocatorRef, - order: CFIndex, - context: *mut CFRunLoopSourceContext, - ) -> CFRunLoopSourceRef; - pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode); - #[allow(dead_code)] - pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); - pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); - - pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; - pub fn CFRelease(cftype: *const c_void); -} - -pub enum CFAllocator {} -pub type CFAllocatorRef = *mut CFAllocator; -pub enum CFRunLoop {} -pub type CFRunLoopRef = *mut CFRunLoop; -pub type CFRunLoopMode = CFStringRef; -pub enum CFRunLoopObserver {} -pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; -pub enum CFRunLoopTimer {} -pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; -pub enum CFRunLoopSource {} -pub type CFRunLoopSourceRef = *mut CFRunLoopSource; -pub enum CFString {} -pub type CFStringRef = *const CFString; - -pub type CFHashCode = c_ulong; -pub type CFIndex = c_long; -pub type CFOptionFlags = c_ulong; -pub type CFRunLoopActivity = CFOptionFlags; - -pub type CFAbsoluteTime = CFTimeInterval; -pub type CFTimeInterval = f64; - -#[allow(non_upper_case_globals)] -pub const kCFRunLoopEntry: CFRunLoopActivity = 0; -#[allow(non_upper_case_globals)] -pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; -#[allow(non_upper_case_globals)] -pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; -#[allow(non_upper_case_globals)] -pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; - -pub type CFRunLoopObserverCallBack = - extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void); -pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void); - -pub enum CFRunLoopTimerContext {} - -/// This mirrors the struct with the same name from Core Foundation. -/// https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc -#[allow(non_snake_case)] -#[repr(C)] -pub struct CFRunLoopObserverContext { - pub version: CFIndex, - pub info: *mut c_void, - pub retain: Option *const c_void>, - pub release: Option, - pub copyDescription: Option CFStringRef>, -} - -#[allow(non_snake_case)] -#[repr(C)] -pub struct CFRunLoopSourceContext { - pub version: CFIndex, - pub info: *mut c_void, - pub retain: Option *const c_void>, - pub release: Option, - pub copyDescription: Option CFStringRef>, - pub equal: Option ffi::Boolean>, - pub hash: Option CFHashCode>, - pub schedule: Option, - pub cancel: Option, - pub perform: Option, -} - -unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) -where - F: FnOnce(Weak) + UnwindSafe, -{ - let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo); - // Asserting unwind safety on this type should be fine because `PanicInfo` is - // `RefUnwindSafe` and `Rc` is `UnwindSafe` if `T` is `RefUnwindSafe`. - let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw)); - // `from_raw` takes ownership of the data behind the pointer. - // But if this scope takes ownership of the weak pointer, then - // the weak pointer will get free'd at the end of the scope. - // However we want to keep that weak reference around after the function. - std::mem::forget(info_from_raw); - - stop_app_on_panic(Weak::clone(&panic_info), move || { - let _ = &panic_info; - f(panic_info.0) - }); -} - -// begin is queued with the highest priority to ensure it is processed before other observers -extern "C" fn control_flow_begin_handler( - _: CFRunLoopObserverRef, - activity: CFRunLoopActivity, - panic_info: *mut c_void, -) { - unsafe { - control_flow_handler(panic_info, |panic_info| { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopAfterWaiting => { - //trace!("Triggered `CFRunLoopAfterWaiting`"); - AppState::wakeup(panic_info); - //trace!("Completed `CFRunLoopAfterWaiting`"); - } - kCFRunLoopEntry => unimplemented!(), // not expected to ever happen - _ => unreachable!(), - } - }); - } -} - -// end is queued with the lowest priority to ensure it is processed after other observers -// without that, LoopDestroyed would get sent after MainEventsCleared -extern "C" fn control_flow_end_handler( - _: CFRunLoopObserverRef, - activity: CFRunLoopActivity, - panic_info: *mut c_void, -) { - unsafe { - control_flow_handler(panic_info, |panic_info| { - #[allow(non_upper_case_globals)] - match activity { - kCFRunLoopBeforeWaiting => { - //trace!("Triggered `CFRunLoopBeforeWaiting`"); - AppState::cleared(panic_info); - //trace!("Completed `CFRunLoopBeforeWaiting`"); - } - kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen - _ => unreachable!(), - } - }); - } -} - -struct RunLoop(CFRunLoopRef); - -impl RunLoop { - unsafe fn get() -> Self { - RunLoop(CFRunLoopGetMain()) - } - - unsafe fn add_observer( - &self, - flags: CFOptionFlags, - priority: CFIndex, - handler: CFRunLoopObserverCallBack, - context: *mut CFRunLoopObserverContext, - ) { - let observer = CFRunLoopObserverCreate( - ptr::null_mut(), - flags, - ffi::TRUE, // Indicates we want this to run repeatedly - priority, // The lower the value, the sooner this will run - handler, - context, - ); - CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes); - } -} - -pub fn setup_control_flow_observers(panic_info: Weak) { - unsafe { - let mut context = CFRunLoopObserverContext { - info: Weak::into_raw(panic_info) as *mut _, - version: 0, - retain: None, - release: None, - copyDescription: None, - }; - let run_loop = RunLoop::get(); - run_loop.add_observer( - kCFRunLoopEntry | kCFRunLoopAfterWaiting, - CFIndex::MIN, - control_flow_begin_handler, - &mut context as *mut _, - ); - run_loop.add_observer( - kCFRunLoopExit | kCFRunLoopBeforeWaiting, - CFIndex::MAX, - control_flow_end_handler, - &mut context as *mut _, - ); - } -} - -pub struct EventLoopWaker { - timer: CFRunLoopTimerRef, -} - -impl Drop for EventLoopWaker { - fn drop(&mut self) { - unsafe { - CFRunLoopTimerInvalidate(self.timer); - CFRelease(self.timer as _); - } - } -} - -impl Default for EventLoopWaker { - fn default() -> EventLoopWaker { - extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} - unsafe { - // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. - // It is initially setup with a first fire time really far into the - // future, but that gets changed to fire immediately in did_finish_launching - let timer = CFRunLoopTimerCreate( - ptr::null_mut(), - f64::MAX, - 0.000_000_1, - 0, - 0, - wakeup_main_loop, - ptr::null_mut(), - ); - CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); - EventLoopWaker { timer } - } - } -} - -impl EventLoopWaker { - pub fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } - } - - pub fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } - } - - pub fn start_at(&mut self, instant: Instant) { - let now = Instant::now(); - if now >= instant { - self.start(); - } else { - unsafe { - let current = CFAbsoluteTimeGetCurrent(); - let duration = instant - now; - let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64; - CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) - } - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs deleted file mode 100644 index d50015361..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::{ffi::CStr, sync::Once}; - -use objc2::{ - msg_send, - rc::Retained, - runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, -}; -use objc2_foundation::{NSArray, NSInsetRect, NSPoint, NSRect, NSSize}; - -use super::ffi::{id, nil, NO}; -use crate::window::{ProgressBarState, ProgressState}; - -/// Set progress indicator in the Dock. -pub fn set_progress_indicator(progress_state: ProgressBarState) { - unsafe { - let ns_app: id = msg_send![class!(NSApplication), sharedApplication]; - let dock_tile: id = msg_send![ns_app, dockTile]; - if dock_tile == nil { - return; - } - - // check progress indicator is already set or create new one - let progress_indicator: id = get_exist_progress_indicator(dock_tile) - .unwrap_or_else(|| create_progress_indicator(ns_app, dock_tile)); - - // set progress indicator state - if let Some(progress) = progress_state.progress { - let progress = progress.clamp(0, 100) as f64; - let _: () = msg_send![progress_indicator, setDoubleValue: progress]; - let _: () = msg_send![progress_indicator, setHidden: NO]; - } - #[allow(deprecated)] // TODO: Use define_class! - if let Some(state) = progress_state.state { - *(*progress_indicator).get_mut_ivar("state") = state as u8; - let _: () = msg_send![ - progress_indicator, - setHidden: matches!(state, ProgressState::None) - ]; - } - - let _: () = msg_send![dock_tile, display]; - } -} - -fn create_progress_indicator(ns_app: id, dock_tile: id) -> id { - unsafe { - let mut image_view: id = msg_send![dock_tile, contentView]; - if image_view == nil { - // create new dock tile view with current app icon - let app_icon_image: id = msg_send![ns_app, applicationIconImage]; - image_view = msg_send![class!(NSImageView), imageViewWithImage: app_icon_image]; - let _: () = msg_send![dock_tile, setContentView: image_view]; - } - - // create custom progress indicator - let dock_tile_size: NSSize = msg_send![dock_tile, size]; - let frame = NSRect::new( - NSPoint::new(0.0, 0.0), - NSSize::new(dock_tile_size.width, 15.0), - ); - let progress_class = create_progress_indicator_class(); - let progress_indicator: id = msg_send![progress_class, alloc]; - let progress_indicator: id = msg_send![progress_indicator, initWithFrame: frame]; - let _: id = msg_send![progress_indicator, autorelease]; - - // set progress indicator to the dock tile - let _: () = msg_send![image_view, addSubview: progress_indicator]; - - progress_indicator - } -} - -fn get_exist_progress_indicator(dock_tile: id) -> Option { - unsafe { - let content_view: id = msg_send![dock_tile, contentView]; - if content_view == nil { - return None; - } - let subviews: Option> = msg_send![content_view, subviews]; - let subviews = subviews?; - - for idx in 0..subviews.count() { - let subview: id = msg_send![&subviews, objectAtIndex: idx]; - - let is_progress_indicator: bool = - msg_send![subview, isKindOfClass: class!(NSProgressIndicator)]; - if is_progress_indicator { - return Some(subview); - } - } - } - None -} - -fn create_progress_indicator_class() -> *const Class { - static mut APP_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = class!(NSProgressIndicator); - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoProgressIndicator\0").unwrap(), - superclass, - ) - .unwrap(); - - decl.add_method(sel!(drawRect:), draw_progress_bar as extern "C" fn(_, _, _)); - - // progress bar states, follows ProgressState - decl.add_ivar::(CStr::from_bytes_with_nul(b"state\0").unwrap()); - - APP_CLASS = decl.register(); - }); - - unsafe { APP_CLASS } -} - -extern "C" fn draw_progress_bar(this: &Object, _: Sel, rect: NSRect) { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let bar = NSRect::new( - NSPoint { x: 0.0, y: 4.0 }, - NSSize { - width: rect.size.width, - height: 8.0, - }, - ); - let bar_inner = NSInsetRect(bar, 0.5, 0.5); - let mut bar_progress = NSInsetRect(bar, 1.0, 1.0); - - // set progress width - let current_progress: f64 = msg_send![this, doubleValue]; - let normalized_progress: f64 = (current_progress / 100.0).clamp(0.0, 1.0); - bar_progress.size.width *= normalized_progress; - - // draw outer bar - let bg_color: id = msg_send![class!(NSColor), colorWithWhite:1.0 alpha:0.05]; - let _: () = msg_send![bg_color, set]; - draw_rounded_rect(bar); - // draw inner bar - draw_rounded_rect(bar_inner); - - // draw progress - let state: u8 = *(this.get_ivar("state")); - let progress_color: id = match state { - x if x == ProgressState::Paused as u8 => msg_send![class!(NSColor), systemYellowColor], - x if x == ProgressState::Error as u8 => msg_send![class!(NSColor), systemRedColor], - _ => msg_send![class!(NSColor), systemBlueColor], - }; - let _: () = msg_send![progress_color, set]; - draw_rounded_rect(bar_progress); - } -} - -fn draw_rounded_rect(rect: NSRect) { - unsafe { - let raduis = rect.size.height / 2.0; - let bezier_path: id = msg_send![class!(NSBezierPath), bezierPathWithRoundedRect:rect, xRadius:raduis, yRadius:raduis]; - let _: () = msg_send![bezier_path, fill]; - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs deleted file mode 100644 index 28363ffac..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - ops::Deref, - sync::{Mutex, Weak}, -}; - -use core_graphics::base::CGFloat; -use dispatch2::{DispatchQueue, DispatchQueueAttr}; -use objc2::{rc::autoreleasepool, Message}; -use objc2_app_kit::{NSScreen, NSView, NSWindow, NSWindowStyleMask}; -use objc2_foundation::{MainThreadMarker, NSPoint, NSSize, NSString}; - -use crate::{ - dpi::LogicalSize, - platform_impl::platform::{ - ffi::{self, id, NO, YES}, - window::SharedState, - }, -}; - -pub fn is_main_thread() -> bool { - unsafe { msg_send!(class!(NSThread), isMainThread) } -} - -// Unsafe wrapper type that allows us to dispatch things that aren't Send. -// This should *only* be used to dispatch to the main queue. -// While it is indeed not guaranteed that these types can safely be sent to -// other threads, we know that they're safe to use on the main thread. -struct MainThreadSafe(T); - -unsafe impl Send for MainThreadSafe {} - -impl Deref for MainThreadSafe { - type Target = T; - fn deref(&self) -> &T { - &self.0 - } -} - -fn run_on_main(f: F) { - if is_main_thread() { - f(); - } else { - DispatchQueue::main().exec_sync(f); - } -} - -unsafe fn set_style_mask(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) { - ns_window.setStyleMask(mask); - // If we don't do this, key handling will break - // (at least until the window is clicked again/etc.) - ns_window.makeFirstResponder(Some(ns_view)); -} - -// Always use this function instead of trying to modify `styleMask` directly! -// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. -// Otherwise, this would vomit out errors about not being on the main thread -// and fail to do anything. -pub unsafe fn set_style_mask_async( - ns_window: &NSWindow, - ns_view: &NSView, - mask: NSWindowStyleMask, -) { - let ns_window = MainThreadSafe(ns_window.retain()); - let ns_view = MainThreadSafe(ns_view.retain()); - DispatchQueue::main().exec_async(move || { - set_style_mask(&ns_window, &ns_view, mask); - }); -} -pub unsafe fn set_style_mask_sync(ns_window: &NSWindow, ns_view: &NSView, mask: NSWindowStyleMask) { - if is_main_thread() { - set_style_mask(ns_window, ns_view, mask); - } else { - let ns_window = MainThreadSafe(ns_window.retain()); - let ns_view = MainThreadSafe(ns_view.retain()); - DispatchQueue::main().exec_sync(move || { - set_style_mask(&ns_window, &ns_view, mask); - }) - } -} - -// `setContentSize:` isn't thread-safe either, though it doesn't log any errors -// and just fails silently. Anyway, GCD to the rescue! -pub unsafe fn set_content_size_async(ns_window: &NSWindow, size: LogicalSize) { - let ns_window = MainThreadSafe(ns_window.retain()); - DispatchQueue::main().exec_async(move || { - ns_window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); - }); -} - -// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy -// to log errors. -pub unsafe fn set_frame_top_left_point_async(ns_window: &NSWindow, point: NSPoint) { - let ns_window = MainThreadSafe(ns_window.retain()); - DispatchQueue::main().exec_async(move || { - ns_window.setFrameTopLeftPoint(point); - }); -} - -// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. -pub unsafe fn set_level_async(ns_window: &NSWindow, level: ffi::NSWindowLevel) { - let ns_window = MainThreadSafe(ns_window.retain()); - DispatchQueue::main().exec_async(move || { - ns_window.setLevel(level as _); - }); -} - -// `toggleFullScreen` is thread-safe, but our additional logic to account for -// window styles isn't. -pub unsafe fn toggle_full_screen_async( - ns_window: &NSWindow, - ns_view: &NSView, - not_fullscreen: bool, - shared_state: Weak>, -) { - let ns_window = MainThreadSafe(ns_window.retain()); - let ns_view = MainThreadSafe(ns_view.retain()); - let shared_state = MainThreadSafe(shared_state); - DispatchQueue::main().exec_async(move || { - // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we - // set a normal style temporarily. The previous state will be - // restored in `WindowDelegate::window_did_exit_fullscreen`. - if not_fullscreen { - let curr_mask = ns_window.styleMask(); - let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; - if !curr_mask.contains(required) { - set_style_mask(&ns_window, &ns_view, required); - if let Some(shared_state) = shared_state.upgrade() { - trace!("Locked shared state in `toggle_full_screen_callback`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - shared_state_lock.saved_style = Some(curr_mask); - trace!("Unlocked shared state in `toggle_full_screen_callback`"); - } - } - } - // Window level must be restored from `CGShieldingWindowLevel() - // + 1` back to normal in order for `toggleFullScreen` to do - // anything - ns_window.setLevel(0); - ns_window.toggleFullScreen(None); - }); -} - -pub unsafe fn restore_display_mode_async(ns_screen: u32) { - DispatchQueue::main().exec_async(move || { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); - }); -} - -// `setMaximized` is not thread-safe -pub unsafe fn set_maximized_async( - ns_window: &NSWindow, - is_zoomed: bool, - maximized: bool, - shared_state: Weak>, -) { - let ns_window = MainThreadSafe(ns_window.retain()); - let shared_state = MainThreadSafe(shared_state); - DispatchQueue::main().exec_async(move || { - if let Some(shared_state) = shared_state.upgrade() { - trace!("Locked shared state in `set_maximized`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - - // Save the standard frame sized if it is not zoomed - if !is_zoomed { - shared_state_lock.standard_frame = Some(NSWindow::frame(&ns_window)); - } - - shared_state_lock.maximized = maximized; - - let curr_mask = ns_window.styleMask(); - if shared_state_lock.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } else if curr_mask.contains(NSWindowStyleMask::Resizable) - && curr_mask.contains(NSWindowStyleMask::Titled) - { - // Just use the native zoom if resizable - ns_window.zoom(None); - } else { - // if it's not resizable, we set the frame directly - let new_rect = if maximized { - let mtm = MainThreadMarker::new_unchecked(); - let screen = NSScreen::mainScreen(mtm).unwrap(); - NSScreen::visibleFrame(&screen) - } else { - shared_state_lock.saved_standard_frame() - }; - let _: () = msg_send![&*ns_window, setFrame:new_rect, display:NO, animate: YES]; - } - - trace!("Unlocked shared state in `set_maximized`"); - } - }); -} - -// `orderOut:` isn't thread-safe. Calling it from another thread actually works, -// but with an odd delay. -pub unsafe fn order_out_sync(ns_window: &NSWindow) { - let ns_window = MainThreadSafe(ns_window.retain()); - run_on_main(move || { - ns_window.orderOut(None); - }); -} - -// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread -// actually works, but with an odd delay. -pub unsafe fn make_key_and_order_front_sync(ns_window: &NSWindow) { - let ns_window = MainThreadSafe(ns_window.retain()); - run_on_main(move || { - ns_window.makeKeyAndOrderFront(None); - }); -} - -// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the -// window drag regions, which throws an exception when not done in the main -// thread -pub unsafe fn set_title_async(ns_window: &NSWindow, title: String) { - let ns_window = MainThreadSafe(ns_window.retain()); - DispatchQueue::main().exec_async(move || { - let title = NSString::from_str(&title); - ns_window.setTitle(&title); - }); -} - -// `setFocus:` isn't thread-safe. -pub unsafe fn set_focus(ns_window: &NSWindow) { - let ns_window = MainThreadSafe(ns_window.retain()); - run_on_main(move || { - ns_window.makeKeyAndOrderFront(None); - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let () = msg_send![app, activateIgnoringOtherApps: YES]; - }); -} - -// `close:` is thread-safe, but we want the event to be triggered from the main -// thread. Though, it's a good idea to look into that more... -pub unsafe fn close_async(ns_window: &NSWindow) { - let ns_window = MainThreadSafe(ns_window.retain()); - run_on_main(move || { - autoreleasepool(move |_| { - ns_window.close(); - }); - }); -} - -// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently. -pub unsafe fn set_ignore_mouse_events(ns_window: &NSWindow, ignore: bool) { - let ns_window = MainThreadSafe(ns_window.retain()); - DispatchQueue::main().exec_async(move || { - ns_window.setIgnoresMouseEvents(ignore); - }); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs deleted file mode 100644 index 4ecdce5ae..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::platform_impl::platform::ffi::{id, nil, NO}; -use objc2::{ - msg_send, - rc::Retained, - runtime::{AnyObject, Sel}, - AllocAnyThread, -}; -use objc2_app_kit::NSImage; -use objc2_foundation::{ns_string, NSDictionary, NSPoint, NSString}; -use std::{ - cell::RefCell, - ffi::{c_void, CString}, - ptr::null_mut, -}; - -use crate::window::CursorIcon; - -#[derive(Default)] -pub enum Cursor { - #[default] - Default, - Native(&'static str), - Undocumented(&'static str), - WebKit(&'static str), -} - -impl From for Cursor { - fn from(cursor: CursorIcon) -> Self { - // See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc. - match cursor { - CursorIcon::Default => Cursor::Default, - CursorIcon::Arrow => Cursor::Native("arrowCursor"), - CursorIcon::Hand => Cursor::Native("pointingHandCursor"), - CursorIcon::Grab => Cursor::Native("openHandCursor"), - CursorIcon::Grabbing => Cursor::Native("closedHandCursor"), - CursorIcon::Text => Cursor::Native("IBeamCursor"), - CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), - CursorIcon::Copy => Cursor::Native("dragCopyCursor"), - CursorIcon::Alias => Cursor::Native("dragLinkCursor"), - CursorIcon::NotAllowed | CursorIcon::NoDrop => Cursor::Native("operationNotAllowedCursor"), - CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"), - CursorIcon::Crosshair => Cursor::Native("crosshairCursor"), - CursorIcon::EResize => Cursor::Native("resizeRightCursor"), - CursorIcon::NResize => Cursor::Native("resizeUpCursor"), - CursorIcon::WResize => Cursor::Native("resizeLeftCursor"), - CursorIcon::SResize => Cursor::Native("resizeDownCursor"), - CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"), - CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"), - - // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 - CursorIcon::Help => Cursor::Undocumented("_helpCursor"), - CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"), - CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"), - CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"), - CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"), - CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"), - CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"), - CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"), - CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"), - - // While these are available, the former just loads a white arrow, - // and the latter loads an ugly deflated beachball! - // CursorIcon::Move => Cursor::Undocumented("_moveCursor"), - // CursorIcon::Wait => Cursor::Undocumented("_waitCursor"), - - // An even more undocumented cursor... - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 - // This is the wrong semantics for `Wait`, but it's the same as - // what's used in Safari and Chrome. - CursorIcon::Wait | CursorIcon::Progress => Cursor::Undocumented("busyButClickableCursor"), - - // For the rest, we can just snatch the cursors from WebKit... - // They fit the style of the native cursors, and will seem - // completely standard to macOS users. - // https://stackoverflow.com/a/21786835/5435443 - CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"), - CursorIcon::Cell => Cursor::WebKit("cell"), - } - } -} - -impl Cursor { - pub unsafe fn load(&self) -> id { - match self { - Cursor::Default => null_mut(), - Cursor::Native(cursor_name) => { - let sel = Sel::register(&CString::new(*cursor_name).unwrap()); - msg_send![class!(NSCursor), performSelector: sel] - } - Cursor::Undocumented(cursor_name) => { - let class = class!(NSCursor); - let sel = Sel::register(&CString::new(*cursor_name).unwrap()); - let sel = if msg_send![class, respondsToSelector: sel] { - sel - } else { - warn!("Cursor `{}` appears to be invalid", cursor_name); - sel!(arrowCursor) - }; - msg_send![class, performSelector: sel] - } - Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name), - } - } -} - -// Note that loading `busybutclickable` with this code won't animate the frames; -// instead you'll just get them all in a column. -pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { - const CURSOR_ROOT: &str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"; - let cursor_root = ns_string!(CURSOR_ROOT); - let cursor_name = NSString::from_str(cursor_name); - let cursor_pdf = ns_string!("cursor.pdf"); - let cursor_plist = ns_string!("info.plist"); - let key_x = ns_string!("hotx"); - let key_y = ns_string!("hoty"); - - let cursor_path: Retained = - msg_send![cursor_root, stringByAppendingPathComponent: &*cursor_name]; - let pdf_path: Retained = - msg_send![&cursor_path, stringByAppendingPathComponent: &*cursor_pdf]; - let info_path: Retained = - msg_send![&cursor_path, stringByAppendingPathComponent: &*cursor_plist]; - - let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap(); - #[allow(deprecated)] - let info = - NSDictionary::::dictionaryWithContentsOfFile(&info_path).unwrap(); - let x = info.objectForKey(&key_x).unwrap(); - let y = info.objectForKey(&key_y).unwrap(); - let point = NSPoint::new(msg_send![&x, doubleValue], msg_send![&y, doubleValue]); - let cursor: id = msg_send![class!(NSCursor), alloc]; - msg_send![ - cursor, - initWithImage:&*image, - hotSpot:point, - ] -} - -pub unsafe fn invisible_cursor() -> id { - // 16x16 GIF data for invisible cursor - // You can reproduce this via ImageMagick. - // $ convert -size 16x16 xc:none cursor.gif - static CURSOR_BYTES: &[u8] = &[ - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, 0xA3, 0x9C, 0xB4, - 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, - ]; - - thread_local! { - // We can't initialize this at startup. - static CURSOR_OBJECT: RefCell = const { RefCell::new(nil) }; - } - - CURSOR_OBJECT.with(|cursor_obj| { - if *cursor_obj.borrow() == nil { - // Create a cursor from `CURSOR_BYTES` - let cursor_data: id = msg_send![ - class!(NSData), - dataWithBytesNoCopy:CURSOR_BYTES.as_ptr().cast::(), - length:CURSOR_BYTES.len(), - freeWhenDone:NO, - ]; - - let ns_image: id = msg_send![class!(NSImage), alloc]; - let _: id = msg_send![ns_image, initWithData: cursor_data]; - let cursor: id = msg_send![class!(NSCursor), alloc]; - *cursor_obj.borrow_mut() = - msg_send![cursor, initWithImage:ns_image, hotSpot: NSPoint::new(0.0, 0.0)]; - } - *cursor_obj.borrow() - }) -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs deleted file mode 100644 index b9a39147e..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -mod r#async; -mod cursor; - -pub use self::{cursor::*, r#async::*}; - -use std::ops::{BitAnd, Deref}; - -use core_graphics::display::CGDisplay; -use objc2::{ - class, - runtime::{AnyClass as Class, AnyObject as Object, Sel}, -}; -use objc2_app_kit::{NSApp, NSView, NSWindow, NSWindowStyleMask}; -use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSPoint, NSRange, NSRect, NSUInteger}; - -use crate::{ - dpi::{LogicalPosition, PhysicalPosition}, - error::ExternalError, - platform_impl::platform::ffi::{self, id, nil, BOOL, YES}, -}; - -// Replace with `!` once stable -#[derive(Debug)] -pub enum Never {} - -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} - -pub const EMPTY_RANGE: NSRange = NSRange { - location: ffi::NSNotFound as NSUInteger, - length: 0, -}; - -#[derive(Debug, PartialEq)] -pub struct IdRef(id); - -impl IdRef { - pub fn new(inner: id) -> IdRef { - IdRef(inner) - } - - #[allow(dead_code)] - pub fn retain(inner: id) -> IdRef { - if inner != nil { - let _: id = unsafe { msg_send![inner, retain] }; - } - IdRef(inner) - } -} - -impl Drop for IdRef { - fn drop(&mut self) { - if self.0 != nil { - unsafe { - let _pool = NSAutoreleasePool::new(); - let () = msg_send![self.0, release]; - }; - } - } -} - -impl Deref for IdRef { - type Target = id; - #[allow(clippy::needless_lifetimes)] - fn deref<'a>(&'a self) -> &'a id { - &self.0 - } -} - -impl Clone for IdRef { - fn clone(&self) -> IdRef { - IdRef::retain(self.0) - } -} - -// For consistency with other platforms, this will... -// 1. translate the bottom-left window corner into the top-left window corner -// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one -pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { - CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) -} - -/// Converts from tao screen-coordinates to macOS screen-coordinates. -/// Tao: top-left is (0, 0) and y increasing downwards -/// macOS: bottom-left is (0, 0) and y increasing upwards -pub fn window_position(position: LogicalPosition) -> NSPoint { - NSPoint::new( - position.x, - CGDisplay::main().pixels_high() as f64 - position.y, - ) -} - -pub fn cursor_position() -> Result, ExternalError> { - let point: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] }; - let y = CGDisplay::main().pixels_high() as f64 - point.y; - let point = LogicalPosition::new(point.x, y); - Ok(point.to_physical(super::monitor::primary_monitor().scale_factor())) -} - -pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { - let superclass: *const Class = msg_send![this, superclass]; - &*superclass -} - -pub unsafe fn create_input_context(view: &NSView) -> IdRef { - let input_context: id = msg_send![class!(NSTextInputContext), alloc]; - let input_context: id = msg_send![input_context, initWithClient: view]; - IdRef::new(input_context) -} - -#[allow(dead_code)] -pub unsafe fn open_emoji_picker() { - // SAFETY: TODO - let mtm = unsafe { MainThreadMarker::new_unchecked() }; - let () = msg_send![&NSApp(mtm), orderFrontCharacterPalette: nil]; -} - -pub extern "C" fn yes(_: &Object, _: Sel) -> BOOL { - YES -} - -pub unsafe fn toggle_style_mask( - window: &NSWindow, - view: &NSView, - mask: NSWindowStyleMask, - on: bool, -) { - let current_style_mask = window.styleMask(); - if on { - window.setStyleMask(current_style_mask | mask); - } else { - window.setStyleMask(current_style_mask & (!mask)); - } - - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! - window.makeFirstResponder(Some(view)); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs deleted file mode 100644 index dfeba9414..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs +++ /dev/null @@ -1,1182 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -#![allow(unused_unsafe)] -#![allow(deprecated)] // TODO: Use define_class! - -use std::{ - boxed::Box, - collections::{HashSet, VecDeque}, - ffi::CStr, - os::raw::*, - ptr, - sync::{Arc, Mutex, Weak}, -}; - -use objc2::{ - msg_send, - rc::Retained, - runtime::{ - AnyClass as Class, AnyObject as Object, AnyProtocol as Protocol, ClassBuilder as ClassDecl, Sel, - }, - AllocAnyThread, -}; -use objc2_app_kit::{ - NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow, NSWindowButton, -}; -use objc2_foundation::{ - ns_string, MainThreadMarker, NSAttributedString, NSInteger, NSMutableAttributedString, NSPoint, - NSRange, NSRect, NSSize, NSString, NSUInteger, -}; -use once_cell::sync::Lazy; - -use crate::{ - dpi::LogicalPosition, - event::{ - DeviceEvent, ElementState, Event, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, - }, - keyboard::{KeyCode, ModifiersState}, - platform_impl::platform::{ - app_state::AppState, - event::{code_to_key, create_key_event, event_mods, get_scancode, EventWrapper}, - ffi::*, - util::{self}, - window::get_window_id, - DEVICE_ID, - }, - window::WindowId, -}; - -pub struct CursorState { - pub visible: bool, - pub cursor: util::Cursor, -} - -impl Default for CursorState { - fn default() -> Self { - Self { - visible: true, - cursor: Default::default(), - } - } -} - -pub(super) struct ViewState { - ns_window: objc2::rc::Weak, - pub cursor_state: Arc>, - ime_spot: Option<(f64, f64)>, - - /// This is true when we are currently modifying a marked text - /// using ime. When the text gets commited, this is set to false. - in_ime_preedit: bool, - - /// This is used to detect if a key-press causes an ime event. - /// If a key-press does not cause an ime event, that means - /// that the key-press cancelled the ime session. (Except arrow keys) - key_triggered_ime: bool, - // Not Needed Anymore - //raw_characters: Option, - is_key_down: bool, - pub(super) modifiers: ModifiersState, - phys_modifiers: HashSet, - tracking_rect: Option, - pub(super) traffic_light_inset: Option>, -} - -impl ViewState { - fn get_scale_factor(&self) -> f64 { - (unsafe { NSWindow::backingScaleFactor(&self.ns_window.load().unwrap()) }) as f64 - } -} - -pub fn new_view(ns_window: &NSWindow) -> (Option>, Weak>) { - let cursor_state = Default::default(); - let cursor_access = Arc::downgrade(&cursor_state); - let state = ViewState { - ns_window: objc2::rc::Weak::from(ns_window), - cursor_state, - ime_spot: None, - in_ime_preedit: false, - key_triggered_ime: false, - is_key_down: false, - modifiers: Default::default(), - phys_modifiers: Default::default(), - tracking_rect: None, - traffic_light_inset: None, - }; - unsafe { - // This is free'd in `dealloc` - let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - let ns_view = msg_send![VIEW_CLASS.0, alloc]; - (msg_send![ns_view, initWithTao: state_ptr], cursor_access) - } -} - -pub unsafe fn set_ime_position(ns_view: &NSView, input_context: id, x: f64, y: f64) { - let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - let content_rect = NSWindow::contentRectForFrameRect( - &state.ns_window.load().unwrap(), - NSWindow::frame(&state.ns_window.load().unwrap()), - ); - let base_x = content_rect.origin.x as f64; - let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - state.ime_spot = Some((base_x + x, base_y - y)); - let _: () = msg_send![input_context, invalidateCharacterCoordinates]; -} - -fn is_arrow_key(keycode: KeyCode) -> bool { - matches!( - keycode, - KeyCode::ArrowUp | KeyCode::ArrowDown | KeyCode::ArrowLeft | KeyCode::ArrowRight - ) -} - -struct ViewClass(&'static Class); -unsafe impl Send for ViewClass {} -unsafe impl Sync for ViewClass {} - -static VIEW_CLASS: Lazy = Lazy::new(|| unsafe { - let superclass = class!(NSView); - let mut decl = - ClassDecl::new(CStr::from_bytes_with_nul(b"TaoView\0").unwrap(), superclass).unwrap(); - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); - decl.add_method( - sel!(initWithTao:), - init_with_tao as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(viewDidMoveToWindow), - view_did_move_to_window as extern "C" fn(_, _), - ); - decl.add_method(sel!(drawRect:), draw_rect as extern "C" fn(_, _, _)); - decl.add_method( - sel!(acceptsFirstResponder), - accepts_first_responder as extern "C" fn(_, _) -> _, - ); - decl.add_method(sel!(touchBar), touch_bar as extern "C" fn(_, _) -> _); - decl.add_method( - sel!(resetCursorRects), - reset_cursor_rects as extern "C" fn(_, _), - ); - decl.add_method( - sel!(hasMarkedText), - has_marked_text as extern "C" fn(_, _) -> _, - ); - decl.add_method(sel!(markedRange), marked_range as extern "C" fn(_, _) -> _); - decl.add_method( - sel!(selectedRange), - selected_range as extern "C" fn(_, _) -> _, - ); - decl.add_method( - sel!(setMarkedText:selectedRange:replacementRange:), - set_marked_text as extern "C" fn(_, _, _, _, _), - ); - decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(_, _)); - decl.add_method( - sel!(validAttributesForMarkedText), - valid_attributes_for_marked_text as extern "C" fn(_, _) -> _, - ); - decl.add_method( - sel!(attributedSubstringForProposedRange:actualRange:), - attributed_substring_for_proposed_range as extern "C" fn(_, _, _, _) -> _, - ); - decl.add_method( - sel!(insertText:replacementRange:), - insert_text as extern "C" fn(_, _, _, _), - ); - decl.add_method( - sel!(characterIndexForPoint:), - character_index_for_point as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(firstRectForCharacterRange:actualRange:), - first_rect_for_character_range as extern "C" fn(_, _, _, _) -> _, - ); - decl.add_method( - sel!(doCommandBySelector:), - do_command_by_selector as extern "C" fn(_, _, _), - ); - decl.add_method(sel!(keyDown:), key_down as extern "C" fn(_, _, _)); - decl.add_method(sel!(keyUp:), key_up as extern "C" fn(_, _, _)); - decl.add_method(sel!(flagsChanged:), flags_changed as extern "C" fn(_, _, _)); - decl.add_method(sel!(insertTab:), insert_tab as extern "C" fn(_, _, _)); - decl.add_method( - sel!(insertBackTab:), - insert_back_tab as extern "C" fn(_, _, _), - ); - decl.add_method(sel!(mouseDown:), mouse_down as extern "C" fn(_, _, _)); - decl.add_method(sel!(mouseUp:), mouse_up as extern "C" fn(_, _, _)); - decl.add_method( - sel!(rightMouseDown:), - right_mouse_down as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(rightMouseUp:), - right_mouse_up as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(otherMouseDown:), - other_mouse_down as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(otherMouseUp:), - other_mouse_up as extern "C" fn(_, _, _), - ); - decl.add_method(sel!(mouseMoved:), mouse_moved as extern "C" fn(_, _, _)); - decl.add_method(sel!(mouseDragged:), mouse_dragged as extern "C" fn(_, _, _)); - decl.add_method( - sel!(rightMouseDragged:), - right_mouse_dragged as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(otherMouseDragged:), - other_mouse_dragged as extern "C" fn(_, _, _), - ); - decl.add_method(sel!(mouseEntered:), mouse_entered as extern "C" fn(_, _, _)); - decl.add_method(sel!(mouseExited:), mouse_exited as extern "C" fn(_, _, _)); - decl.add_method(sel!(scrollWheel:), scroll_wheel as extern "C" fn(_, _, _)); - decl.add_method( - sel!(pressureChangeWithEvent:), - pressure_change_with_event as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(_wantsKeyDownForEvent:), - wants_key_down_for_event as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(cancelOperation:), - cancel_operation as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(frameDidChange:), - frame_did_change as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(acceptsFirstMouse:), - accepts_first_mouse as extern "C" fn(_, _, _) -> _, - ); - decl.add_ivar::<*mut c_void>(CStr::from_bytes_with_nul(b"taoState\0").unwrap()); - decl.add_ivar::(CStr::from_bytes_with_nul(b"markedText\0").unwrap()); - let protocol = Protocol::get(CStr::from_bytes_with_nul(b"NSTextInputClient\0").unwrap()).unwrap(); - decl.add_protocol(protocol); - ViewClass(decl.register()) -}); - -extern "C" fn dealloc(this: &Object, _sel: Sel) { - unsafe { - let state: *mut c_void = *this.get_ivar("taoState"); - let marked_text: *mut NSMutableAttributedString = *this.get_ivar("markedText"); - let _: () = msg_send![marked_text, release]; - drop(Box::from_raw(state as *mut ViewState)); - } -} - -extern "C" fn init_with_tao(this: &Object, _sel: Sel, state: *mut c_void) -> id { - unsafe { - let this: id = msg_send![this, init]; - if this != nil { - *(*this).get_mut_ivar("taoState") = state; - let marked_text = Retained::into_raw(NSMutableAttributedString::new()); - *(*this).get_mut_ivar("markedText") = marked_text; - let _: () = msg_send![this, setPostsFrameChangedNotifications: YES]; - - let notification_center: &Object = msg_send![class!(NSNotificationCenter), defaultCenter]; - let notification_name = ns_string!("NSViewFrameDidChangeNotification"); - let _: () = msg_send![ - notification_center, - addObserver: this - selector: sel!(frameDidChange:) - name: &*notification_name - object: this - ]; - } - this - } -} - -extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) { - trace!("Triggered `viewDidMoveToWindow`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![this, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![this, visibleRect]; - let tracking_rect: NSInteger = msg_send![this, - addTrackingRect:rect - owner:this - userData:ptr::null_mut::() - assumeInside:NO - ]; - state.tracking_rect = Some(tracking_rect); - } - trace!("Completed `viewDidMoveToWindow`"); -} - -extern "C" fn frame_did_change(this: &Object, _sel: Sel, _event: id) { - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(tracking_rect) = state.tracking_rect.take() { - let _: () = msg_send![this, removeTrackingRect: tracking_rect]; - } - - let rect: NSRect = msg_send![this, visibleRect]; - let tracking_rect: NSInteger = msg_send![this, - addTrackingRect:rect - owner:this - userData:ptr::null_mut::() - assumeInside:NO - ]; - - state.tracking_rect = Some(tracking_rect); - } -} - -extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - if let Some(position) = state.traffic_light_inset { - let window = state.ns_window.load().unwrap(); - inset_traffic_lights(&window, position); - } - - AppState::handle_redraw(WindowId(get_window_id(&state.ns_window.load().unwrap()))); - - let superclass = util::superclass(this); - let () = msg_send![super(this, superclass), drawRect: rect]; - } -} - -extern "C" fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { - YES -} - -// This is necessary to prevent a beefy terminal error on MacBook Pros: -// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem -// TODO: Add an API extension for using `NSTouchBar` -extern "C" fn touch_bar(_this: &Object, _sel: Sel) -> id { - nil -} - -extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let bounds: NSRect = msg_send![this, bounds]; - let cursor_state = state.cursor_state.lock().unwrap(); - let cursor = if cursor_state.visible { - cursor_state.cursor.load() - } else { - util::invisible_cursor() - }; - - if !cursor.is_null() { - let _: () = msg_send![this, - addCursorRect:bounds - cursor:cursor - ]; - } - } -} - -extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { - unsafe { - trace!("Triggered `hasMarkedText`"); - let marked_text: &NSMutableAttributedString = *this.get_ivar("markedText"); - trace!("Completed `hasMarkedText`"); - (marked_text.length() > 0).into() - } -} - -extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange { - unsafe { - trace!("Triggered `markedRange`"); - let marked_text: &NSMutableAttributedString = *this.get_ivar("markedText"); - let length = marked_text.length(); - trace!("Completed `markedRange`"); - if length > 0 { - NSRange::new(0, length - 1) - } else { - util::EMPTY_RANGE - } - } -} - -extern "C" fn selected_range(_this: &Object, _sel: Sel) -> NSRange { - trace!("Triggered `selectedRange`"); - trace!("Completed `selectedRange`"); - util::EMPTY_RANGE -} - -/// An IME pre-edit operation happened, changing the text that's -/// currently being pre-edited. This more or less corresponds to -/// the `compositionupdate` event on the web. -extern "C" fn set_marked_text( - this: &mut Object, - _sel: Sel, - string: id, - _selected_range: NSRange, - _replacement_range: NSRange, -) { - trace!("Triggered `setMarkedText`"); - unsafe { - let has_attr: bool = msg_send![string, isKindOfClass: class!(NSAttributedString)]; - let marked_text = if has_attr { - NSMutableAttributedString::initWithAttributedString( - NSMutableAttributedString::alloc(), - &*(string as *const NSAttributedString), - ) - } else { - NSMutableAttributedString::initWithString( - NSMutableAttributedString::alloc(), - &*(string as *const NSString), - ) - }; - let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); - let () = msg_send![(*marked_text_ref), release]; - *marked_text_ref = Retained::into_raw(marked_text); - - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - state.in_ime_preedit = true; - state.key_triggered_ime = true; - } - trace!("Completed `setMarkedText`"); -} - -extern "C" fn unmark_text(this: &mut Object, _sel: Sel) { - trace!("Triggered `unmarkText`"); - unsafe { - let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); - let () = msg_send![(*marked_text_ref), release]; - *marked_text_ref = Retained::into_raw(NSMutableAttributedString::new()); - let input_context: id = msg_send![this, inputContext]; - let _: () = msg_send![input_context, discardMarkedText]; - } - trace!("Completed `unmarkText`"); -} - -extern "C" fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id { - trace!("Triggered `validAttributesForMarkedText`"); - trace!("Completed `validAttributesForMarkedText`"); - unsafe { msg_send![class!(NSArray), array] } -} - -extern "C" fn attributed_substring_for_proposed_range( - _this: &Object, - _sel: Sel, - _range: NSRange, - _actual_range: *mut c_void, // *mut NSRange -) -> id { - trace!("Triggered `attributedSubstringForProposedRange`"); - trace!("Completed `attributedSubstringForProposedRange`"); - nil -} - -extern "C" fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger { - trace!("Triggered `characterIndexForPoint`"); - trace!("Completed `characterIndexForPoint`"); - 0 -} - -extern "C" fn first_rect_for_character_range( - this: &Object, - _sel: Sel, - _range: NSRange, - _actual_range: *mut c_void, // *mut NSRange -) -> NSRect { - unsafe { - trace!("Triggered `firstRectForCharacterRange`"); - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - let (x, y) = state.ime_spot.unwrap_or_else(|| { - let content_rect = NSWindow::contentRectForFrameRect( - &state.ns_window.load().unwrap(), - NSWindow::frame(&state.ns_window.load().unwrap()), - ); - let x = content_rect.origin.x; - let y = util::bottom_left_to_top_left(content_rect); - (x, y) - }); - trace!("Completed `firstRectForCharacterRange`"); - NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0)) - } -} - -extern "C" fn insert_text( - this: &Object, - _sel: Sel, - string: &NSString, - _replacement_range: NSRange, -) { - trace!("Triggered `insertText`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let has_attr: bool = msg_send![string, isKindOfClass: class!(NSAttributedString)]; - let characters = if has_attr { - // This is a *mut NSAttributedString - msg_send![string, string] - } else { - // This is already a *mut NSString - string - }; - - let string: String = characters - .to_string() - .chars() - .filter(|c| !is_corporate_character(*c)) - .collect(); - state.is_key_down = true; - - // We don't need this now, but it's here if that changes. - //let event: id = msg_send![NSApp(), currentEvent]; - - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::ReceivedImeText(string), - })); - if state.in_ime_preedit { - state.in_ime_preedit = false; - state.key_triggered_ime = true; - } - } - trace!("Completed `insertText`"); -} - -extern "C" fn do_command_by_selector(_this: &Object, _sel: Sel, _command: Sel) { - trace!("Triggered `doCommandBySelector`"); - // TODO: (Artur) all these inputs seem to trigger a key event with the correct text - // content so this is not needed anymore, it seems. - - // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character - // happens, i.e. newlines, tabs, and Ctrl+C. - - // unsafe { - // let state_ptr: *mut c_void = *this.get_ivar("taoState"); - // let state = &mut *(state_ptr as *mut ViewState); - - // let mut events = VecDeque::with_capacity(1); - // if command == sel!(insertNewline:) { - // // The `else` condition would emit the same character, but I'm keeping this here both... - // // 1) as a reminder for how `doCommandBySelector` works - // // 2) to make our use of carriage return explicit - // events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { - // window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - // event: WindowEvent::ReceivedCharacter('\r'), - // })); - // } else { - // let raw_characters = state.raw_characters.take(); - // if let Some(raw_characters) = raw_characters { - // for character in raw_characters - // .chars() - // .filter(|c| !is_corporate_character(*c)) - // { - // events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { - // window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - // event: WindowEvent::ReceivedCharacter(character), - // })); - // } - // } - // }; - // AppState::queue_events(events); - // } - - trace!("Completed `doCommandBySelector`"); -} - -// Get characters -// fn get_characters(event: id, ignore_modifiers: bool) -> String { -// unsafe { -// let characters: id = if ignore_modifiers { -// msg_send![event, charactersIgnoringModifiers] -// } else { -// msg_send![event, characters] -// }; - -// assert_ne!(characters, nil); -// let slice = slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len()); - -// let string = str::from_utf8_unchecked(slice); -// string.to_owned() -// } -// } - -// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT -fn is_corporate_character(c: char) -> bool { - #[allow(clippy::match_like_matches_macro)] - match c { - '\u{F700}'..='\u{F747}' - | '\u{F802}'..='\u{F84F}' - | '\u{F850}' - | '\u{F85C}' - | '\u{F85D}' - | '\u{F85F}' - | '\u{F860}'..='\u{F86B}' - | '\u{F870}'..='\u{F8FF}' => true, - _ => false, - } -} - -// // Retrieves a layout-independent keycode given an event. -// fn retrieve_keycode(event: id) -> Option { -// #[inline] -// fn get_code(ev: id, raw: bool) -> Option { -// let characters = get_characters(ev, raw); -// characters.chars().next().and_then(|c| char_to_keycode(c)) -// } - -// // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. -// // If we don't get a match, then we fall back to unmodified characters. -// let code = get_code(event, false).or_else(|| get_code(event, true)); - -// // We've checked all layout related keys, so fall through to scancode. -// // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). -// // -// // We're additionally checking here for F21-F24 keys, since their keycode -// // can vary, but we know that they are encoded -// // in characters property. -// code.or_else(|| { -// let scancode = get_scancode(event); -// scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true))) -// }) -// } - -// Update `state.modifiers` if `event` has something different -fn update_potentially_stale_modifiers(state: &mut ViewState, event: &NSEvent) { - let event_modifiers = event_mods(event); - if state.modifiers != event_modifiers { - state.modifiers = event_modifiers; - - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::ModifiersChanged(state.modifiers), - })); - } -} - -extern "C" fn key_down(this: &mut Object, _sel: Sel, event: &NSEvent) { - trace!("Triggered `keyDown`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - let window_id = WindowId(get_window_id(&state.ns_window.load().unwrap())); - - // keyboard refactor: don't seems to be needed anymore - // let characters = get_characters(event, false); - //state.raw_characters = Some(characters); - - let is_repeat: bool = msg_send![event, isARepeat]; - - update_potentially_stale_modifiers(state, event); - - let pass_along = !is_repeat || !state.is_key_down; - if pass_along { - // See below for why we do this. - let marked_text_ref: &mut *mut NSMutableAttributedString = this.get_mut_ivar("markedText"); - let () = msg_send![(*marked_text_ref), release]; - *marked_text_ref = Retained::into_raw(NSMutableAttributedString::new()); - state.key_triggered_ime = false; - - // Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do... - // So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some - // keys to generate twice as many characters. - let array: id = msg_send![class!(NSArray), arrayWithObject: event]; - let () = msg_send![&*this, interpretKeyEvents: array]; - } - // The `interpretKeyEvents` above, may invoke `set_marked_text` or `insert_text`, - // if the event corresponds to an IME event. - let in_ime = state.key_triggered_ime; - let key_event = create_key_event(event, true, is_repeat, in_ime, None); - let is_arrow_key = is_arrow_key(key_event.physical_key); - if pass_along { - // The `interpretKeyEvents` above, may invoke `set_marked_text` or `insert_text`, - // if the event corresponds to an IME event. - // If `set_marked_text` or `insert_text` were not invoked, then the IME was deactivated, - // and we should cancel the IME session. - // When using arrow keys in an IME window, the input context won't invoke the - // IME related methods, so in that case we shouldn't cancel the IME session. - let is_preediting: bool = state.in_ime_preedit; - if is_preediting && !state.key_triggered_ime && !is_arrow_key { - // In this case we should cancel the IME session. - let () = msg_send![this, unmarkText]; - state.in_ime_preedit = false; - } - } - let window_event = Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event: key_event, - is_synthetic: false, - }, - }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `keyDown`"); -} - -extern "C" fn key_up(this: &Object, _sel: Sel, event: &NSEvent) { - trace!("Triggered `keyUp`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - state.is_key_down = false; - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event: create_key_event(event, false, false, false, None), - is_synthetic: false, - }, - }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `keyUp`"); -} - -extern "C" fn flags_changed(this: &Object, _sel: Sel, ns_event: &NSEvent) { - use KeyCode::{ - AltLeft, AltRight, ControlLeft, ControlRight, ShiftLeft, ShiftRight, SuperLeft, SuperRight, - }; - - trace!("Triggered `flagsChanged`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let scancode = get_scancode(ns_event); - - // We'll correct the `is_press` and the `key_override` below. - let event = create_key_event(ns_event, false, false, false, Some(KeyCode::SuperLeft)); - let mut events = VecDeque::with_capacity(4); - - macro_rules! process_event { - ($tao_flag:expr, $ns_flag:expr, $target_key:expr) => { - let ns_event_contains_keymask = NSEvent::modifierFlags(ns_event).contains($ns_flag); - let mut actual_key = KeyCode::from_scancode(scancode as u32); - let was_pressed = state.phys_modifiers.contains(&actual_key); - let mut is_pressed = ns_event_contains_keymask; - let matching_keys; - if actual_key == KeyCode::KeyA { - // When switching keyboard layout using Ctrl+Space, the Ctrl release event - // has `KeyA` as its keycode which would produce an incorrect key event. - // To avoid this, we detect this scenario and override the key with one - // that should be reasonable - actual_key = $target_key; - matching_keys = true; - } else { - matching_keys = actual_key == $target_key; - if matching_keys && is_pressed && was_pressed { - // If we received a modifier flags changed event AND the key that triggered - // it was already pressed then this must mean that this event indicates the - // release of that key. - is_pressed = false; - } - } - if matching_keys { - if is_pressed != was_pressed { - let mut event = event.clone(); - event.state = if is_pressed { - ElementState::Pressed - } else { - ElementState::Released - }; - event.physical_key = actual_key; - event.logical_key = code_to_key(event.physical_key, scancode); - events.push_back(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event, - is_synthetic: false, - }); - if is_pressed { - state.phys_modifiers.insert($target_key); - } else { - state.phys_modifiers.remove(&$target_key); - } - if ns_event_contains_keymask { - state.modifiers.insert($tao_flag); - } else { - state.modifiers.remove($tao_flag); - } - } - } - }; - } - process_event!( - ModifiersState::SHIFT, - NSEventModifierFlags::Shift, - ShiftLeft - ); - process_event!( - ModifiersState::SHIFT, - NSEventModifierFlags::Shift, - ShiftRight - ); - process_event!( - ModifiersState::CONTROL, - NSEventModifierFlags::Control, - ControlLeft - ); - process_event!( - ModifiersState::CONTROL, - NSEventModifierFlags::Control, - ControlRight - ); - process_event!(ModifiersState::ALT, NSEventModifierFlags::Option, AltLeft); - process_event!(ModifiersState::ALT, NSEventModifierFlags::Option, AltRight); - process_event!( - ModifiersState::SUPER, - NSEventModifierFlags::Command, - SuperLeft - ); - process_event!( - ModifiersState::SUPER, - NSEventModifierFlags::Command, - SuperRight - ); - - let window_id = WindowId(get_window_id(&state.ns_window.load().unwrap())); - - for event in events { - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event, - })); - } - - AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(state.modifiers), - })); - } - trace!("Completed `flagsChanged`"); -} - -extern "C" fn insert_tab(this: &Object, _sel: Sel, _sender: id) { - unsafe { - let window: id = msg_send![this, window]; - let first_responder: id = msg_send![window, firstResponder]; - let this_ptr = this as *const _ as *mut _; - if first_responder == this_ptr { - let (): _ = msg_send![window, selectNextKeyView: this]; - } - } -} - -extern "C" fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { - unsafe { - let window: id = msg_send![this, window]; - let first_responder: id = msg_send![window, firstResponder]; - let this_ptr = this as *const _ as *mut _; - if first_responder == this_ptr { - let (): _ = msg_send![window, selectPreviousKeyView: this]; - } - } -} - -// Allows us to receive Cmd-. (the shortcut for closing a dialog) -// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 -extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { - trace!("Triggered `cancelOperation`"); - unsafe { - let mtm = MainThreadMarker::new_unchecked(); - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let event: Retained = msg_send![&NSApp(mtm), currentEvent]; - update_potentially_stale_modifiers(state, &event); - - let scancode = 0x2f; - let key = KeyCode::from_scancode(scancode); - debug_assert_eq!(key, KeyCode::Period); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event: create_key_event(&event, true, false, false, Some(key)), - is_synthetic: false, - }, - }; - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `cancelOperation`"); -} - -fn mouse_click(this: &Object, event: &NSEvent, button: MouseButton, button_state: ElementState) { - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::MouseInput { - device_id: DEVICE_ID, - state: button_state, - button, - modifiers: event_mods(event), - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -extern "C" fn mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Left, ElementState::Pressed); -} - -extern "C" fn mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Left, ElementState::Released); -} - -extern "C" fn right_mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Right, ElementState::Pressed); -} - -extern "C" fn right_mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Right, ElementState::Released); -} - -extern "C" fn other_mouse_down(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Middle, ElementState::Pressed); -} - -extern "C" fn other_mouse_up(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); - mouse_click(this, event, MouseButton::Middle, ElementState::Released); -} - -fn mouse_motion(this: &NSView, event: &NSEvent) { - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let window_point = event.locationInWindow(); - let view_point = this.convertPoint_fromView(window_point, None); - let view_rect = NSView::frame(this); - - if view_point.x.is_sign_negative() - || view_point.y.is_sign_negative() - || view_point.x > view_rect.size.width - || view_point.y > view_rect.size.height - { - let mouse_buttons_down: NSUInteger = msg_send![class!(NSEvent), pressedMouseButtons]; - if mouse_buttons_down == 0 { - // Point is outside of the client area (view) and no buttons are pressed - return; - } - } - - let x = view_point.x as f64; - let y = view_rect.size.height as f64 - view_point.y as f64; - let logical_position = LogicalPosition::new(x, y); - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::CursorMoved { - device_id: DEVICE_ID, - position: logical_position.to_physical(state.get_scale_factor()), - modifiers: event_mods(event), - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } -} - -extern "C" fn mouse_moved(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); -} - -extern "C" fn mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); -} - -extern "C" fn right_mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); -} - -extern "C" fn other_mouse_dragged(this: &NSView, _sel: Sel, event: &NSEvent) { - mouse_motion(this, event); -} - -extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) { - trace!("Triggered `mouseEntered`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let enter_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::CursorEntered { - device_id: DEVICE_ID, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - } - trace!("Completed `mouseEntered`"); -} - -extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) { - trace!("Triggered `mouseExited`"); - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::CursorLeft { - device_id: DEVICE_ID, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `mouseExited`"); -} - -extern "C" fn scroll_wheel(this: &NSView, _sel: Sel, event: &NSEvent) { - trace!("Triggered `scrollWheel`"); - - mouse_motion(this, event); - - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let delta = { - // macOS horizontal sign convention is the inverse of tao. - let (x, y) = (event.scrollingDeltaX() * -1.0, event.scrollingDeltaY()); - if event.hasPreciseScrollingDeltas() { - let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); - MouseScrollDelta::PixelDelta(delta) - } else { - MouseScrollDelta::LineDelta(x as f32, y as f32) - } - }; - let phase = match event.phase() { - NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started, - NSEventPhase::Ended => TouchPhase::Ended, - _ => TouchPhase::Moved, - }; - - let device_event = Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { delta }, - }; - - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - update_potentially_stale_modifiers(state, event); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta, - phase, - modifiers: event_mods(event), - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(device_event)); - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `scrollWheel`"); -} - -extern "C" fn pressure_change_with_event(this: &NSView, _sel: Sel, event: &NSEvent) { - trace!("Triggered `pressureChangeWithEvent`"); - - mouse_motion(this, event); - - unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - - let pressure = event.pressure(); - let stage = event.stage(); - - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(&state.ns_window.load().unwrap())), - event: WindowEvent::TouchpadPressure { - device_id: DEVICE_ID, - pressure, - stage: stage as i64, - }, - }; - - AppState::queue_event(EventWrapper::StaticEvent(window_event)); - } - trace!("Completed `pressureChangeWithEvent`"); -} - -// Allows us to receive Ctrl-Tab and Ctrl-Esc. -// Note that this *doesn't* help with any missing Cmd inputs. -// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 -extern "C" fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL { - YES -} - -extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL { - YES -} - -pub unsafe fn inset_traffic_lights(window: &NSWindow, position: LogicalPosition) { - let (x, y) = (position.x, position.y); - - let close = window - .standardWindowButton(NSWindowButton::CloseButton) - .unwrap(); - let miniaturize = window - .standardWindowButton(NSWindowButton::MiniaturizeButton) - .unwrap(); - let zoom = window - .standardWindowButton(NSWindowButton::ZoomButton) - .unwrap(); - - let title_bar_container_view = close.superview().unwrap().superview().unwrap(); - - let close_rect = NSView::frame(&close); - let title_bar_frame_height = close_rect.size.height + y; - let mut title_bar_rect = NSView::frame(&title_bar_container_view); - title_bar_rect.size.height = title_bar_frame_height; - title_bar_rect.origin.y = window.frame().size.height - title_bar_frame_height; - let _: () = msg_send![&title_bar_container_view, setFrame: title_bar_rect]; - - let window_buttons = vec![close, miniaturize.clone(), zoom]; - let space_between = NSView::frame(&miniaturize).origin.x - close_rect.origin.x; - - for (i, button) in window_buttons.into_iter().enumerate() { - let mut rect = NSView::frame(&button); - rect.origin.x = x + (i as f64 * space_between); - button.setFrameOrigin(rect.origin); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs deleted file mode 100644 index 8a19b9d2c..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs +++ /dev/null @@ -1,1793 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -#![allow(unused_unsafe)] - -use std::{ - collections::VecDeque, - convert::TryInto, - f64, - ffi::CStr, - os::raw::c_void, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, Weak, - }, -}; - -use crate::{ - dpi::{ - LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, - }, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform::macos::WindowExtMacOS, - platform_impl::{ - platform::{ - app_state::AppState, - ffi, - monitor::{self, MonitorHandle, VideoMode}, - util::{self, IdRef}, - view::{self, new_view, CursorState}, - window_delegate::new_delegate, - OsError, - }, - set_badge_label, set_progress_indicator, - }, - window::{ - CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, - WindowAttributes, WindowId as RootWindowId, WindowSizeConstraints, - }, -}; -use core_graphics::{ - base::CGFloat, - display::{CGDisplay, CGDisplayMode}, - geometry::CGPoint, -}; -use objc2::{ - msg_send, - rc::Retained, - runtime::{AnyClass as Class, AnyObject as Object, Bool, ClassBuilder as ClassDecl, Sel}, -}; -use objc2_app_kit::{ - self as appkit, NSApp, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSEvent, - NSEventModifierFlags, NSEventSubtype, NSEventType, NSRequestUserAttentionType, NSScreen, NSView, - NSWindow, NSWindowButton, NSWindowCollectionBehavior, NSWindowFullScreenButton, - NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, -}; -use objc2_foundation::{ - ns_string, MainThreadMarker, NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, - NSRect, NSSize, NSString, NSTimeInterval, NSUInteger, -}; -use once_cell::sync::Lazy; - -use super::{ - ffi::{id, nil, NO}, - view::ViewState, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id(pub usize); - -impl Id { - pub unsafe fn dummy() -> Self { - Id(0) - } -} - -// Convert the `NSWindow` associated with a window to a usize to use as a unique identifier -// for the window. -pub fn get_window_id(window_cocoa_id: &NSWindow) -> Id { - Id(window_cocoa_id as *const NSWindow as _) -} - -#[non_exhaustive] -#[derive(Clone)] -pub enum Parent { - None, - ChildOf(*mut c_void), -} - -#[derive(Clone)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub parent: Parent, - pub movable_by_window_background: bool, - pub titlebar_transparent: bool, - pub title_hidden: bool, - pub titlebar_hidden: bool, - pub titlebar_buttons_hidden: bool, - pub fullsize_content_view: bool, - pub resize_increments: Option>, - pub disallow_hidpi: bool, - pub has_shadow: bool, - pub traffic_light_inset: Option, - pub automatic_tabbing: bool, - pub tabbing_identifier: Option, -} - -impl Default for PlatformSpecificWindowBuilderAttributes { - #[inline] - fn default() -> Self { - Self { - parent: Parent::None, - movable_by_window_background: false, - titlebar_transparent: false, - title_hidden: false, - titlebar_hidden: false, - titlebar_buttons_hidden: false, - fullsize_content_view: false, - resize_increments: None, - disallow_hidpi: false, - has_shadow: true, - traffic_light_inset: None, - automatic_tabbing: true, - tabbing_identifier: None, - } - } -} - -unsafe fn create_view( - ns_window: &NSWindow, - pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option<(Retained, Weak>)> { - let (ns_view, cursor_state) = new_view(ns_window); - ns_view.map(|ns_view| { - if !pl_attribs.disallow_hidpi { - #[allow(deprecated)] - ns_view.setWantsBestResolutionOpenGLSurface(true); - } - - #[allow(deprecated)] // TODO: Use define_class! - if let Some(position) = pl_attribs.traffic_light_inset { - let state_ptr: *mut c_void = *(**ns_view).get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - let scale_factor = NSWindow::backingScaleFactor(ns_window); - let position = position.to_logical(scale_factor); - state.traffic_light_inset = Some(position); - } - - // On Mojave, views automatically become layer-backed shortly after being added to - // a window. Changing the layer-backedness of a view breaks the association between - // the view and its associated OpenGL context. To work around this, on Mojave we - // explicitly make the view layer-backed up front so that AppKit doesn't do it - // itself and break the association with its context. - if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { - ns_view.setWantsLayer(true); - } - - (ns_view, cursor_state) - }) -} - -fn create_window( - attrs: &WindowAttributes, - pl_attrs: &PlatformSpecificWindowBuilderAttributes, -) -> Option> { - unsafe { - let mtm = MainThreadMarker::new_unchecked(); - let _pool = NSAutoreleasePool::new(); - let screen = match attrs.fullscreen { - Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor }))) - | Some(Fullscreen::Exclusive(RootVideoMode { - video_mode: VideoMode { ref monitor, .. }, - })) => { - let monitor_screen = monitor.ns_screen(); - Some(monitor_screen.unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap())) - } - Some(Fullscreen::Borderless(None)) => Some( - attrs - .position - .and_then(screen_from_position) - .unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap()), - ), - None => None, - }; - let frame = match &screen { - Some(screen) => NSScreen::frame(screen), - None => { - let screen = attrs - .position - .and_then(screen_from_position) - .unwrap_or_else(|| appkit::NSScreen::mainScreen(mtm).unwrap()); - let scale_factor = NSScreen::backingScaleFactor(&screen) as f64; - let desired_size = attrs - .inner_size - .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - let (width, height): (f64, f64) = attrs - .inner_size_constraints - .clamp(desired_size, scale_factor) - .to_logical::(scale_factor) - .into(); - let (left, bottom) = match attrs.position { - Some(position) => { - let logical = util::window_position(position.to_logical(scale_factor)); - // macOS wants the position of the bottom left corner, - // but caller is setting the position of top left corner - (logical.x, logical.y - height) - } - // This value is ignored by calling win.center() below - None => (0.0, 0.0), - }; - NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) - } - }; - - let mut masks = if !attrs.decorations && screen.is_none() || pl_attrs.titlebar_hidden { - // Resizable UnownedWindow without a titlebar or borders - // if decorations is set to false, ignore pl_attrs - NSWindowStyleMask::Borderless - | NSWindowStyleMask::Resizable - | NSWindowStyleMask::Miniaturizable - } else { - // default case, resizable window with titlebar and titlebar buttons - NSWindowStyleMask::Closable - | NSWindowStyleMask::Miniaturizable - | NSWindowStyleMask::Resizable - | NSWindowStyleMask::Titled - }; - - if !attrs.resizable { - masks &= !NSWindowStyleMask::Resizable; - } - - if !attrs.minimizable { - masks &= !NSWindowStyleMask::Miniaturizable; - } - - if !attrs.closable { - masks &= !NSWindowStyleMask::Closable; - } - - if pl_attrs.fullsize_content_view { - masks |= NSWindowStyleMask::FullSizeContentView; - } - - let ns_window: id = msg_send![WINDOW_CLASS.0, alloc]; - let ns_window_ptr: id = msg_send![ - ns_window, - initWithContentRect: frame, - styleMask: masks, - backing: NSBackingStoreType::Buffered, - defer: NO, - ]; - - Retained::retain(ns_window_ptr).and_then(|r| r.downcast::().ok()).map(|ns_window| { - #[allow(deprecated)] - { - *((*ns_window_ptr).get_mut_ivar::("focusable")) = attrs.focusable.into(); - } - - let title = NSString::from_str(&attrs.title); - ns_window.setReleasedWhenClosed(false); - ns_window.setTitle(&title); - ns_window.setAcceptsMouseMovedEvents(true); - - if pl_attrs.titlebar_transparent { - ns_window.setTitlebarAppearsTransparent(true); - } - if pl_attrs.title_hidden { - ns_window.setTitleVisibility(appkit::NSWindowTitleVisibility::Hidden); - } - if pl_attrs.titlebar_buttons_hidden { - for titlebar_button in &[ - NSWindowFullScreenButton, - NSWindowButton::MiniaturizeButton, - NSWindowButton::CloseButton, - NSWindowButton::ZoomButton, - ] { - if let Some(button) = ns_window.standardWindowButton(*titlebar_button) { - button.setHidden(true); - } - } - } - if pl_attrs.movable_by_window_background { - ns_window.setMovableByWindowBackground(true); - } - - if attrs.always_on_top { - ns_window.setLevel(ffi::NSWindowLevel::NSFloatingWindowLevel as isize); - } - - if attrs.always_on_bottom { - ns_window.setLevel(ffi::NSWindowLevel::BelowNormalWindowLevel as isize); - } - - if attrs.content_protection { - ns_window.setSharingType(NSWindowSharingType::None); - } - - if !attrs.maximizable { - if let Some(button) = ns_window - .standardWindowButton(NSWindowButton::ZoomButton) { - button.setEnabled(false); - } - } - - if let Some(increments) = pl_attrs.resize_increments { - let (x, y) = (increments.width, increments.height); - if x >= 1.0 && y >= 1.0 { - let size = NSSize::new(x as CGFloat, y as CGFloat); - ns_window.setResizeIncrements(size); - } - } - - if let Parent::ChildOf(parent) = pl_attrs.parent { - let _: () = - msg_send![parent as id, addChildWindow: &*ns_window, ordered: NSWindowOrderingMode::Above]; - } - - if !pl_attrs.automatic_tabbing { - NSWindow::setAllowsAutomaticWindowTabbing(false, mtm); - } - - if let Some(tabbing_identifier) = &pl_attrs.tabbing_identifier { - ns_window.setTabbingIdentifier( &NSString::from_str(tabbing_identifier)); - } - - if !pl_attrs.has_shadow { - ns_window.setHasShadow(false); - } - if attrs.position.is_none() { - ns_window.center(); - } - - ns_window - }) - } -} - -fn screen_from_position(position: Position) -> Option> { - for m in monitor::available_monitors() { - let monitor_pos = m.position(); - let monitor_size = m.size(); - - // type annotations required for 32bit targets. - let window_position = position.to_physical::(m.scale_factor()); - - let is_in_monitor = monitor_pos.x <= window_position.x - && window_position.x < monitor_pos.x + monitor_size.width as i32 - && monitor_pos.y <= window_position.y - && window_position.y < monitor_pos.y + monitor_size.height as i32; - if is_in_monitor { - return m.ns_screen(); - } - } - None -} - -pub(super) fn get_ns_theme() -> Theme { - unsafe { - let appearances: Vec> = vec![ - NSString::from_str("NSAppearanceNameAqua"), - NSString::from_str("NSAppearanceNameDarkAqua"), - ]; - let app_class = class!(NSApplication); - let app: id = msg_send![app_class, sharedApplication]; - let has_theme: bool = msg_send![app, respondsToSelector: sel!(effectiveAppearance)]; - if !has_theme { - return Theme::Light; - } - let appearance: id = msg_send![app, effectiveAppearance]; - let name: Retained = msg_send![ - appearance, - bestMatchFromAppearancesWithNames: &*NSArray::from_retained_slice(&appearances) - ]; - let name = name.to_string(); - match &*name { - "NSAppearanceNameDarkAqua" => Theme::Dark, - _ => Theme::Light, - } - } -} - -pub(super) fn set_ns_theme(theme: Option) { - unsafe { - let app_class = class!(NSApplication); - let app: id = msg_send![app_class, sharedApplication]; - let has_theme: bool = msg_send![app, respondsToSelector: sel!(effectiveAppearance)]; - if has_theme { - let appearance = if let Some(theme) = theme { - let name = NSString::from_str(match theme { - Theme::Dark => "NSAppearanceNameDarkAqua", - Theme::Light => "NSAppearanceNameAqua", - }); - msg_send![class!(NSAppearance), appearanceNamed: &*name] - } else { - nil - }; - let _: () = msg_send![app, setAppearance: appearance]; - } - } -} - -struct WindowClass(&'static Class); -unsafe impl Send for WindowClass {} -unsafe impl Sync for WindowClass {} - -static WINDOW_CLASS: Lazy = Lazy::new(|| unsafe { - let window_superclass = class!(NSWindow); - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoWindow\0").unwrap(), - window_superclass, - ) - .unwrap(); - decl.add_method( - sel!(canBecomeMainWindow), - is_focusable as extern "C" fn(_, _) -> _, - ); - decl.add_method( - sel!(canBecomeKeyWindow), - is_focusable as extern "C" fn(_, _) -> _, - ); - decl.add_method(sel!(sendEvent:), send_event as extern "C" fn(_, _, _)); - // progress bar states, follows ProgressState - decl.add_ivar::(CStr::from_bytes_with_nul(b"focusable\0").unwrap()); - WindowClass(decl.register()) -}); - -extern "C" fn is_focusable(this: &Object, _: Sel) -> Bool { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - *(this.get_ivar("focusable")) - } -} - -extern "C" fn send_event(this: &Object, _sel: Sel, event: &NSEvent) { - unsafe { - let event_type = event.r#type(); - if event_type == NSEventType::LeftMouseDown { - // When wkwebview is set on NSWindow, `WindowBuilder::with_movable_by_window_background` is not working. - // Because of this, we need to invoke `[NSWindow performWindowDragWithEvent]` in NSLeftMouseDown event. - let is_movable_window: bool = msg_send![this, isMovableByWindowBackground]; - if is_movable_window { - let _: () = msg_send![this, performWindowDragWithEvent: event]; - } - } - let superclass = util::superclass(this); - let _: () = msg_send![super(this, superclass), sendEvent: event]; - } -} - -#[derive(Default)] -pub struct SharedState { - pub resizable: bool, - pub fullscreen: Option, - // This is true between windowWillEnterFullScreen and windowDidEnterFullScreen - // or windowWillExitFullScreen and windowDidExitFullScreen. - // We must not toggle fullscreen when this is true. - pub in_fullscreen_transition: bool, - // If it is attempted to toggle fullscreen when in_fullscreen_transition is true, - // Set target_fullscreen and do after fullscreen transition is end. - pub target_fullscreen: Option>, - pub maximized: bool, - pub standard_frame: Option, - is_simple_fullscreen: bool, - pub saved_style: Option, - /// Presentation options saved before entering `set_simple_fullscreen`, and - /// restored upon exiting it. Also used when transitioning from Borderless to - /// Exclusive fullscreen in `set_fullscreen` because we need to disable the menu - /// bar in exclusive fullscreen but want to restore the original options when - /// transitioning back to borderless fullscreen. - save_presentation_opts: Option, - pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, - pub current_theme: Theme, -} - -impl SharedState { - pub fn saved_standard_frame(&self) -> NSRect { - self - .standard_frame - .unwrap_or_else(|| NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0))) - } -} - -impl From for SharedState { - fn from(attribs: WindowAttributes) -> Self { - SharedState { - resizable: attribs.resizable, - // This fullscreen field tracks the current state of the window - // (as seen by `WindowDelegate`), and since the window hasn't - // actually been fullscreened yet, we can't set it yet. This is - // necessary for state transitions to work right, since otherwise - // the initial value and the first `set_fullscreen` call would be - // identical, resulting in a no-op. - fullscreen: None, - maximized: attribs.maximized, - ..Default::default() - } - } -} - -pub struct UnownedWindow { - pub ns_window: Retained, // never changes - pub ns_view: Retained, // never changes - input_context: IdRef, // never changes - pub shared_state: Arc>, - decorations: AtomicBool, - cursor_state: Weak>, - transparent: bool, - pub inner_rect: Option>, -} - -unsafe impl Send for UnownedWindow {} -unsafe impl Sync for UnownedWindow {} - -impl UnownedWindow { - pub fn new( - mut win_attribs: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result<(Arc, IdRef), RootOsError> { - if !util::is_main_thread() { - panic!("Windows can only be created on the main thread on macOS"); - } - trace!("Creating new window"); - - let _pool = unsafe { NSAutoreleasePool::new() }; - let ns_window = create_window(&win_attribs, &pl_attribs) - .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; - - let (ns_view, cursor_state) = unsafe { create_view(&ns_window, &pl_attribs) } - .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSView`")))?; - - // Configure the new view as the "key view" for the window - unsafe { - ns_window.setContentView(Some(&ns_view)); - ns_window.setInitialFirstResponder(Some(&ns_view)); - } - - let input_context = unsafe { util::create_input_context(&ns_view) }; - - let scale_factor = unsafe { NSWindow::backingScaleFactor(&ns_window) as f64 }; - - unsafe { - if win_attribs.transparent { - ns_window.setOpaque(false); - } - - if win_attribs.transparent || win_attribs.background_color.is_some() { - let color = win_attribs - .background_color - .map(|(r, g, b, a)| { - NSColor::colorWithRed_green_blue_alpha( - r as f64 / 255.0, - g as f64 / 255.0, - b as f64 / 255.0, - a as f64 / 255.0, - ) - }) - .unwrap_or_else(|| NSColor::clearColor()); - ns_window.setBackgroundColor(Some(&color)); - } - - if win_attribs.inner_size_constraints.has_min() { - let min_size = win_attribs - .inner_size_constraints - .min_size_logical(scale_factor); - set_min_inner_size(&ns_window, min_size); - } - if win_attribs.inner_size_constraints.has_max() { - let max_size = win_attribs - .inner_size_constraints - .max_size_logical(scale_factor); - set_max_inner_size(&ns_window, max_size); - } - - // register for drag and drop operations. - ns_window.registerForDraggedTypes(&NSArray::arrayWithObject(appkit::NSFilenamesPboardType)); - } - - // Since `win_attribs` is put into a mutex below, we'll just copy these - // attributes now instead of bothering to lock it later. - // Also, `SharedState` doesn't carry `fullscreen` over; it's set - // indirectly by us calling `set_fullscreen` below, causing handlers in - // `WindowDelegate` to update the state. - let fullscreen = win_attribs.fullscreen.take(); - let maximized = win_attribs.maximized; - let transparent = win_attribs.transparent; - let visible = win_attribs.visible; - let focused = win_attribs.focused; - let decorations = win_attribs.decorations; - let visible_on_all_workspaces = win_attribs.visible_on_all_workspaces; - let inner_rect = win_attribs - .inner_size - .map(|size| size.to_physical(scale_factor)); - - let cloned_preferred_theme = win_attribs.preferred_theme; - - let window = Arc::new(UnownedWindow { - ns_view, - ns_window, - input_context, - shared_state: Arc::new(Mutex::new(win_attribs.into())), - decorations: AtomicBool::new(decorations), - cursor_state, - inner_rect, - transparent, - }); - - match cloned_preferred_theme { - Some(theme) => { - set_ns_theme(Some(theme)); - let mut state = window.shared_state.lock().unwrap(); - state.current_theme = theme.clone(); - } - None => { - let mut state = window.shared_state.lock().unwrap(); - state.current_theme = get_ns_theme(); - } - } - - let delegate = new_delegate(&window, fullscreen.is_some()); - - // Set fullscreen mode after we setup everything - window.set_fullscreen(fullscreen); - window.set_visible_on_all_workspaces(visible_on_all_workspaces); - - // Setting the window as key has to happen *after* we set the fullscreen - // state, since otherwise we'll briefly see the window at normal size - // before it transitions. - if visible { - if focused { - // Tightly linked with `app_state::window_activation_hack` - unsafe { window.ns_window.makeKeyAndOrderFront(None) }; - } else { - unsafe { window.ns_window.orderFront(None) }; - } - } - - if maximized { - window.set_maximized(maximized); - } - - Ok((window, delegate)) - } - - fn set_style_mask_async(&self, mask: NSWindowStyleMask) { - unsafe { util::set_style_mask_async(&self.ns_window, &self.ns_view, mask) }; - } - - fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { - unsafe { util::set_style_mask_sync(&self.ns_window, &self.ns_view, mask) }; - } - - pub fn id(&self) -> Id { - get_window_id(&self.ns_window) - } - - pub fn set_title(&self, title: &str) { - unsafe { - util::set_title_async(&self.ns_window, title.to_string()); - } - } - - pub fn title(&self) -> String { - self.ns_window.title().to_string() - } - - pub fn set_visible(&self, visible: bool) { - match visible { - true => unsafe { util::make_key_and_order_front_sync(&self.ns_window) }, - false => unsafe { util::order_out_sync(&self.ns_window) }, - } - } - - #[inline] - // Shortener for set_visible(true) - pub fn set_focus(&self) { - unsafe { - let is_minimized = self.ns_window.isMiniaturized(); - let is_visible = self.ns_window.isVisible(); - if !is_minimized && is_visible { - util::set_focus(&self.ns_window); - } - } - } - - #[inline] - pub fn set_focusable(&self, focusable: bool) { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let ns_window = - Retained::into_raw(Retained::cast_unchecked::(self.ns_window.clone())); - *((*ns_window).get_mut_ivar::("focusable")) = focusable.into(); - } - } - - #[inline] - pub fn is_focused(&self) -> bool { - unsafe { self.ns_window.isKeyWindow() } - } - - pub fn request_redraw(&self) { - AppState::queue_redraw(RootWindowId(self.id())); - } - - pub fn outer_position(&self) -> Result, NotSupportedError> { - let frame_rect = unsafe { NSWindow::frame(&self.ns_window) }; - let position = LogicalPosition::new( - frame_rect.origin.x as f64, - util::bottom_left_to_top_left(frame_rect), - ); - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } - - pub fn inner_position(&self) -> Result, NotSupportedError> { - let content_rect = unsafe { - NSWindow::contentRectForFrameRect(&self.ns_window, NSWindow::frame(&self.ns_window)) - }; - let position = LogicalPosition::new( - content_rect.origin.x as f64, - util::bottom_left_to_top_left(content_rect), - ); - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) - } - - pub fn set_outer_position(&self, position: Position) { - let scale_factor = self.scale_factor(); - let position = position.to_logical(scale_factor); - unsafe { - util::set_frame_top_left_point_async(&self.ns_window, util::window_position(position)); - } - } - - #[inline] - pub fn inner_size(&self) -> PhysicalSize { - let view_frame = unsafe { NSView::frame(&self.ns_view) }; - let logical: LogicalSize = - (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let scale_factor = self.scale_factor(); - logical.to_physical(scale_factor) - } - - #[inline] - pub fn outer_size(&self) -> PhysicalSize { - let view_frame = unsafe { NSWindow::frame(&self.ns_window) }; - let logical: LogicalSize = - (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let scale_factor = self.scale_factor(); - logical.to_physical(scale_factor) - } - - #[inline] - pub fn set_inner_size(&self, size: Size) { - unsafe { - let scale_factor = self.scale_factor(); - util::set_content_size_async(&self.ns_window, size.to_logical(scale_factor)); - } - } - - pub fn set_min_inner_size(&self, dimensions: Option) { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: 0.0, - height: 0.0, - })); - let scale_factor = self.scale_factor(); - unsafe { - set_min_inner_size(&self.ns_window, dimensions.to_logical(scale_factor)); - } - } - - pub fn set_max_inner_size(&self, dimensions: Option) { - let dimensions = dimensions.unwrap_or(Logical(LogicalSize { - width: f32::MAX as f64, - height: f32::MAX as f64, - })); - let scale_factor = self.scale_factor(); - unsafe { - set_max_inner_size(&self.ns_window, dimensions.to_logical(scale_factor)); - } - } - - pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - let scale_factor = self.scale_factor(); - unsafe { - let min_size = constraints.min_size_logical(scale_factor); - set_min_inner_size(&self.ns_window, min_size); - let max_size = constraints.max_size_logical(scale_factor); - set_max_inner_size(&self.ns_window, max_size); - } - } - - #[inline] - pub fn set_resizable(&self, resizable: bool) { - let fullscreen = { - trace!("Locked shared state in `set_resizable`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.resizable = resizable; - trace!("Unlocked shared state in `set_resizable`"); - shared_state_lock.fullscreen.is_some() - }; - if !fullscreen { - let mut mask = unsafe { self.ns_window.styleMask() }; - if resizable { - mask |= NSWindowStyleMask::Resizable; - } else { - mask &= !NSWindowStyleMask::Resizable; - } - self.set_style_mask_sync(mask); - } // Otherwise, we don't change the mask until we exit fullscreen. - } - - #[inline] - pub fn set_minimizable(&self, minimizable: bool) { - let mut mask = unsafe { self.ns_window.styleMask() }; - if minimizable { - mask |= NSWindowStyleMask::Miniaturizable; - } else { - mask &= !NSWindowStyleMask::Miniaturizable; - } - self.set_style_mask_sync(mask); - } - - #[inline] - pub fn set_maximizable(&self, maximizable: bool) { - unsafe { - if let Some(button) = self - .ns_window - .standardWindowButton(NSWindowButton::ZoomButton) - { - button.setEnabled(maximizable); - } - } - } - - #[inline] - pub fn set_closable(&self, closable: bool) { - let mut mask = unsafe { self.ns_window.styleMask() }; - if closable { - mask |= NSWindowStyleMask::Closable; - } else { - mask &= !NSWindowStyleMask::Closable; - } - self.set_style_mask_sync(mask); - } - - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor_state.upgrade() { - cursor_access.lock().unwrap().cursor = cursor; - } - unsafe { - self.ns_window.invalidateCursorRectsForView(&self.ns_view); - } - } - - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 - CGDisplay::associate_mouse_and_mouse_cursor_position(!grab) - .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) - } - - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - if let Some(cursor_access) = self.cursor_state.upgrade() { - let mut cursor_state = cursor_access.lock().unwrap(); - if visible != cursor_state.visible { - cursor_state.visible = visible; - drop(cursor_state); - unsafe { - self.ns_window.invalidateCursorRectsForView(&self.ns_view); - } - } - } - } - - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position() - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - NSWindow::backingScaleFactor(&self.ns_window) as _ - } - - #[inline] - pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { - let physical_window_position = self.inner_position().unwrap(); - let scale_factor = self.scale_factor(); - let window_position = physical_window_position.to_logical::(scale_factor); - let logical_cursor_position = cursor_position.to_logical::(scale_factor); - let point = CGPoint { - x: logical_cursor_position.x + window_position.x, - y: logical_cursor_position.y + window_position.y, - }; - CGDisplay::warp_mouse_cursor_position(point) - .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; - CGDisplay::associate_mouse_and_mouse_cursor_position(true) - .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; - - Ok(()) - } - - #[inline] - pub fn set_background_color(&self, color: Option) { - unsafe { - let color = color - .map(|(r, g, b, a)| { - Some(NSColor::colorWithRed_green_blue_alpha( - r as f64 / 255.0, - g as f64 / 255.0, - b as f64 / 255.0, - a as f64 / 255.0, - )) - }) - .unwrap_or_else(|| { - if self.transparent { - Some(NSColor::clearColor()) - } else { - None - } - }); - self.ns_window.setBackgroundColor(color.as_deref()); - } - } - - #[inline] - pub fn drag_window(&self) -> Result<(), ExternalError> { - unsafe { - let mtm = MainThreadMarker::new_unchecked(); - let mut event: id = msg_send![&NSApp(mtm), currentEvent]; - - let event_type: NSUInteger = msg_send![event, type]; - if event_type == 0x15 { - let mouse_location: NSPoint = msg_send![class!(NSEvent), mouseLocation]; - let event_modifier_flags: NSEventModifierFlags = msg_send![event, modifierFlags]; - let event_timestamp: NSTimeInterval = msg_send![event, timestamp]; - let event_window_number: NSInteger = msg_send![event, windowNumber]; - - event = msg_send![ - class!(NSEvent), - mouseEventWithType: NSEventType::LeftMouseDown, - location: mouse_location, - modifierFlags: event_modifier_flags, - timestamp: event_timestamp, - windowNumber: event_window_number, - context: nil, - eventNumber: NSEventSubtype::WindowExposed, - clickCount: 1, - pressure: 1.0, - ]; - } - - let _: () = msg_send![&self.ns_window, performWindowDragWithEvent: event]; - } - - Ok(()) - } - - pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - #[inline] - pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - unsafe { - util::set_ignore_mouse_events(&self.ns_window, ignore); - } - - Ok(()) - } - - pub(crate) fn is_zoomed(&self) -> bool { - // Previously, is_zoomed temporarily mutated the window's styleMask(or resizable) - // to force macOS to return a valid result for borderless windows. - // This synchronous mutation could trigger unnecessary layout passes or state inconsistencies. - // Related issue: https://github.com/rust-windowing/winit/issues/4071 and https://github.com/tauri-apps/plugins-workspace/issues/3240 - - unsafe { - let curr_mask = self.ns_window.styleMask(); - - let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; - if curr_mask.contains(required) { - return self.ns_window.isZoomed(); - } - - if let Some(screen) = self.ns_window.screen() { - let frame = self.ns_window.frame(); - let visible_frame = screen.visibleFrame(); - - return (frame.size.width - visible_frame.size.width).abs() < 1.0 - && (frame.size.height - visible_frame.size.height).abs() < 1.0; - } - - // Fallback to original `isZoomed` check - self.ns_window.isZoomed() - } - } - - fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { - let base_mask = shared_state - .saved_style - .take() - .unwrap_or_else(|| unsafe { self.ns_window.styleMask() }); - if shared_state.resizable { - base_mask | NSWindowStyleMask::Resizable - } else { - base_mask & !NSWindowStyleMask::Resizable - } - } - - /// This is called when the window is exiting fullscreen, whether by the - /// user clicking on the green fullscreen button or programmatically by - /// `toggleFullScreen:` - pub(crate) fn restore_state_from_fullscreen(&self) { - trace!("Locked shared state in `restore_state_from_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - - shared_state_lock.fullscreen = None; - - let maximized = shared_state_lock.maximized; - let mask = self.saved_style(&mut *shared_state_lock); - - drop(shared_state_lock); - trace!("Unlocked shared state in `restore_state_from_fullscreen`"); - - self.set_style_mask_async(mask); - self.set_maximized(maximized); - } - - #[inline] - pub fn set_minimized(&self, minimized: bool) { - let is_minimized: bool = unsafe { self.ns_window.isMiniaturized() }; - if is_minimized == minimized { - return; - } - - if minimized { - unsafe { - NSWindow::miniaturize(&self.ns_window, Some(&self.ns_window)); - } - } else { - unsafe { - NSWindow::deminiaturize(&self.ns_window, Some(&self.ns_window)); - } - } - } - - #[inline] - pub fn set_maximized(&self, maximized: bool) { - let is_zoomed = self.is_zoomed(); - if is_zoomed == maximized { - return; - }; - unsafe { - util::set_maximized_async( - &self.ns_window, - is_zoomed, - maximized, - Arc::downgrade(&self.shared_state), - ); - } - } - - #[inline] - pub fn fullscreen(&self) -> Option { - let shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen.clone() - } - - #[inline] - pub fn is_visible(&self) -> bool { - unsafe { self.ns_window.isVisible() } - } - - #[inline] - pub fn is_always_on_top(&self) -> bool { - unsafe { self.ns_window.level() == ffi::kCGFloatingWindowLevelKey } - } - - #[inline] - pub fn is_maximized(&self) -> bool { - self.is_zoomed() - } - - #[inline] - pub fn is_minimized(&self) -> bool { - unsafe { self.ns_window.isMiniaturized() } - } - - #[inline] - pub fn is_resizable(&self) -> bool { - unsafe { msg_send![&self.ns_window, isResizable] } - } - - #[inline] - pub fn is_minimizable(&self) -> bool { - unsafe { msg_send![&self.ns_window, isMiniaturizable] } - } - - #[inline] - pub fn is_maximizable(&self) -> bool { - unsafe { - if let Some(button) = self - .ns_window - .standardWindowButton(NSWindowButton::ZoomButton) - { - button.isEnabled() - } else { - false - } - } - } - - #[inline] - pub fn is_closable(&self) -> bool { - let is_closable: bool = unsafe { msg_send![&self.ns_window, hasCloseBox] }; - is_closable - } - - #[inline] - pub fn is_decorated(&self) -> bool { - self.decorations.load(Ordering::Acquire) - } - - #[inline] - pub fn set_fullscreen(&self, fullscreen: Option) { - let mtm = unsafe { MainThreadMarker::new_unchecked() }; - - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - if shared_state_lock.is_simple_fullscreen { - trace!("Unlocked shared state in `set_fullscreen`"); - return; - } - if shared_state_lock.in_fullscreen_transition { - // We can't set fullscreen here. - // Set fullscreen after transition. - shared_state_lock.target_fullscreen = Some(fullscreen); - trace!("Unlocked shared state in `set_fullscreen`"); - return; - } - let old_fullscreen = shared_state_lock.fullscreen.clone(); - if fullscreen == old_fullscreen { - trace!("Unlocked shared state in `set_fullscreen`"); - return; - } - trace!("Unlocked shared state in `set_fullscreen`"); - drop(shared_state_lock); - - // If the fullscreen is on a different monitor, we must move the window - // to that monitor before we toggle fullscreen (as `toggleFullScreen` - // does not take a screen parameter, but uses the current screen) - if let Some(ref fullscreen) = fullscreen { - let new_screen = match fullscreen { - Fullscreen::Borderless(Some(borderless)) => { - let RootMonitorHandle { inner: monitor } = borderless.clone(); - monitor - } - Fullscreen::Borderless(None) => { - if let Some(current) = self.current_monitor_inner() { - let RootMonitorHandle { inner: monitor } = current; - monitor - } else { - return; - } - } - Fullscreen::Exclusive(RootVideoMode { - video_mode: VideoMode { ref monitor, .. }, - }) => monitor.clone(), - } - .ns_screen() - .unwrap(); - - unsafe { - let old_screen = NSWindow::screen(&self.ns_window); - if old_screen.as_ref() != Some(&new_screen) { - let mut screen_frame: NSRect = msg_send![&new_screen, frame]; - // The coordinate system here has its origin at bottom-left - // and Y goes up - screen_frame.origin.y += screen_frame.size.height; - util::set_frame_top_left_point_async(&self.ns_window, screen_frame.origin); - } - } - } - - if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { - // Note: `enterFullScreenMode:withOptions:` seems to do the exact - // same thing as we're doing here (captures the display, sets the - // video mode, and hides the menu bar and dock), with the exception - // of that I couldn't figure out how to set the display mode with - // it. I think `enterFullScreenMode:withOptions:` is still using the - // older display mode API where display modes were of the type - // `CFDictionary`, but this has changed, so we can't obtain the - // correct parameter for this any longer. Apple's code samples for - // this function seem to just pass in "YES" for the display mode - // parameter, which is not consistent with the docs saying that it - // takes a `NSDictionary`.. - - let display_id = video_mode.monitor().inner.native_identifier(); - - let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; - - if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { - unsafe { - let app = NSApp(mtm); - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); - } - } - - unsafe { - // Fade to black (and wait for the fade to complete) to hide the - // flicker from capturing the display and switching display mode - if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) == ffi::kCGErrorSuccess { - ffi::CGDisplayFade( - fade_token, - 0.3, - ffi::kCGDisplayBlendNormal, - ffi::kCGDisplayBlendSolidColor, - 0.0, - 0.0, - 0.0, - ffi::TRUE, - ); - } - - assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); - } - - unsafe { - let result = ffi::CGDisplaySetDisplayMode( - display_id, - video_mode.video_mode.native_mode.0, - std::ptr::null(), - ); - assert!(result == ffi::kCGErrorSuccess, "failed to set video mode"); - - // After the display has been configured, fade back in - // asynchronously - if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { - ffi::CGDisplayFade( - fade_token, - 0.6, - ffi::kCGDisplayBlendSolidColor, - ffi::kCGDisplayBlendNormal, - 0.0, - 0.0, - 0.0, - ffi::FALSE, - ); - ffi::CGReleaseDisplayFadeReservation(fade_token); - } - } - } - - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen = fullscreen.clone(); - trace!("Unlocked shared state in `set_fullscreen`"); - - match (&old_fullscreen, &fullscreen) { - (&None, &Some(_)) => unsafe { - util::toggle_full_screen_async( - &self.ns_window, - &self.ns_view, - old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), - ); - }, - (&Some(Fullscreen::Borderless(_)), &None) => unsafe { - // State is restored by `window_did_exit_fullscreen` - util::toggle_full_screen_async( - &self.ns_window, - &self.ns_view, - old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), - ); - }, - (&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe { - util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); - // Rest of the state is restored by `window_did_exit_fullscreen` - util::toggle_full_screen_async( - &self.ns_window, - &self.ns_view, - old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), - ); - }, - (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { - // If we're already in fullscreen mode, calling - // `CGDisplayCapture` will place the shielding window on top of - // our window, which results in a black display and is not what - // we want. So, we must place our window on top of the shielding - // window. Unfortunately, this also makes our window be on top - // of the menu bar, and this looks broken, so we must make sure - // that the menu bar is disabled. This is done in the window - // delegate in `window:willUseFullScreenPresentationOptions:`. - let app = NSApp(mtm); - trace!("Locked shared state in `set_fullscreen`"); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); - - let presentation_options = NSApplicationPresentationOptions::FullScreen - | NSApplicationPresentationOptions::HideDock - | NSApplicationPresentationOptions::HideMenuBar; - app.setPresentationOptions(presentation_options); - - self - .ns_window - .setLevel(ffi::CGShieldingWindowLevel() as isize + 1); - }, - ( - &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), - &Some(Fullscreen::Borderless(_)), - ) => unsafe { - let presentation_options = shared_state_lock.save_presentation_opts.unwrap_or_else(|| { - NSApplicationPresentationOptions::FullScreen - | NSApplicationPresentationOptions::AutoHideDock - | NSApplicationPresentationOptions::AutoHideMenuBar - }); - NSApp(mtm).setPresentationOptions(presentation_options); - - util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); - - // Restore the normal window level following the Borderless fullscreen - // `CGShieldingWindowLevel() + 1` hack. - let () = msg_send![ - &self.ns_window, - setLevel: ffi::NSWindowLevel::NSNormalWindowLevel - ]; - }, - _ => {} - } - trace!("Unlocked shared state in `set_fullscreen`"); - } - - #[inline] - pub fn set_decorations(&self, decorations: bool) { - if decorations != self.decorations.load(Ordering::Acquire) { - self.decorations.store(decorations, Ordering::Release); - - let (fullscreen, resizable) = { - trace!("Locked shared state in `set_decorations`"); - let shared_state_lock = self.shared_state.lock().unwrap(); - trace!("Unlocked shared state in `set_decorations`"); - ( - shared_state_lock.fullscreen.is_some(), - shared_state_lock.resizable, - ) - }; - - // If we're in fullscreen mode, we wait to apply decoration changes - // until we're in `window_did_exit_fullscreen`. - if fullscreen { - return; - } - - let new_mask = { - let mut new_mask = if decorations { - NSWindowStyleMask::Closable - | NSWindowStyleMask::Miniaturizable - | NSWindowStyleMask::Resizable - | NSWindowStyleMask::Titled - } else { - NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable - }; - if !resizable { - new_mask &= !NSWindowStyleMask::Resizable; - } - new_mask - }; - self.set_style_mask_async(new_mask); - } - } - - #[inline] - pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - let level = if always_on_bottom { - ffi::NSWindowLevel::BelowNormalWindowLevel - } else { - ffi::NSWindowLevel::NSNormalWindowLevel - }; - unsafe { util::set_level_async(&self.ns_window, level) }; - } - - #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { - let level = if always_on_top { - ffi::NSWindowLevel::NSFloatingWindowLevel - } else { - ffi::NSWindowLevel::NSNormalWindowLevel - }; - unsafe { util::set_level_async(&self.ns_window, level) }; - } - - #[inline] - pub fn set_window_icon(&self, _icon: Option) { - // macOS doesn't have window icons. Though, there is - // `setRepresentedFilename`, but that's semantically distinct and should - // only be used when the window is in some way representing a specific - // file/directory. For instance, Terminal.app uses this for the CWD. - // Anyway, that should eventually be implemented as - // `WindowBuilderExt::with_represented_file` or something, and doesn't - // have anything to do with `set_window_icon`. - // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html - } - - #[inline] - pub fn set_ime_position(&self, spot: Position) { - let scale_factor = self.scale_factor(); - let logical_spot = spot.to_logical(scale_factor); - unsafe { - view::set_ime_position( - &self.ns_view, - *self.input_context, - logical_spot.x, - logical_spot.y, - ); - } - } - - #[inline] - pub fn request_user_attention(&self, request_type: Option) { - let ns_request_type = request_type.map(|ty| match ty { - UserAttentionType::Critical => NSRequestUserAttentionType::CriticalRequest, - UserAttentionType::Informational => NSRequestUserAttentionType::InformationalRequest, - }); - unsafe { - let mtm = MainThreadMarker::new_unchecked(); - if let Some(ty) = ns_request_type { - NSApp(mtm).requestUserAttention(ty); - } - } - } - - #[inline] - // Allow directly accessing the current monitor internally without unwrapping. - pub(crate) fn current_monitor_inner(&self) -> Option { - unsafe { - let screen: Retained = self.ns_window.screen()?; - let desc = NSScreen::deviceDescription(&screen); - let key = ns_string!("NSScreenNumber"); - let value = NSDictionary::objectForKey(&desc, &key).unwrap(); - let display_id: NSUInteger = msg_send![&value, unsignedIntegerValue]; - Some(RootMonitorHandle { - inner: MonitorHandle::new(display_id.try_into().unwrap()), - }) - } - } - - #[inline] - pub fn current_monitor(&self) -> Option { - self.current_monitor_inner() - } - #[inline] - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - monitor::from_point(x, y).map(|inner| RootMonitorHandle { inner }) - } - - #[inline] - pub fn available_monitors(&self) -> VecDeque { - monitor::available_monitors() - } - - #[inline] - pub fn primary_monitor(&self) -> Option { - let monitor = monitor::primary_monitor(); - Some(RootMonitorHandle { inner: monitor }) - } - - #[cfg(feature = "rwh_04")] - #[inline] - pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { - let mut window_handle = rwh_04::AppKitHandle::empty(); - window_handle.ns_window = self.ns_window(); - window_handle.ns_view = self.ns_view(); - rwh_04::RawWindowHandle::AppKit(window_handle) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { - let mut window_handle = rwh_05::AppKitWindowHandle::empty(); - window_handle.ns_window = self.ns_window(); - window_handle.ns_view = self.ns_view(); - rwh_05::RawWindowHandle::AppKit(window_handle) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_window_handle_rwh_06(&self) -> Result { - let window_handle = rwh_06::AppKitWindowHandle::new({ - let ptr = self.ns_view(); - std::ptr::NonNull::new(ptr).expect("Id should never be null") - }); - Ok(rwh_06::RawWindowHandle::AppKit(window_handle)) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::AppKit( - rwh_06::AppKitDisplayHandle::new(), - )) - } - - #[inline] - pub fn theme(&self) -> Theme { - let state = self.shared_state.lock().unwrap(); - state.current_theme - } - - pub fn set_theme(&self, theme: Option) { - set_ns_theme(theme); - let mut state = self.shared_state.lock().unwrap(); - state.current_theme = theme.unwrap_or_else(get_ns_theme); - } - - pub fn set_content_protection(&self, enabled: bool) { - unsafe { - self.ns_window.setSharingType(if enabled { - NSWindowSharingType::None - } else { - NSWindowSharingType::ReadOnly - }); - } - } - - pub fn set_visible_on_all_workspaces(&self, visible: bool) { - unsafe { - let mut collection_behavior = self.ns_window.collectionBehavior(); - if visible { - collection_behavior |= NSWindowCollectionBehavior::CanJoinAllSpaces; - } else { - collection_behavior &= !NSWindowCollectionBehavior::CanJoinAllSpaces; - }; - self.ns_window.setCollectionBehavior(collection_behavior) - } - } - - pub fn set_progress_bar(&self, progress: ProgressBarState) { - set_progress_indicator(progress); - } - - pub fn set_badge_label(&self, label: Option) { - set_badge_label(label); - } -} - -impl WindowExtMacOS for UnownedWindow { - #[inline] - fn ns_window(&self) -> *mut c_void { - &*self.ns_window as *const NSWindow as *mut _ - } - - #[inline] - fn ns_view(&self) -> *mut c_void { - &*self.ns_window.contentView().unwrap() as *const NSView as *mut _ - } - - #[inline] - fn simple_fullscreen(&self) -> bool { - let shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.is_simple_fullscreen - } - - #[inline] - fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { - let mut shared_state_lock = self.shared_state.lock().unwrap(); - - unsafe { - let mtm = MainThreadMarker::new_unchecked(); - let app = NSApp(mtm); - let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); - let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; - - // Do nothing if native fullscreen is active. - if is_native_fullscreen - || (fullscreen && is_simple_fullscreen) - || (!fullscreen && !is_simple_fullscreen) - { - return false; - } - - if fullscreen { - // Remember the original window's settings - // Exclude title bar - shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect( - &self.ns_window, - NSWindow::frame(&self.ns_window), - )); - shared_state_lock.saved_style = Some(self.ns_window.styleMask()); - shared_state_lock.save_presentation_opts = Some(app.presentationOptions()); - - // Tell our window's state that we're in fullscreen - shared_state_lock.is_simple_fullscreen = true; - - // Simulate pre-Lion fullscreen by hiding the dock and menu bar - let presentation_options = NSApplicationPresentationOptions::AutoHideDock - | NSApplicationPresentationOptions::AutoHideMenuBar; - app.setPresentationOptions(presentation_options); - - // Hide the titlebar - util::toggle_style_mask( - &self.ns_window, - &self.ns_view, - NSWindowStyleMask::Titled, - false, - ); - - // Set the window frame to the screen frame size - let screen = self.ns_window.screen().unwrap(); - let screen_frame = NSScreen::frame(&screen); - NSWindow::setFrame_display(&self.ns_window, screen_frame, true); - - // Fullscreen windows can't be resized, minimized, or moved - util::toggle_style_mask( - &self.ns_window, - &self.ns_view, - NSWindowStyleMask::Miniaturizable, - false, - ); - util::toggle_style_mask( - &self.ns_window, - &self.ns_view, - NSWindowStyleMask::Resizable, - false, - ); - NSWindow::setMovable(&self.ns_window, false); - - true - } else { - let new_mask = self.saved_style(&mut *shared_state_lock); - self.set_style_mask_async(new_mask); - shared_state_lock.is_simple_fullscreen = false; - - if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { - app.setPresentationOptions(presentation_opts); - } - - let frame = shared_state_lock.saved_standard_frame(); - NSWindow::setFrame_display(&self.ns_window, frame, true); - NSWindow::setMovable(&self.ns_window, true); - - true - } - } - } - - #[inline] - fn has_shadow(&self) -> bool { - self.ns_window.hasShadow() - } - - #[inline] - fn set_has_shadow(&self, has_shadow: bool) { - self.ns_window.setHasShadow(has_shadow) - } - - #[inline] - fn set_traffic_light_inset>(&self, position: P) { - let position: Position = position.into(); - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let state_ptr: *mut c_void = *(self.ns_view).get_ivar("taoState"); - let state = &mut *(state_ptr as *mut ViewState); - state.traffic_light_inset = Some(position.to_logical(self.scale_factor())); - } - } - - #[inline] - fn set_is_document_edited(&self, edited: bool) { - self.ns_window.setDocumentEdited(edited) - } - - #[inline] - fn is_document_edited(&self) -> bool { - unsafe { self.ns_window.isDocumentEdited() } - } - - #[inline] - fn set_allows_automatic_window_tabbing(&self, enabled: bool) { - let mtm = MainThreadMarker::new().unwrap(); - NSWindow::setAllowsAutomaticWindowTabbing(enabled, mtm) - } - - #[inline] - fn allows_automatic_window_tabbing(&self) -> bool { - let mtm = MainThreadMarker::new().unwrap(); - NSWindow::allowsAutomaticWindowTabbing(mtm) - } - - #[inline] - fn set_tabbing_identifier(&self, identifier: &str) { - unsafe { - self - .ns_window - .setTabbingIdentifier(&NSString::from_str(identifier)); - } - } - - #[inline] - fn tabbing_identifier(&self) -> String { - let tabbing_identifier = NSWindow::tabbingIdentifier(&self.ns_window); - tabbing_identifier.to_string() - } - - #[inline] - fn set_fullsize_content_view(&self, fullsize: bool) { - let mut mask = self.ns_window.styleMask(); - if fullsize { - mask |= NSWindowStyleMask::FullSizeContentView; - } else { - mask &= !NSWindowStyleMask::FullSizeContentView; - } - self.set_style_mask_sync(mask); - } - - #[inline] - fn set_titlebar_transparent(&self, transparent: bool) { - self.ns_window.setTitlebarAppearsTransparent(transparent); - } - - fn set_badge_label(&self, label: Option) { - set_badge_label(label); - } -} - -impl Drop for UnownedWindow { - fn drop(&mut self) { - trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); - // Close the window if it has not yet been closed. - unsafe { util::close_async(&self.ns_window) }; - } -} - -unsafe fn set_min_inner_size(window: &NSWindow, mut min_size: LogicalSize) { - let mut current_rect = NSWindow::frame(window); - let content_rect = NSWindow::contentRectForFrameRect(window, NSWindow::frame(window)); - // Convert from client area size to window size - min_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 - min_size.height += (current_rect.size.height - content_rect.size.height) as f64; - window.setMinSize(NSSize { - width: min_size.width as CGFloat, - height: min_size.height as CGFloat, - }); - // If necessary, resize the window to match constraint - if current_rect.size.width < min_size.width { - current_rect.size.width = min_size.width; - window.setFrame_display(current_rect, false) - } - if current_rect.size.height < min_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - min_size.height; - current_rect.size.height = min_size.height; - window.setFrame_display(current_rect, false) - } -} - -unsafe fn set_max_inner_size(window: &NSWindow, mut max_size: LogicalSize) { - let mut current_rect = NSWindow::frame(window); - let content_rect = NSWindow::contentRectForFrameRect(window, NSWindow::frame(window)); - // Convert from client area size to window size - max_size.width += (current_rect.size.width - content_rect.size.width) as f64; // this tends to be 0 - max_size.height += (current_rect.size.height - content_rect.size.height) as f64; - window.setMaxSize(NSSize { - width: max_size.width as CGFloat, - height: max_size.height as CGFloat, - }); - // If necessary, resize the window to match constraint - if current_rect.size.width > max_size.width { - current_rect.size.width = max_size.width; - window.setFrame_display(current_rect, false) - } - if current_rect.size.height > max_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - max_size.height; - current_rect.size.height = max_size.height; - window.setFrame_display(current_rect, false) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs deleted file mode 100644 index fa6e63085..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - f64, - ffi::CStr, - os::raw::c_void, - sync::{Arc, Weak}, -}; - -use objc2::{ - msg_send, - rc::Retained, - runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, -}; -use objc2_app_kit::{ - self as appkit, NSApplicationPresentationOptions, NSPasteboard, NSView, NSWindow, -}; -use objc2_foundation::{ns_string, NSArray, NSAutoreleasePool, NSString, NSUInteger}; -use once_cell::sync::Lazy; - -use crate::{ - dpi::{LogicalPosition, LogicalSize}, - event::{Event, WindowEvent}, - keyboard::ModifiersState, - platform_impl::platform::{ - app_state::AppState, - event::{EventProxy, EventWrapper}, - ffi::{id, nil, BOOL, NO, YES}, - util::{self, IdRef}, - view::ViewState, - window::{get_ns_theme, get_window_id, UnownedWindow}, - }, - window::{Fullscreen, WindowId}, -}; - -pub struct WindowDelegateState { - ns_window: Retained, // never changes - // We keep this ns_view because we still need its view state for some extern function - // like didResignKey - ns_view: Retained, // never changes - - window: Weak, - - // TODO: It's possible for delegate methods to be called asynchronously, - // causing data races / `RefCell` panics. - - // This is set when WindowBuilder::with_fullscreen was set, - // see comments of `window_did_fail_to_enter_fullscreen` - initial_fullscreen: bool, - - // During `windowDidResize`, we use this to only send Moved if the position changed. - previous_position: Option<(f64, f64)>, - - // Used to prevent redundant events. - previous_scale_factor: f64, - - // Used to prevent resized events from being fired - // when we are using our workaround in the `is_zoomed` function. - is_checking_zoomed_in: bool, -} - -impl WindowDelegateState { - pub fn new(window: &Arc, initial_fullscreen: bool) -> Self { - let scale_factor = window.scale_factor(); - let mut delegate_state = WindowDelegateState { - ns_window: window.ns_window.clone(), - ns_view: window.ns_view.clone(), - window: Arc::downgrade(window), - initial_fullscreen, - previous_position: None, - previous_scale_factor: scale_factor, - is_checking_zoomed_in: false, - }; - if (scale_factor - 1.0).abs() > f64::EPSILON { - delegate_state.emit_static_scale_factor_changed_event(); - } - - delegate_state - } - - fn ns_view(&self) -> Retained { - self.ns_window.contentView().unwrap() - } - - fn with_window(&mut self, callback: F) -> Option - where - F: FnOnce(&UnownedWindow) -> T, - { - self.window.upgrade().map(|ref window| callback(window)) - } - - pub fn emit_event(&mut self, event: WindowEvent<'static>) { - let event = Event::WindowEvent { - window_id: WindowId(get_window_id(&self.ns_window)), - event, - }; - AppState::queue_event(EventWrapper::StaticEvent(event)); - } - - pub fn emit_static_scale_factor_changed_event(&mut self) { - let scale_factor = self.get_scale_factor(); - if (scale_factor - self.previous_scale_factor).abs() < f64::EPSILON { - return; - }; - - self.previous_scale_factor = scale_factor; - let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { - ns_window: self.ns_window.clone(), - suggested_size: self.view_size(), - scale_factor, - }); - AppState::queue_event(wrapper); - } - - pub fn emit_resize_event(&mut self) { - let rect = NSView::frame(&self.ns_view()); - let scale_factor = self.get_scale_factor(); - let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); - let size = logical_size.to_physical(scale_factor); - self.emit_event(WindowEvent::Resized(size)); - } - - fn emit_move_event(&mut self) { - let rect = NSWindow::frame(&self.ns_window); - let x = rect.origin.x as f64; - let y = util::bottom_left_to_top_left(rect); - let moved = self.previous_position != Some((x, y)); - if moved { - self.previous_position = Some((x, y)); - let scale_factor = self.get_scale_factor(); - let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); - self.emit_event(WindowEvent::Moved(physical_pos)); - } - } - - fn get_scale_factor(&self) -> f64 { - NSWindow::backingScaleFactor(&self.ns_window) as f64 - } - - fn view_size(&self) -> LogicalSize { - let ns_size = NSView::frame(&self.ns_view()).size; - LogicalSize::new(ns_size.width as f64, ns_size.height as f64) - } -} - -pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> IdRef { - let state = WindowDelegateState::new(window, initial_fullscreen); - unsafe { - // This is free'd in `dealloc` - let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - let delegate: id = msg_send![WINDOW_DELEGATE_CLASS.0, alloc]; - IdRef::new(msg_send![delegate, initWithTao: state_ptr]) - } -} - -struct WindowDelegateClass(*const Class); -unsafe impl Send for WindowDelegateClass {} -unsafe impl Sync for WindowDelegateClass {} - -static WINDOW_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { - let superclass = class!(NSResponder); - let mut decl = ClassDecl::new( - CStr::from_bytes_with_nul(b"TaoWindowDelegate\0").unwrap(), - superclass, - ) - .unwrap(); - - decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _)); - decl.add_method( - sel!(initWithTao:), - init_with_tao as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(markIsCheckingZoomedIn), - mark_is_checking_zoomed_in as extern "C" fn(_, _), - ); - decl.add_method( - sel!(clearIsCheckingZoomedIn), - clear_is_checking_zoomed_in as extern "C" fn(_, _), - ); - - decl.add_method( - sel!(windowShouldClose:), - window_should_close as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(windowWillClose:), - window_will_close as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidResize:), - window_did_resize as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidMove:), - window_did_move as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidChangeBackingProperties:), - window_did_change_backing_properties as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidBecomeKey:), - window_did_become_key as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidResignKey:), - window_did_resign_key as extern "C" fn(_, _, _), - ); - - decl.add_method( - sel!(draggingEntered:), - dragging_entered as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(prepareForDragOperation:), - prepare_for_drag_operation as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(performDragOperation:), - perform_drag_operation as extern "C" fn(_, _, _) -> _, - ); - decl.add_method( - sel!(concludeDragOperation:), - conclude_drag_operation as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(draggingExited:), - dragging_exited as extern "C" fn(_, _, _), - ); - - decl.add_method( - sel!(window:willUseFullScreenPresentationOptions:), - window_will_use_fullscreen_presentation_options as extern "C" fn(_, _, _, _) -> _, - ); - decl.add_method( - sel!(windowDidEnterFullScreen:), - window_did_enter_fullscreen as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowWillEnterFullScreen:), - window_will_enter_fullscreen as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidExitFullScreen:), - window_did_exit_fullscreen as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowWillExitFullScreen:), - window_will_exit_fullscreen as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(windowDidFailToEnterFullScreen:), - window_did_fail_to_enter_fullscreen as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(effectiveAppearanceDidChange:), - effective_appearance_did_change as extern "C" fn(_, _, _), - ); - decl.add_method( - sel!(effectiveAppearanceDidChangedOnMainThread:), - effective_appearance_did_changed_on_main_thread as extern "C" fn(_, _, _), - ); - - decl.add_ivar::<*mut c_void>(CStr::from_bytes_with_nul(b"taoState\0").unwrap()); - WindowDelegateClass(decl.register()) -}); - -// This function is definitely unsafe, but labeling that would increase -// boilerplate and wouldn't really clarify anything... -fn with_state T, T>(this: &Object, callback: F) { - #[allow(deprecated)] // TODO: Use define_class! - let state_ptr = unsafe { - let state_ptr: *mut c_void = *this.get_ivar("taoState"); - &mut *(state_ptr as *mut WindowDelegateState) - }; - callback(state_ptr); -} - -extern "C" fn dealloc(this: &Object, _sel: Sel) { - with_state(this, |state| unsafe { - drop(Box::from_raw(state as *mut WindowDelegateState)); - }); -} - -extern "C" fn init_with_tao(this: &Object, _sel: Sel, state: *mut c_void) -> id { - #[allow(deprecated)] // TODO: Use define_class! - unsafe { - let this: id = msg_send![this, init]; - if this != nil { - *(*this).get_mut_ivar("taoState") = state; - with_state(&*this, |state| { - let () = msg_send![&state.ns_window, setDelegate: this]; - }); - } - - let notification_center: &Object = - msg_send![class!(NSDistributedNotificationCenter), defaultCenter]; - let notification_name = ns_string!("AppleInterfaceThemeChangedNotification"); - let _: () = msg_send![ - notification_center, - addObserver: this - selector: sel!(effectiveAppearanceDidChange:) - name: &*notification_name - object: nil - ]; - - this - } -} - -extern "C" fn mark_is_checking_zoomed_in(this: &Object, _sel: Sel) { - with_state(&*this, |state| { - state.is_checking_zoomed_in = true; - }); -} - -extern "C" fn clear_is_checking_zoomed_in(this: &Object, _sel: Sel) { - with_state(&*this, |state| { - state.is_checking_zoomed_in = false; - }); -} - -extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { - trace!("Triggered `windowShouldClose:`"); - with_state(this, |state| state.emit_event(WindowEvent::CloseRequested)); - trace!("Completed `windowShouldClose:`"); - NO -} - -extern "C" fn window_will_close(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowWillClose:`"); - with_state(this, |state| unsafe { - // `setDelegate:` retains the previous value and then autoreleases it - let _pool = NSAutoreleasePool::new(); - // Since El Capitan, we need to be careful that delegate methods can't - // be called after the window closes. - let () = msg_send![&state.ns_window, setDelegate: nil]; - state.emit_event(WindowEvent::Destroyed); - }); - trace!("Completed `windowWillClose:`"); -} - -extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidResize:`"); - with_state(this, |state| { - if !state.is_checking_zoomed_in { - state.emit_resize_event(); - state.emit_move_event(); - } - }); - trace!("Completed `windowDidResize:`"); -} - -// This won't be triggered if the move was part of a resize. -extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidMove:`"); - with_state(this, |state| { - state.emit_move_event(); - }); - trace!("Completed `windowDidMove:`"); -} - -extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidChangeBackingProperties:`"); - with_state(this, |state| { - state.emit_static_scale_factor_changed_event(); - }); - trace!("Completed `windowDidChangeBackingProperties:`"); -} - -extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidBecomeKey:`"); - with_state(this, |state| { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - state.emit_event(WindowEvent::Focused(true)); - }); - trace!("Completed `windowDidBecomeKey:`"); -} - -extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidResignKey:`"); - with_state(this, |state| { - // It happens rather often, e.g. when the user is Cmd+Tabbing, that the - // NSWindowDelegate will receive a didResignKey event despite no event - // being received when the modifiers are released. This is because - // flagsChanged events are received by the NSView instead of the - // NSWindowDelegate, and as a result a tracked modifiers state can quite - // easily fall out of synchrony with reality. This requires us to emit - // a synthetic ModifiersChanged event when we lose focus. - // - // Here we (very unsafely) acquire the taoState (a ViewState) from the - // Object referenced by state.ns_view (an IdRef, which is dereferenced - // to an id) - #[allow(deprecated)] // TODO: Use define_class! - let view_state: &mut ViewState = unsafe { - let ns_view: &Object = &state.ns_view; - let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); - &mut *(state_ptr as *mut ViewState) - }; - - // Both update the state and emit a ModifiersChanged event. - if !view_state.modifiers.is_empty() { - view_state.modifiers = ModifiersState::empty(); - state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); - } - - state.emit_event(WindowEvent::Focused(false)); - }); - trace!("Completed `windowDidResignKey:`"); -} - -/// Invoked when the dragged image enters destination bounds or frame -extern "C" fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { - trace!("Triggered `draggingEntered:`"); - - use std::path::PathBuf; - - let pb: Retained = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = - unsafe { NSPasteboard::propertyListForType(&pb, appkit::NSFilenamesPboardType) }.unwrap(); - - for file in unsafe { Retained::cast_unchecked::(filenames) } { - let file = unsafe { Retained::cast_unchecked::(file) }; - - unsafe { - let f = NSString::UTF8String(&file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); - - with_state(this, |state| { - state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); - }); - } - } - - trace!("Completed `draggingEntered:`"); - YES -} - -/// Invoked when the image is released -extern "C" fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { - trace!("Triggered `prepareForDragOperation:`"); - trace!("Completed `prepareForDragOperation:`"); - YES -} - -/// Invoked after the released image has been removed from the screen -extern "C" fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { - trace!("Triggered `performDragOperation:`"); - - use std::path::PathBuf; - - let pb: Retained = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = - unsafe { NSPasteboard::propertyListForType(&pb, appkit::NSFilenamesPboardType) }.unwrap(); - - for file in unsafe { Retained::cast_unchecked::(filenames) } { - let file = unsafe { Retained::cast_unchecked::(file) }; - - unsafe { - let f = NSString::UTF8String(&file); - let path = CStr::from_ptr(f).to_string_lossy().into_owned(); - - with_state(this, |state| { - state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); - }); - } - } - - trace!("Completed `performDragOperation:`"); - YES -} - -/// Invoked when the dragging operation is complete -extern "C" fn conclude_drag_operation(_: &Object, _: Sel, _: id) { - trace!("Triggered `concludeDragOperation:`"); - trace!("Completed `concludeDragOperation:`"); -} - -/// Invoked when the dragging operation is cancelled -extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { - trace!("Triggered `draggingExited:`"); - with_state(this, |state| { - state.emit_event(WindowEvent::HoveredFileCancelled) - }); - trace!("Completed `draggingExited:`"); -} - -/// Invoked when before enter fullscreen -extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowWillEnterFullscreen:`"); - - with_state(this, |state| { - state.with_window(|window| { - trace!("Locked shared state in `window_will_enter_fullscreen`"); - let mut shared_state = window.shared_state.lock().unwrap(); - shared_state.maximized = window.is_zoomed(); - match shared_state.fullscreen { - // Exclusive mode sets the state in `set_fullscreen` as the user - // can't enter exclusive mode by other means (like the - // fullscreen button on the window decorations) - Some(Fullscreen::Exclusive(_)) => (), - // `window_will_enter_fullscreen` was triggered and we're already - // in fullscreen, so we must've reached here by `set_fullscreen` - // as it updates the state - Some(Fullscreen::Borderless(_)) => (), - // Otherwise, we must've reached fullscreen by the user clicking - // on the green fullscreen button. Update state! - None => { - let current_monitor = window.current_monitor_inner(); - shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) - } - } - shared_state.in_fullscreen_transition = true; - trace!("Unlocked shared state in `window_will_enter_fullscreen`"); - }) - }); - trace!("Completed `windowWillEnterFullscreen:`"); -} - -/// Invoked when before exit fullscreen -extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowWillExitFullScreen:`"); - - with_state(this, |state| { - state.with_window(|window| { - trace!("Locked shared state in `window_will_exit_fullscreen`"); - let mut shared_state = window.shared_state.lock().unwrap(); - shared_state.in_fullscreen_transition = true; - trace!("Unlocked shared state in `window_will_exit_fullscreen`"); - }); - }); - trace!("Completed `windowWillExitFullScreen:`"); -} - -extern "C" fn window_will_use_fullscreen_presentation_options( - this: &Object, - _: Sel, - _: id, - proposed_options: NSUInteger, -) -> NSUInteger { - // Generally, games will want to disable the menu bar and the dock. Ideally, - // this would be configurable by the user. Unfortunately because of our - // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is - // placed on top of the menu bar in exclusive fullscreen mode. This looks - // broken so we always disable the menu bar in exclusive fullscreen. We may - // still want to make this configurable for borderless fullscreen. Right now - // we don't, for consistency. If we do, it should be documented that the - // user-provided options are ignored in exclusive fullscreen. - let mut options: NSUInteger = proposed_options; - with_state(this, |state| { - state.with_window(|window| { - trace!("Locked shared state in `window_will_use_fullscreen_presentation_options`"); - let shared_state = window.shared_state.lock().unwrap(); - if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { - options = (NSApplicationPresentationOptions::FullScreen - | NSApplicationPresentationOptions::HideDock - | NSApplicationPresentationOptions::HideMenuBar) - .bits(); - } - trace!("Unlocked shared state in `window_will_use_fullscreen_presentation_options`"); - }) - }); - - options -} - -/// Invoked when entered fullscreen -extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidEnterFullscreen:`"); - with_state(this, |state| { - state.initial_fullscreen = false; - state.with_window(|window| { - trace!("Locked shared state in `window_did_enter_fullscreen`"); - let mut shared_state = window.shared_state.lock().unwrap(); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - trace!("Unlocked shared state in `window_did_enter_fullscreen`"); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }); - state.emit_resize_event(); - state.emit_move_event(); - }); - trace!("Completed `windowDidEnterFullscreen:`"); -} - -/// Invoked when exited fullscreen -extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidExitFullscreen:`"); - with_state(this, |state| { - state.with_window(|window| { - window.restore_state_from_fullscreen(); - trace!("Locked shared state in `window_did_exit_fullscreen`"); - let mut shared_state = window.shared_state.lock().unwrap(); - shared_state.in_fullscreen_transition = false; - let target_fullscreen = shared_state.target_fullscreen.take(); - trace!("Unlocked shared state in `window_did_exit_fullscreen`"); - drop(shared_state); - if let Some(target_fullscreen) = target_fullscreen { - window.set_fullscreen(target_fullscreen); - } - }); - state.emit_resize_event(); - state.emit_move_event(); - }); - trace!("Completed `windowDidExitFullscreen:`"); -} - -/// Invoked when fail to enter fullscreen -/// -/// When this window launch from a fullscreen app (e.g. launch from VS Code -/// terminal), it creates a new virtual destkop and a transition animation. -/// This animation takes one second and cannot be disable without -/// elevated privileges. In this animation time, all toggleFullscreen events -/// will be failed. In this implementation, we will try again by using -/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. -/// It should be fine as we only do this at initialzation (i.e with_fullscreen -/// was set). -/// -/// From Apple doc: -/// In some cases, the transition to enter full-screen mode can fail, -/// due to being in the midst of handling some other animation or user gesture. -/// This method indicates that there was an error, and you should clean up any -/// work you may have done to prepare to enter full-screen mode. -extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidFailToEnterFullscreen:`"); - with_state(this, |state| { - state.with_window(|window| { - trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`"); - let mut shared_state = window.shared_state.lock().unwrap(); - shared_state.in_fullscreen_transition = false; - shared_state.target_fullscreen = None; - trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`"); - }); - if state.initial_fullscreen { - let _: () = unsafe { - msg_send![ - &state.ns_window, - performSelector:sel!(toggleFullScreen:), - withObject:nil, - afterDelay: 0.5, - ] - }; - } else { - state.with_window(|window| window.restore_state_from_fullscreen()); - } - }); - trace!("Completed `windowDidFailToEnterFullscreen:`"); -} - -// Observe theme change -extern "C" fn effective_appearance_did_change(this: &Object, _: Sel, _: id) { - trace!("Triggered `effectiveAppearDidChange:`"); - unsafe { - let _: () = msg_send![this, performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:), withObject:nil, waitUntilDone:false]; - } -} -extern "C" fn effective_appearance_did_changed_on_main_thread(this: &Object, _: Sel, _: id) { - with_state(this, |state| { - let theme = get_ns_theme(); - let current_theme = state.window.upgrade().map(|w| { - let mut state = w.shared_state.lock().unwrap(); - let current_theme = state.current_theme; - state.current_theme = theme; - current_theme - }); - if current_theme != Some(theme) { - state.emit_event(WindowEvent::ThemeChanged(theme)); - } - }); - trace!("Completed `effectiveAppearDidChange:`"); -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs deleted file mode 100644 index e8b62af96..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(target_os = "windows")] -#[path = "windows/mod.rs"] -mod platform; -#[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -#[path = "linux/mod.rs"] -mod platform; -#[cfg(target_os = "macos")] -#[path = "macos/mod.rs"] -mod platform; -#[cfg(target_os = "android")] -#[path = "android/mod.rs"] -mod platform; -#[cfg(target_os = "ios")] -#[path = "ios/mod.rs"] -mod platform; - -pub use platform::*; - -#[cfg(all( - not(target_os = "ios"), - not(target_os = "windows"), - not(target_os = "linux"), - not(target_os = "macos"), - not(target_os = "android"), - not(target_os = "dragonfly"), - not(target_os = "freebsd"), - not(target_os = "netbsd"), - not(target_os = "openbsd"), -))] -compile_error!("The platform you're compiling for is not supported by tao"); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs deleted file mode 100644 index 2a0189aac..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -#![allow(non_snake_case)] - -use once_cell::sync::Lazy; -/// This is a simple implementation of support for Windows Dark Mode, -/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode -use windows::{ - core::{s, w, BOOL, PCSTR, PSTR}, - Win32::{ - Foundation::{ERROR_SUCCESS, HANDLE, HMODULE, HWND, LPARAM, WPARAM}, - Graphics::Dwm::{DwmSetWindowAttribute, DWMWINDOWATTRIBUTE}, - System::{ - LibraryLoader::*, - Registry::{RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD}, - }, - UI::{Accessibility::*, Input::KeyboardAndMouse::GetActiveWindow, WindowsAndMessaging::*}, - }, -}; - -use std::ffi::c_void; - -use crate::window::Theme; - -use super::util; - -static HUXTHEME: Lazy = - Lazy::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); - -static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { - // We won't try to do anything for windows versions < 17763 - // (Windows 10 October 2018 update) - let v = *util::WIN_VERSION; - v.major == 10 && v.minor == 0 && v.build >= 17763 -}); - -pub fn allow_dark_mode_for_app(is_dark_mode: bool) { - if *DARK_MODE_SUPPORTED { - const UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL: u16 = 135; - type AllowDarkModeForApp = unsafe extern "system" fn(bool) -> bool; - static ALLOW_DARK_MODE_FOR_APP: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } - - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); - - #[repr(C)] - enum PreferredAppMode { - Default, - AllowDark, - // ForceDark, - // ForceLight, - // Max, - } - const UXTHEME_SETPREFERREDAPPMODE_ORDINAL: u16 = 135; - type SetPreferredAppMode = unsafe extern "system" fn(PreferredAppMode) -> PreferredAppMode; - static SET_PREFERRED_APP_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } - - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); - - if util::WIN_VERSION.build < 18362 { - if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP { - unsafe { _allow_dark_mode_for_app(is_dark_mode) }; - } - } else if let Some(_set_preferred_app_mode) = *SET_PREFERRED_APP_MODE { - let mode = if is_dark_mode { - PreferredAppMode::AllowDark - } else { - PreferredAppMode::Default - }; - unsafe { _set_preferred_app_mode(mode) }; - } - - refresh_immersive_color_policy_state(); - } -} - -fn refresh_immersive_color_policy_state() { - const UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL: u16 = 104; - type RefreshImmersiveColorPolicyState = unsafe extern "system" fn(); - static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: Lazy> = - Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } - - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); - - if let Some(_refresh_immersive_color_policy_state) = *REFRESH_IMMERSIVE_COLOR_POLICY_STATE { - unsafe { _refresh_immersive_color_policy_state() } - } -} - -/// Attempt to set a theme on a window, if necessary. -/// Returns the theme that was picked -pub fn try_window_theme( - hwnd: HWND, - preferred_theme: Option, - redraw_title_bar: bool, -) -> Theme { - if *DARK_MODE_SUPPORTED { - let is_dark_mode = match preferred_theme { - Some(theme) => theme == Theme::Dark, - None => should_use_dark_mode(), - }; - - let theme = match is_dark_mode { - true => Theme::Dark, - false => Theme::Light, - }; - - refresh_titlebar_theme_color(hwnd, is_dark_mode, redraw_title_bar); - - theme - } else { - Theme::Light - } -} - -pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { - const UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL: u16 = 133; - type AllowDarkModeForWindow = unsafe extern "system" fn(HWND, bool) -> bool; - static ALLOW_DARK_MODE_FOR_WINDOW: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } - - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); - - if *DARK_MODE_SUPPORTED { - if let Some(_allow_dark_mode_for_window) = *ALLOW_DARK_MODE_FOR_WINDOW { - unsafe { _allow_dark_mode_for_window(hwnd, is_dark_mode) }; - } - } -} - -fn refresh_titlebar_theme_color(hwnd: HWND, is_dark_mode: bool, redraw_title_bar: bool) { - if util::WIN_VERSION.build < 17763 { - let mut is_dark_mode_bigbool: i32 = is_dark_mode.into(); - unsafe { - let _ = SetPropW( - hwnd, - w!("UseImmersiveDarkModeColors"), - Some(HANDLE(&mut is_dark_mode_bigbool as *mut _ as _)), - ); - } - } else { - // https://github.com/MicrosoftDocs/sdk-api/pull/966/files - let dwmwa_use_immersive_dark_mode = if util::WIN_VERSION.build > 18985 { - DWMWINDOWATTRIBUTE(20) - } else { - DWMWINDOWATTRIBUTE(19) - }; - let dark_mode = BOOL::from(is_dark_mode); - unsafe { - let _ = DwmSetWindowAttribute( - hwnd, - dwmwa_use_immersive_dark_mode, - &dark_mode as *const BOOL as *const c_void, - std::mem::size_of::() as u32, - ); - if redraw_title_bar { - if GetActiveWindow() == hwnd { - DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default()); - DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default()); - } else { - DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default()); - DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default()); - } - } - } - } -} - -fn should_use_dark_mode() -> bool { - should_apps_use_dark_mode() && !is_high_contrast() -} - -fn should_apps_use_dark_mode() -> bool { - if let Some(apps_use_light_theme) = read_apps_use_light_theme() { - return !apps_use_light_theme; - } - // This undocumented method `ShouldAppsUseDarkMode` may return - // incorrect values on Windows 11. - // See https://github.com/tauri-apps/tao/pull/1165 - const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: u16 = 132; - type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; - static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } - - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); - - SHOULD_APPS_USE_DARK_MODE - .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) - .unwrap_or(false) -} - -fn read_apps_use_light_theme() -> Option { - let mut data: u32 = 0; - let mut data_size = std::mem::size_of::() as u32; - let status = unsafe { - RegGetValueW( - HKEY_CURRENT_USER, - w!(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"), - w!("AppsUseLightTheme"), - RRF_RT_REG_DWORD, - None, - Some(&mut data as *mut _ as _), - Some(&mut data_size), - ) - }; - if status == ERROR_SUCCESS { - Some(data != 0) - } else { - None - } -} - -fn is_high_contrast() -> bool { - const HCF_HIGHCONTRASTON: u32 = 1; - - let mut hc = HIGHCONTRASTA { - cbSize: 0, - dwFlags: Default::default(), - lpszDefaultScheme: PSTR::null(), - }; - - let ok = unsafe { - SystemParametersInfoA( - SPI_GETHIGHCONTRAST, - std::mem::size_of_val(&hc) as _, - Some(&mut hc as *mut _ as _), - Default::default(), - ) - }; - - ok.is_ok() && (HCF_HIGHCONTRASTON & hc.dwFlags.0) != 0 -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs deleted file mode 100644 index e4eb427c7..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![allow(non_snake_case, unused_unsafe)] - -use std::sync::Once; - -use windows::Win32::{ - Foundation::HWND, - Graphics::Gdi::*, - UI::{HiDpi::*, WindowsAndMessaging::*}, -}; - -use crate::platform_impl::platform::util::{ - ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE, - SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT, -}; - -pub fn become_dpi_aware() { - static ENABLE_DPI_AWARENESS: Once = Once::new(); - ENABLE_DPI_AWARENESS.call_once(|| { - unsafe { - if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT { - // We are on Windows 10 Anniversary Update (1607) or later. - if !SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).as_bool() { - // V2 only works with Windows 10 Creators Update (1703). Try using the older - // V1 if we can't set V2. - let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); - } - } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { - // We are on Windows 8.1 or later. - let _ = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { - // We are on Vista or later. - let _ = SetProcessDPIAware(); - } - } - }); -} - -pub fn enable_non_client_dpi_scaling(hwnd: HWND) { - unsafe { - if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { - let _ = EnableNonClientDpiScaling(hwnd); - } - } -} - -pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option { - unsafe { - if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { - // We are on Windows 8.1 or later. - let mut dpi_x = 0; - let mut dpi_y = 0; - if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { - // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to - // record one of the values to determine the DPI and respond appropriately". - // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx - return Some(dpi_x); - } - } - } - None -} - -pub fn dpi_to_scale_factor(dpi: u32) -> f64 { - dpi as f64 / USER_DEFAULT_SCREEN_DPI as f64 -} - -pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { - let hdc = GetDC(Some(hwnd)); - if hdc.is_invalid() { - panic!("[tao] `GetDC` returned null!"); - } - if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { - // We are on Windows 10 Anniversary Update (1607) or later. - match GetDpiForWindow(hwnd) { - 0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid - dpi => dpi, - } - } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { - // We are on Windows 8.1 or later. - let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - if monitor.is_invalid() { - return USER_DEFAULT_SCREEN_DPI; - } - - let mut dpi_x = 0; - let mut dpi_y = 0; - if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { - dpi_x - } else { - USER_DEFAULT_SCREEN_DPI - } - } else { - // We are on Vista or later. - if IsProcessDPIAware().as_bool() { - // If the process is DPI aware, then scaling must be handled by the application using - // this DPI value. - GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 - } else { - // If the process is DPI unaware, then scaling is performed by the OS; we thus return - // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the - // application and the WM. - USER_DEFAULT_SCREEN_DPI - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs deleted file mode 100644 index d2685cd12..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{cell::UnsafeCell, ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf, ptr}; - -use windows::{ - core::implement, - Win32::{ - Foundation::{self as win32f, HWND, POINTL}, - System::{ - Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, - Ole::{ - IDropTarget, IDropTarget_Impl, CF_HDROP, DROPEFFECT, DROPEFFECT_COPY, DROPEFFECT_NONE, - }, - SystemServices::MODIFIERKEYS_FLAGS, - }, - UI::Shell::{DragFinish, DragQueryFileW, HDROP}, - }, -}; - -use crate::platform_impl::platform::WindowId; - -use crate::{event::Event, window::WindowId as SuperWindowId}; - -#[implement(IDropTarget)] -pub struct FileDropHandler { - window: HWND, - send_event: Box)>, - cursor_effect: UnsafeCell, - hovered_is_valid: UnsafeCell, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ -} - -impl FileDropHandler { - pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { - Self { - window, - send_event, - cursor_effect: DROPEFFECT_NONE.into(), - hovered_is_valid: false.into(), - } - } - - unsafe fn iterate_filenames( - data_obj: windows_core::Ref<'_, IDataObject>, - callback: F, - ) -> Option - where - F: Fn(PathBuf), - { - let drop_format = FORMATETC { - cfFormat: CF_HDROP.0, - ptd: ptr::null_mut(), - dwAspect: DVASPECT_CONTENT.0, - lindex: -1, - tymed: TYMED_HGLOBAL.0 as u32, - }; - - match data_obj - .as_ref() - .expect("Received null IDataObject") - .GetData(&drop_format) - { - Ok(medium) => { - let hglobal = medium.u.hGlobal; - let hdrop = HDROP(hglobal.0 as _); - - // The second parameter (0xFFFFFFFF) instructs the function to return the item count - let mut lpsz_file = []; - let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, Some(&mut lpsz_file)); - - for i in 0..item_count { - // Get the length of the path string NOT including the terminating null character. - // Previously, this was using a fixed size array of MAX_PATH length, but the - // Windows API allows longer paths under certain circumstances. - let character_count = DragQueryFileW(hdrop, i, Some(&mut lpsz_file)) as usize; - let str_len = character_count + 1; - - // Fill path_buf with the null-terminated file name - let mut path_buf = Vec::with_capacity(str_len); - DragQueryFileW(hdrop, i, std::mem::transmute(path_buf.spare_capacity_mut())); - path_buf.set_len(str_len); - - callback(OsString::from_wide(&path_buf[0..character_count]).into()); - } - - Some(hdrop) - } - Err(error) => { - debug!( - "{}", - match error.code() { - win32f::DV_E_FORMATETC => { - // If the dropped item is not a file this error will occur. - // In this case it is OK to return without taking further action. - "Error occured while processing dropped/hovered item: item is not a file." - } - _ => "Unexpected error occured while processing dropped/hovered item.", - } - ); - None - } - } - } -} - -#[allow(non_snake_case)] -impl IDropTarget_Impl for FileDropHandler_Impl { - fn DragEnter( - &self, - pDataObj: windows_core::Ref<'_, IDataObject>, - _grfKeyState: MODIFIERKEYS_FLAGS, - _pt: &POINTL, - pdwEffect: *mut DROPEFFECT, - ) -> windows::core::Result<()> { - use crate::event::WindowEvent::HoveredFile; - unsafe { - let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { - (self.send_event)(Event::WindowEvent { - window_id: SuperWindowId(WindowId(self.window.0 as _)), - event: HoveredFile(filename), - }); - }); - let hovered_is_valid = hdrop.is_some(); - let cursor_effect = if hovered_is_valid { - DROPEFFECT_COPY - } else { - DROPEFFECT_NONE - }; - *self.hovered_is_valid.get() = hovered_is_valid; - *self.cursor_effect.get() = cursor_effect; - *pdwEffect = cursor_effect; - } - Ok(()) - } - - fn DragOver( - &self, - _grfKeyState: MODIFIERKEYS_FLAGS, - _pt: &POINTL, - pdwEffect: *mut DROPEFFECT, - ) -> windows::core::Result<()> { - unsafe { - *pdwEffect = *self.cursor_effect.get(); - } - Ok(()) - } - - fn DragLeave(&self) -> windows::core::Result<()> { - use crate::event::WindowEvent::HoveredFileCancelled; - if unsafe { *self.hovered_is_valid.get() } { - (self.send_event)(Event::WindowEvent { - window_id: SuperWindowId(WindowId(self.window.0 as _)), - event: HoveredFileCancelled, - }); - } - Ok(()) - } - - fn Drop( - &self, - pDataObj: windows_core::Ref<'_, IDataObject>, - _grfKeyState: MODIFIERKEYS_FLAGS, - _pt: &POINTL, - _pdwEffect: *mut DROPEFFECT, - ) -> windows::core::Result<()> { - use crate::event::WindowEvent::DroppedFile; - unsafe { - let hdrop = FileDropHandler::iterate_filenames(pDataObj, |filename| { - (self.send_event)(Event::WindowEvent { - window_id: SuperWindowId(WindowId(self.window.0 as _)), - event: DroppedFile(filename), - }); - }); - if let Some(hdrop) = hdrop { - DragFinish(hdrop); - } - } - Ok(()) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs deleted file mode 100644 index b2fa29cdf..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - char, - os::raw::c_int, - ptr, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, -}; - -use crate::event::{ModifiersState, ScanCode, VirtualKeyCode}; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, WPARAM}, - UI::{ - Input::KeyboardAndMouse::*, - TextServices::HKL, - WindowsAndMessaging::{self as win32wm, *}, - }, -}; - -fn key_pressed(vkey: c_int) -> bool { - unsafe { (GetKeyState(vkey) & (1 << 15)) == (1 << 15) } -} - -pub fn get_key_mods() -> ModifiersState { - let filter_out_altgr = layout_uses_altgr() && key_pressed(VK_RMENU); - - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); - mods.set( - ModifiersState::CTRL, - key_pressed(VK_CONTROL) && !filter_out_altgr, - ); - mods.set( - ModifiersState::ALT, - key_pressed(VK_MENU) && !filter_out_altgr, - ); - mods.set( - ModifiersState::LOGO, - key_pressed(VK_LWIN) || key_pressed(VK_RWIN), - ); - mods -} - -bitflags! { - #[derive(Default)] - pub struct ModifiersStateSide: u32 { - const LSHIFT = 0b010 << 0; - const RSHIFT = 0b001 << 0; - - const LCTRL = 0b010 << 3; - const RCTRL = 0b001 << 3; - - const LALT = 0b010 << 6; - const RALT = 0b001 << 6; - - const LLOGO = 0b010 << 9; - const RLOGO = 0b001 << 9; - } -} - -impl ModifiersStateSide { - pub fn filter_out_altgr(&self) -> ModifiersStateSide { - match layout_uses_altgr() && self.contains(Self::RALT) { - false => *self, - true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT), - } - } -} - -impl From for ModifiersState { - fn from(side: ModifiersStateSide) -> Self { - let mut state = ModifiersState::default(); - state.set( - Self::SHIFT, - side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT), - ); - state.set( - Self::CTRL, - side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL), - ); - state.set( - Self::ALT, - side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT), - ); - state.set( - Self::LOGO, - side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO), - ); - state - } -} - -pub fn get_pressed_keys() -> impl Iterator { - let mut keyboard_state = vec![0u8; 256]; - unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) }; - keyboard_state - .into_iter() - .enumerate() - .filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit - .map(|(i, _)| i as c_int) -} - -unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { - let mut unicode_bytes = [0u16; 5]; - let len = ToUnicodeEx( - v_key, - 0, - keyboard_state.as_ptr(), - unicode_bytes.as_mut_ptr(), - unicode_bytes.len() as _, - 0, - hkl, - ); - if len >= 1 { - char::decode_utf16(unicode_bytes.iter().cloned()) - .next() - .and_then(|c| c.ok()) - } else { - None - } -} - -/// Figures out if the keyboard layout has an AltGr key instead of an Alt key. -/// -/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So, -/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every -/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If -/// pressing AltGr outputs characters that are different from the standard characters, the layout -/// uses AltGr. Otherwise, it doesn't. -/// -/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416 -fn layout_uses_altgr() -> bool { - unsafe { - static ACTIVE_LAYOUT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - static USES_ALTGR: AtomicBool = AtomicBool::new(false); - - let hkl = GetKeyboardLayout(0); - let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst); - - if hkl == old_hkl { - return USES_ALTGR.load(Ordering::SeqCst); - } - - let mut keyboard_state_altgr = [0u8; 256]; - // AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses - // we have to emulate to do an AltGr test. - keyboard_state_altgr[VK_MENU as usize] = 0x80; - keyboard_state_altgr[VK_CONTROL as usize] = 0x80; - - let keyboard_state_empty = [0u8; 256]; - - for v_key in 0..=255 { - let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl); - let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl); - if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) { - if noaltgr != altgr { - USES_ALTGR.store(true, Ordering::SeqCst); - return true; - } - } - } - - USES_ALTGR.store(false, Ordering::SeqCst); - false - } -} - -pub fn vkey_to_tao_vkey(vkey: u32) -> Option { - // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - match vkey { - //win32wm::VK_LBUTTON => Some(VirtualKeyCode::Lbutton), - //win32wm::VK_RBUTTON => Some(VirtualKeyCode::Rbutton), - //win32wm::VK_CANCEL => Some(VirtualKeyCode::Cancel), - //win32wm::VK_MBUTTON => Some(VirtualKeyCode::Mbutton), - //win32wm::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1), - //win32wm::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2), - win32wm::VK_BACK => Some(VirtualKeyCode::Back), - win32wm::VK_TAB => Some(VirtualKeyCode::Tab), - //win32wm::VK_CLEAR => Some(VirtualKeyCode::Clear), - win32wm::VK_RETURN => Some(VirtualKeyCode::Return), - win32wm::VK_LSHIFT => Some(VirtualKeyCode::LShift), - win32wm::VK_RSHIFT => Some(VirtualKeyCode::RShift), - win32wm::VK_LCONTROL => Some(VirtualKeyCode::LControl), - win32wm::VK_RCONTROL => Some(VirtualKeyCode::RControl), - win32wm::VK_LMENU => Some(VirtualKeyCode::LAlt), - win32wm::VK_RMENU => Some(VirtualKeyCode::RAlt), - win32wm::VK_PAUSE => Some(VirtualKeyCode::Pause), - win32wm::VK_CAPITAL => Some(VirtualKeyCode::Capital), - win32wm::VK_KANA => Some(VirtualKeyCode::Kana), - //win32wm::VK_HANGUEL => Some(VirtualKeyCode::Hanguel), - //win32wm::VK_HANGUL => Some(VirtualKeyCode::Hangul), - //win32wm::VK_JUNJA => Some(VirtualKeyCode::Junja), - //win32wm::VK_FINAL => Some(VirtualKeyCode::Final), - //win32wm::VK_HANJA => Some(VirtualKeyCode::Hanja), - win32wm::VK_KANJI => Some(VirtualKeyCode::Kanji), - win32wm::VK_ESCAPE => Some(VirtualKeyCode::Escape), - win32wm::VK_CONVERT => Some(VirtualKeyCode::Convert), - win32wm::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert), - //win32wm::VK_ACCEPT => Some(VirtualKeyCode::Accept), - //win32wm::VK_MODECHANGE => Some(VirtualKeyCode::Modechange), - win32wm::VK_SPACE => Some(VirtualKeyCode::Space), - win32wm::VK_PRIOR => Some(VirtualKeyCode::PageUp), - win32wm::VK_NEXT => Some(VirtualKeyCode::PageDown), - win32wm::VK_END => Some(VirtualKeyCode::End), - win32wm::VK_HOME => Some(VirtualKeyCode::Home), - win32wm::VK_LEFT => Some(VirtualKeyCode::Left), - win32wm::VK_UP => Some(VirtualKeyCode::Up), - win32wm::VK_RIGHT => Some(VirtualKeyCode::Right), - win32wm::VK_DOWN => Some(VirtualKeyCode::Down), - //win32wm::VK_SELECT => Some(VirtualKeyCode::Select), - //win32wm::VK_PRINT => Some(VirtualKeyCode::Print), - //win32wm::VK_EXECUTE => Some(VirtualKeyCode::Execute), - win32wm::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot), - win32wm::VK_INSERT => Some(VirtualKeyCode::Insert), - win32wm::VK_DELETE => Some(VirtualKeyCode::Delete), - //win32wm::VK_HELP => Some(VirtualKeyCode::Help), - 0x30 => Some(VirtualKeyCode::Key0), - 0x31 => Some(VirtualKeyCode::Key1), - 0x32 => Some(VirtualKeyCode::Key2), - 0x33 => Some(VirtualKeyCode::Key3), - 0x34 => Some(VirtualKeyCode::Key4), - 0x35 => Some(VirtualKeyCode::Key5), - 0x36 => Some(VirtualKeyCode::Key6), - 0x37 => Some(VirtualKeyCode::Key7), - 0x38 => Some(VirtualKeyCode::Key8), - 0x39 => Some(VirtualKeyCode::Key9), - 0x41 => Some(VirtualKeyCode::A), - 0x42 => Some(VirtualKeyCode::B), - 0x43 => Some(VirtualKeyCode::C), - 0x44 => Some(VirtualKeyCode::D), - 0x45 => Some(VirtualKeyCode::E), - 0x46 => Some(VirtualKeyCode::F), - 0x47 => Some(VirtualKeyCode::G), - 0x48 => Some(VirtualKeyCode::H), - 0x49 => Some(VirtualKeyCode::I), - 0x4A => Some(VirtualKeyCode::J), - 0x4B => Some(VirtualKeyCode::K), - 0x4C => Some(VirtualKeyCode::L), - 0x4D => Some(VirtualKeyCode::M), - 0x4E => Some(VirtualKeyCode::N), - 0x4F => Some(VirtualKeyCode::O), - 0x50 => Some(VirtualKeyCode::P), - 0x51 => Some(VirtualKeyCode::Q), - 0x52 => Some(VirtualKeyCode::R), - 0x53 => Some(VirtualKeyCode::S), - 0x54 => Some(VirtualKeyCode::T), - 0x55 => Some(VirtualKeyCode::U), - 0x56 => Some(VirtualKeyCode::V), - 0x57 => Some(VirtualKeyCode::W), - 0x58 => Some(VirtualKeyCode::X), - 0x59 => Some(VirtualKeyCode::Y), - 0x5A => Some(VirtualKeyCode::Z), - win32wm::VK_LWIN => Some(VirtualKeyCode::LWin), - win32wm::VK_RWIN => Some(VirtualKeyCode::RWin), - win32wm::VK_APPS => Some(VirtualKeyCode::Apps), - win32wm::VK_SLEEP => Some(VirtualKeyCode::Sleep), - win32wm::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), - win32wm::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1), - win32wm::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2), - win32wm::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3), - win32wm::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4), - win32wm::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5), - win32wm::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6), - win32wm::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), - win32wm::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), - win32wm::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), - win32wm::VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), - win32wm::VK_ADD => Some(VirtualKeyCode::NumpadAdd), - //win32wm::VK_SEPARATOR => Some(VirtualKeyCode::Separator), - win32wm::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), - win32wm::VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), - win32wm::VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), - win32wm::VK_F1 => Some(VirtualKeyCode::F1), - win32wm::VK_F2 => Some(VirtualKeyCode::F2), - win32wm::VK_F3 => Some(VirtualKeyCode::F3), - win32wm::VK_F4 => Some(VirtualKeyCode::F4), - win32wm::VK_F5 => Some(VirtualKeyCode::F5), - win32wm::VK_F6 => Some(VirtualKeyCode::F6), - win32wm::VK_F7 => Some(VirtualKeyCode::F7), - win32wm::VK_F8 => Some(VirtualKeyCode::F8), - win32wm::VK_F9 => Some(VirtualKeyCode::F9), - win32wm::VK_F10 => Some(VirtualKeyCode::F10), - win32wm::VK_F11 => Some(VirtualKeyCode::F11), - win32wm::VK_F12 => Some(VirtualKeyCode::F12), - win32wm::VK_F13 => Some(VirtualKeyCode::F13), - win32wm::VK_F14 => Some(VirtualKeyCode::F14), - win32wm::VK_F15 => Some(VirtualKeyCode::F15), - win32wm::VK_F16 => Some(VirtualKeyCode::F16), - win32wm::VK_F17 => Some(VirtualKeyCode::F17), - win32wm::VK_F18 => Some(VirtualKeyCode::F18), - win32wm::VK_F19 => Some(VirtualKeyCode::F19), - win32wm::VK_F20 => Some(VirtualKeyCode::F20), - win32wm::VK_F21 => Some(VirtualKeyCode::F21), - win32wm::VK_F22 => Some(VirtualKeyCode::F22), - win32wm::VK_F23 => Some(VirtualKeyCode::F23), - win32wm::VK_F24 => Some(VirtualKeyCode::F24), - win32wm::VK_NUMLOCK => Some(VirtualKeyCode::Numlock), - win32wm::VK_SCROLL => Some(VirtualKeyCode::Scroll), - win32wm::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward), - win32wm::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward), - win32wm::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh), - win32wm::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop), - win32wm::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch), - win32wm::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites), - win32wm::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome), - win32wm::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute), - win32wm::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown), - win32wm::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp), - win32wm::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack), - win32wm::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack), - win32wm::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop), - win32wm::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause), - win32wm::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail), - win32wm::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect), - /*win32wm::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1), - win32wm::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/ - win32wm::VK_OEM_PLUS => Some(VirtualKeyCode::Equals), - win32wm::VK_OEM_COMMA => Some(VirtualKeyCode::Comma), - win32wm::VK_OEM_MINUS => Some(VirtualKeyCode::Minus), - win32wm::VK_OEM_PERIOD => Some(VirtualKeyCode::Period), - win32wm::VK_OEM_1 => map_text_keys(vkey), - win32wm::VK_OEM_2 => map_text_keys(vkey), - win32wm::VK_OEM_3 => map_text_keys(vkey), - win32wm::VK_OEM_4 => map_text_keys(vkey), - win32wm::VK_OEM_5 => map_text_keys(vkey), - win32wm::VK_OEM_6 => map_text_keys(vkey), - win32wm::VK_OEM_7 => map_text_keys(vkey), - /* win32wm::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */ - win32wm::VK_OEM_102 => Some(VirtualKeyCode::OEM102), - /*win32wm::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey), - win32wm::VK_PACKET => Some(VirtualKeyCode::Packet), - win32wm::VK_ATTN => Some(VirtualKeyCode::Attn), - win32wm::VK_CRSEL => Some(VirtualKeyCode::Crsel), - win32wm::VK_EXSEL => Some(VirtualKeyCode::Exsel), - win32wm::VK_EREOF => Some(VirtualKeyCode::Ereof), - win32wm::VK_PLAY => Some(VirtualKeyCode::Play), - win32wm::VK_ZOOM => Some(VirtualKeyCode::Zoom), - win32wm::VK_NONAME => Some(VirtualKeyCode::Noname), - win32wm::VK_PA1 => Some(VirtualKeyCode::Pa1), - win32wm::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/ - _ => None, - } -} - -pub fn handle_extended_keys( - vkey: u32, - mut scancode: UINT, - extended: bool, -) -> Option<(c_int, UINT)> { - // Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ - scancode = if extended { 0xE000 } else { 0x0000 } | scancode; - let vkey = match vkey { - win32wm::VK_SHIFT => unsafe { MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) as _ }, - win32wm::VK_CONTROL => { - if extended { - VK_RCONTROL - } else { - VK_LCONTROL - } - } - win32wm::VK_MENU => { - if extended { - VK_RMENU - } else { - VK_LMENU - } - } - _ => { - match scancode { - // When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE - // as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input. - // Don't emit anything for the LeftControl event in the pair... - 0xE01D if vkey == VK_PAUSE => return None, - // ...and emit the Pause event for the second event in the pair. - 0x45 if vkey == VK_PAUSE || vkey == 0xFF as _ => { - scancode = 0xE059; - VK_PAUSE - } - // VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different - // scancode when used with modifiers than when used without - 0xE046 => { - scancode = 0xE059; - VK_PAUSE - } - // VK_SCROLL has an incorrect vkey value when used with modifiers. - 0x46 => VK_SCROLL, - _ => vkey, - } - } - }; - Some((vkey, scancode)) -} - -pub fn process_key_params( - wparam: WPARAM, - lparam: LPARAM, -) -> Option<(ScanCode, Option)> { - let scancode = ((lparam >> 16) & 0xff) as UINT; - let extended = (lparam & 0x01000000) != 0; - handle_extended_keys(wparam as _, scancode, extended) - .map(|(vkey, scancode)| (scancode, vkey_to_tao_vkey(vkey))) -} - -// This is needed as windows doesn't properly distinguish -// some virtual key codes for different keyboard layouts -fn map_text_keys(win_virtual_key: i32) -> Option { - let char_key = unsafe { MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF; - match char::from_u32(char_key) { - Some(';') => Some(VirtualKeyCode::Semicolon), - Some('/') => Some(VirtualKeyCode::Slash), - Some('`') => Some(VirtualKeyCode::Grave), - Some('[') => Some(VirtualKeyCode::LBracket), - Some(']') => Some(VirtualKeyCode::RBracket), - Some('\'') => Some(VirtualKeyCode::Apostrophe), - Some('\\') => Some(VirtualKeyCode::Backslash), - _ => None, - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs deleted file mode 100644 index d580d0d58..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs +++ /dev/null @@ -1,2637 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![allow(non_snake_case)] - -mod runner; - -use crossbeam_channel::{self as channel, Receiver, Sender}; -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use std::{ - cell::Cell, - collections::VecDeque, - ffi::c_void, - marker::PhantomData, - mem, panic, - rc::Rc, - sync::Arc, - thread, - time::{Duration, Instant}, -}; -use windows::{ - core::{s, BOOL, PCWSTR}, - Win32::{ - Foundation::{ - HANDLE, HINSTANCE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM, - }, - Graphics::Gdi::*, - System::{ - LibraryLoader::GetModuleHandleW, - Ole::{IDropTarget, RevokeDragDrop}, - Threading::{GetCurrentThreadId, INFINITE}, - }, - UI::{ - Controls::{self as win32c, HOVER_DEFAULT}, - Input::{KeyboardAndMouse::*, Pointer::*, Touch::*, *}, - Shell::{ - DefSubclassProc, RemoveWindowSubclass, SHAppBarMessage, SetWindowSubclass, ABE_BOTTOM, - ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, - }, - WindowsAndMessaging::{self as win32wm, *}, - }, - }, -}; - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize, PixelUnit}, - error::ExternalError, - event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, - event_loop::{ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW}, - keyboard::{KeyCode, ModifiersState}, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::{ - dark_mode::try_window_theme, - dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, - keyboard::is_msg_keyboard_related, - keyboard_layout::LAYOUT_CACHE, - minimal_ime::is_msg_ime_related, - monitor::{self, MonitorHandle}, - raw_input, util, - window::set_skip_taskbar, - window_state::{CursorFlags, WindowFlags, WindowState}, - wrap_device_id, WindowId, DEVICE_ID, - }, - window::{Fullscreen, Theme, WindowId as RootWindowId}, -}; -use runner::{EventLoopRunner, EventLoopRunnerShared}; - -use super::{dpi::hwnd_dpi, util::get_system_metrics_for_dpi}; - -// This is defined in `winuser.h` as a macro that expands to `UINT_MAX` -const WHEEL_PAGESCROLL: u32 = u32::MAX; -// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLLINES -const DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA: isize = 3; -// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLCHARS -const DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA: isize = 3; - -type GetPointerFrameInfoHistory = unsafe extern "system" fn( - pointerId: u32, - entriesCount: *mut u32, - pointerCount: *mut u32, - pointerInfo: *mut POINTER_INFO, -) -> BOOL; - -type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL; - -type GetPointerDeviceRects = unsafe extern "system" fn( - device: HANDLE, - pointerDeviceRect: *mut RECT, - displayRect: *mut RECT, -) -> BOOL; - -type GetPointerTouchInfo = - unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL; - -type GetPointerPenInfo = - unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; - -static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); -static SKIP_POINTER_FRAME_MESSAGES: Lazy> = - Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); -static GET_POINTER_DEVICE_RECTS: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); -static GET_POINTER_TOUCH_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); -static GET_POINTER_PEN_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); - -pub(crate) struct SubclassInput { - pub window_state: Arc>, - pub event_loop_runner: EventLoopRunnerShared, - pub _file_drop_handler: Option, - pub subclass_removed: Cell, - pub recurse_depth: Cell, - pub event_loop_preferred_theme: Arc>>, -} - -impl SubclassInput { - unsafe fn send_event(&self, event: Event<'_, T>) { - self.event_loop_runner.send_event(event); - } -} - -struct ThreadMsgTargetSubclassInput { - event_loop_runner: EventLoopRunnerShared, - user_event_receiver: Receiver, -} - -impl ThreadMsgTargetSubclassInput { - unsafe fn send_event(&self, event: Event<'_, T>) { - self.event_loop_runner.send_event(event); - } -} - -/// The result of a subclass procedure (the message handling callback) -pub(crate) enum ProcResult { - DefSubclassProc, // <- this should be the default value - DefWindowProc, - Value(LRESULT), -} - -pub struct EventLoop { - thread_msg_sender: Sender, - window_target: RootELW, - msg_hook: Option bool + 'static>>, -} - -pub(crate) struct PlatformSpecificEventLoopAttributes { - pub(crate) any_thread: bool, - pub(crate) dpi_aware: bool, - pub(crate) msg_hook: Option bool + 'static>>, - pub(crate) preferred_theme: Option, -} - -impl Default for PlatformSpecificEventLoopAttributes { - fn default() -> Self { - Self { - any_thread: false, - dpi_aware: true, - msg_hook: None, - preferred_theme: None, - } - } -} - -#[derive(Clone)] -pub struct EventLoopWindowTarget { - thread_id: u32, - thread_msg_target: HWND, - pub(crate) preferred_theme: Arc>>, - pub(crate) runner_shared: EventLoopRunnerShared, -} - -impl EventLoop { - pub(crate) fn new(attributes: &mut PlatformSpecificEventLoopAttributes) -> EventLoop { - let thread_id = unsafe { GetCurrentThreadId() }; - - if !attributes.any_thread && thread_id != main_thread_id() { - panic!( - "Initializing the event loop outside of the main thread is a significant \ - cross-platform compatibility hazard. If you absolutely need to create an \ - EventLoop on a different thread, you can use the \ - `EventLoopBuilderExtWindows::any_thread` function." - ); - } - - if attributes.dpi_aware { - become_dpi_aware(); - } - - let thread_msg_target = create_event_target_window(); - - super::dark_mode::allow_dark_mode_for_app(true); - - let send_thread_msg_target = thread_msg_target.0 as isize; - thread::spawn(move || wait_thread(thread_id, HWND(send_thread_msg_target as _))); - let wait_thread_id = get_wait_thread_id(); - - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); - - let thread_msg_sender = subclass_event_target_window(thread_msg_target, runner_shared.clone()); - raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); - - EventLoop { - thread_msg_sender, - window_target: RootELW { - p: EventLoopWindowTarget { - thread_id, - thread_msg_target, - runner_shared, - preferred_theme: Arc::new(Mutex::new(attributes.preferred_theme)), - }, - _marker: PhantomData, - }, - msg_hook: attributes.msg_hook.take(), - } - } - - pub fn window_target(&self) -> &RootELW { - &self.window_target - } - - pub fn run(mut self, event_handler: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let exit_code = self.run_return(event_handler); - ::std::process::exit(exit_code); - } - - pub fn run_return(&mut self, mut event_handler: F) -> i32 - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let event_loop_windows_ref = &self.window_target; - - unsafe { - self - .window_target - .p - .runner_shared - .set_event_handler(move |event, control_flow| { - event_handler(event, event_loop_windows_ref, control_flow); - }); - } - - let runner = &self.window_target.p.runner_shared; - - let exit_code = unsafe { - let mut msg = MSG::default(); - - runner.poll(); - 'main: loop { - if !GetMessageW(&mut msg, None, 0, 0).as_bool() { - break 'main 0; - } - - let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { - callback(&mut msg as *mut _ as *mut _) - } else { - false - }; - if !handled { - let _ = TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); - } - - if let ControlFlow::ExitWithCode(code) = runner.control_flow() { - if !runner.handling_events() { - break 'main code; - } - } - } - }; - - unsafe { - runner.loop_destroyed(); - } - runner.reset_runner(); - exit_code - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - target_window: self.window_target.p.thread_msg_target, - event_send: self.thread_msg_sender.clone(), - } - } -} - -impl EventLoopWindowTarget { - #[inline(always)] - pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor { - EventLoopThreadExecutor { - thread_id: self.thread_id, - target_window: self.thread_msg_target, - } - } - - // TODO: Investigate opportunities for caching - pub fn available_monitors(&self) -> VecDeque { - monitor::available_monitors() - } - - pub fn primary_monitor(&self) -> Option { - let monitor = monitor::primary_monitor(); - Some(RootMonitorHandle { inner: monitor }) - } - - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - monitor::from_point(x, y) - } - - #[cfg(feature = "rwh_05")] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::Windows( - rwh_06::WindowsDisplayHandle::new(), - )) - } - - pub fn set_device_event_filter(&self, filter: DeviceEventFilter) { - raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, filter); - } - - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position().map_err(Into::into) - } - - #[inline] - pub fn set_theme(&self, theme: Option) { - *self.preferred_theme.lock() = theme; - self.runner_shared.owned_windows(|window| { - let _ = unsafe { SendMessageW(window, *CHANGE_THEME_MSG_ID, None, None) }; - }); - } -} - -fn main_thread_id() -> u32 { - static mut MAIN_THREAD_ID: u32 = 0; - #[used] - #[allow(non_upper_case_globals)] - #[link_section = ".CRT$XCU"] - static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = { - unsafe extern "C" fn initer() { - MAIN_THREAD_ID = GetCurrentThreadId(); - } - initer - }; - - unsafe { MAIN_THREAD_ID } -} - -fn get_wait_thread_id() -> u32 { - unsafe { - let mut msg = MSG::default(); - let result = GetMessageW( - &mut msg, - None, - *SEND_WAIT_THREAD_ID_MSG_ID, - *SEND_WAIT_THREAD_ID_MSG_ID, - ); - assert_eq!( - msg.message, *SEND_WAIT_THREAD_ID_MSG_ID, - "this shouldn't be possible. please open an issue with Tauri. error code: {}", - result.0 - ); - msg.lParam.0 as u32 - } -} - -fn wait_thread(parent_thread_id: u32, msg_window_id: HWND) { - unsafe { - let mut msg: MSG; - - let cur_thread_id = GetCurrentThreadId(); - let _ = PostThreadMessageW( - parent_thread_id, - *SEND_WAIT_THREAD_ID_MSG_ID, - WPARAM(0), - LPARAM(cur_thread_id as _), - ); - - let mut wait_until_opt = None; - 'main: loop { - // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get - // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't - // additional messages to process. - msg = MSG::default(); - - if wait_until_opt.is_some() { - if PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() { - let _ = TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } else if !GetMessageW(&mut msg, None, 0, 0).as_bool() { - break 'main; - } else { - let _ = TranslateMessage(&msg); - DispatchMessageW(&msg); - } - - if msg.message == *WAIT_UNTIL_MSG_ID { - wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam.0 as *mut _)); - } else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID { - wait_until_opt = None; - } - - if let Some(wait_until) = wait_until_opt { - let now = Instant::now(); - if now < wait_until { - // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract - // 1 millisecond from the requested time and spinlock for the remainder to - // compensate for that. - let resume_reason = MsgWaitForMultipleObjectsEx( - None, - dur2timeout(wait_until - now).saturating_sub(1), - QS_ALLEVENTS, - MWMO_INPUTAVAILABLE, - ); - if resume_reason == WAIT_TIMEOUT { - let _ = PostMessageW( - Some(msg_window_id), - *PROCESS_NEW_EVENTS_MSG_ID, - WPARAM(0), - LPARAM(0), - ); - wait_until_opt = None; - } - } else { - let _ = PostMessageW( - Some(msg_window_id), - *PROCESS_NEW_EVENTS_MSG_ID, - WPARAM(0), - LPARAM(0), - ); - wait_until_opt = None; - } - } - } - } -} - -// Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs -fn dur2timeout(dur: Duration) -> u32 { - // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the - // timeouts in windows APIs are typically u32 milliseconds. To translate, we - // have two pieces to take care of: - // - // * Nanosecond precision is rounded up - // * Greater than u32::MAX milliseconds (50 days) is rounded up to INFINITE - // (never time out). - dur - .as_secs() - .checked_mul(1000) - .and_then(|ms| ms.checked_add((dur.subsec_nanos() as u64) / 1_000_000)) - .and_then(|ms| { - ms.checked_add(if dur.subsec_nanos() % 1_000_000 > 0 { - 1 - } else { - 0 - }) - }) - .map(|ms| { - if ms > u32::MAX as u64 { - INFINITE - } else { - ms as u32 - } - }) - .unwrap_or(INFINITE) -} - -impl Drop for EventLoop { - fn drop(&mut self) { - unsafe { - let _ = DestroyWindow(self.window_target.p.thread_msg_target); - } - } -} - -pub(crate) struct EventLoopThreadExecutor { - thread_id: u32, - target_window: HWND, -} - -unsafe impl Send for EventLoopThreadExecutor {} -unsafe impl Sync for EventLoopThreadExecutor {} - -impl EventLoopThreadExecutor { - /// Check to see if we're in the parent event loop's thread. - pub(super) fn in_event_loop_thread(&self) -> bool { - let cur_thread_id = unsafe { GetCurrentThreadId() }; - self.thread_id == cur_thread_id - } - - /// Executes a function in the event loop thread. If we're already in the event loop thread, - /// we just call the function directly. - /// - /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is - /// removed automatically if the callback receives a `WM_CLOSE` message for the window. - /// - /// Note that if you are using this to change some property of a window and updating - /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the - /// events may be sent to the other thread in different order to the one in which you set - /// `WindowState`, leaving them out of sync. - /// - /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent - /// to the unstable FnBox. - pub(super) fn execute_in_thread(&self, mut function: F) - where - F: FnMut() + Send + 'static, - { - unsafe { - if self.in_event_loop_thread() { - function(); - } else { - // We double-box because the first box is a fat pointer. - let boxed = Box::new(function) as Box; - let boxed2: ThreadExecFn = Box::new(boxed); - - let raw = Box::into_raw(boxed2); - - let res = PostMessageW( - Some(self.target_window), - *EXEC_MSG_ID, - WPARAM(raw as _), - LPARAM(0), - ); - assert!( - res.is_ok(), - "PostMessage failed ; is the messages queue full?" - ); - } - } - } -} - -type ThreadExecFn = Box>; - -pub struct EventLoopProxy { - target_window: HWND, - event_send: Sender, -} -unsafe impl Send for EventLoopProxy {} -unsafe impl Sync for EventLoopProxy {} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - Self { - target_window: self.target_window, - event_send: self.event_send.clone(), - } - } -} - -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - unsafe { - if PostMessageW( - Some(self.target_window), - *USER_EVENT_MSG_ID, - WPARAM(0), - LPARAM(0), - ) - .is_ok() - { - self.event_send.send(event).ok(); - Ok(()) - } else { - Err(EventLoopClosed(event)) - } - } - } -} - -type WaitUntilInstantBox = Box; - -/// Message sent by the `EventLoopProxy` when we want to wake up the thread. -/// WPARAM and LPARAM are unused. -static USER_EVENT_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); -/// Message sent when we want to execute a closure in the thread. -/// WPARAM contains a Box> that must be retrieved with `Box::from_raw`, -/// and LPARAM is unused. -static EXEC_MSG_ID: Lazy = Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); -static PROCESS_NEW_EVENTS_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); -/// lparam is the wait thread's message id. -static SEND_WAIT_THREAD_ID_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); -/// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should -/// be sent. -static WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); -static CANCEL_WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); -/// Message sent by a `Window` when it wants to be destroyed by the main thread. -/// WPARAM and LPARAM are unused. -pub static DESTROY_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); -/// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the -/// documentation in the `window_state` module for more information. -pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); -/// Message sent by event loop when event loop's prefered theme changed. -/// WPARAM and LPARAM are unused. -pub static CHANGE_THEME_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); -/// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows -/// When the application receives this message, it should assume that any taskbar icons it added have been removed and add them again. -pub static S_U_TASKBAR_RESTART: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); -static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = Lazy::new(|| unsafe { - let class_name = util::encode_wide("Tao Thread Event Target"); - - let class = WNDCLASSEXW { - cbSize: mem::size_of::() as u32, - style: Default::default(), - lpfnWndProc: Some(util::call_default_window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), - hIcon: HICON::default(), - hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly - hbrBackground: HBRUSH::default(), - lpszMenuName: PCWSTR::null(), - lpszClassName: PCWSTR::from_raw(class_name.as_ptr()), - hIconSm: HICON::default(), - }; - - RegisterClassExW(&class); - - class_name -}); - -fn create_event_target_window() -> HWND { - let window = unsafe { - CreateWindowExW( - WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | - // WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which - // we want to avoid. If you remove this style, this window won't show up in the - // taskbar *initially*, but it can show up at some later point. This can sometimes - // happen on its own after several hours have passed, although this has proven - // difficult to reproduce. Alternatively, it can be manually triggered by killing - // `explorer.exe` and then starting the process back up. - // It is unclear why the bug is triggered by waiting for several hours. - WS_EX_TOOLWINDOW, - PCWSTR::from_raw(THREAD_EVENT_TARGET_WINDOW_CLASS.clone().as_ptr()), - PCWSTR::null(), - WS_OVERLAPPED, - 0, - 0, - 0, - 0, - None, - None, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), - None, - ) - }; - - let window = match window { - Ok(w) => w, - Err(_) => return HWND::default(), - }; - - util::SetWindowLongPtrW( - window, - GWL_STYLE, - // The window technically has to be visible to receive WM_PAINT messages (which are used - // for delivering events during resizes), but it isn't displayed to the user because of - // the LAYERED style. - (WS_VISIBLE | WS_POPUP).0 as isize, - ); - window -} - -fn subclass_event_target_window( - window: HWND, - event_loop_runner: EventLoopRunnerShared, -) -> Sender { - unsafe { - let (tx, rx) = channel::unbounded(); - - let subclass_input = ThreadMsgTargetSubclassInput { - event_loop_runner, - user_event_receiver: rx, - }; - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = SetWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - input_ptr as usize, - ); - assert!(subclass_result.as_bool()); - - tx - } -} - -fn remove_event_target_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); -} - -/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of -/// the window. -unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) { - window_state.mouse.capture_count += 1; - SetCapture(window); -} - -/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor -/// is outside the window. -unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowState>) { - window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1); - if window_state.mouse.capture_count == 0 { - // ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state. - drop(window_state); - let _ = ReleaseCapture(); - } -} - -const WINDOW_SUBCLASS_ID: usize = 0; -const THREAD_EVENT_TARGET_SUBCLASS_ID: usize = 1; -pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { - subclass_input.event_loop_runner.register_window(window); - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = unsafe { - SetWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - input_ptr as usize, - ) - }; - assert!(subclass_result.as_bool()); -} - -fn remove_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); -} - -fn normalize_pointer_pressure(pressure: u32) -> Option { - match pressure { - 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), - _ => None, - } -} - -/// Flush redraw events for Tao's windows. -/// -/// Tao's API guarantees that all redraw events will be clustered together and dispatched all at -/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple -/// windows have had redraws scheduled, but an input event is pushed to the message queue between -/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows -/// will dispatch the input event immediately instead of flushing all the redraw events. This -/// function explicitly pulls all of Tao's redraw events out of the event queue so that they -/// always all get processed in one fell swoop. -/// -/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, -/// it won't flush the redraw events and will return `false`. -#[must_use] -unsafe fn flush_paint_messages( - except: Option, - runner: &EventLoopRunner, -) -> bool { - if !runner.redrawing() { - runner.main_events_cleared(); - let mut msg = MSG::default(); - runner.owned_windows(|redraw_window| { - if Some(redraw_window) == except { - return; - } - - if !PeekMessageW( - &mut msg, - Some(redraw_window), - WM_PAINT, - WM_PAINT, - PM_REMOVE | PM_QS_PAINT, - ) - .as_bool() - { - return; - } - - let _ = TranslateMessage(&msg); - DispatchMessageW(&msg); - }); - true - } else { - false - } -} - -unsafe fn process_control_flow(runner: &EventLoopRunner) { - match runner.control_flow() { - ControlFlow::Poll => { - let _ = PostMessageW( - Some(runner.thread_msg_target()), - *PROCESS_NEW_EVENTS_MSG_ID, - WPARAM(0), - LPARAM(0), - ); - } - ControlFlow::Wait => (), - ControlFlow::WaitUntil(until) => { - let _ = PostThreadMessageW( - runner.wait_thread_id(), - *WAIT_UNTIL_MSG_ID, - WPARAM(0), - LPARAM(Box::into_raw(WaitUntilInstantBox::new(until)) as _), - ); - } - ControlFlow::ExitWithCode(_) => (), - } -} - -/// Emit a `ModifiersChanged` event whenever modifiers have changed. -/// Returns the current modifier state -fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> ModifiersState { - use crate::event::WindowEvent::ModifiersChanged; - - let modifiers = LAYOUT_CACHE.lock().get_agnostic_mods(); - let mut window_state = subclass_input.window_state.lock(); - if window_state.modifiers_state != modifiers { - window_state.modifiers_state = modifiers; - - // Drop lock - drop(window_state); - - unsafe { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ModifiersChanged(modifiers), - }); - } - } - modifiers -} - -unsafe fn gain_active_focus(window: HWND, subclass_input: &SubclassInput) { - use crate::event::WindowEvent::Focused; - update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: Focused(true), - }); -} - -unsafe fn lose_active_focus(window: HWND, subclass_input: &SubclassInput) { - use crate::event::WindowEvent::{Focused, ModifiersChanged}; - - subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ModifiersChanged(ModifiersState::empty()), - }); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: Focused(false), - }); -} - -/// Any window whose callback is configured to this function will have its events propagated -/// through the events loop of the thread the window was created in. -// -// This is the callback that is called by `DispatchMessage` in the events loop. -// -// Returning 0 tells the Win32 API that the message has been processed. -// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -unsafe extern "system" fn public_window_callback( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, - uidsubclass: usize, - subclass_input_ptr: usize, -) -> LRESULT { - let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput; - let (result, subclass_removed, recurse_depth) = { - let subclass_input = &*subclass_input_ptr; - subclass_input - .recurse_depth - .set(subclass_input.recurse_depth.get() + 1); - - // Clear userdata - util::SetWindowLongPtrW(window, GWL_USERDATA, 0); - - let result = - public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input); - - let subclass_removed = subclass_input.subclass_removed.get(); - let recurse_depth = subclass_input.recurse_depth.get() - 1; - subclass_input.recurse_depth.set(recurse_depth); - - (result, subclass_removed, recurse_depth) - }; - - if subclass_removed && recurse_depth == 0 { - drop(Box::from_raw(subclass_input_ptr)) - } - - result -} - -unsafe fn public_window_callback_inner( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, - _: usize, - subclass_input: &SubclassInput, -) -> LRESULT { - let _ = RedrawWindow( - Some(subclass_input.event_loop_runner.thread_msg_target()), - None, - None, - RDW_INTERNALPAINT, - ); - - let mut result = ProcResult::DefSubclassProc; - - // Send new modifiers before sending key events. - let mods_changed_callback = || match msg { - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - update_modifiers(window, subclass_input); - result = ProcResult::Value(LRESULT(0)); - } - _ => (), - }; - subclass_input - .event_loop_runner - .catch_unwind(mods_changed_callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - - let keyboard_callback = || { - use crate::event::WindowEvent::KeyboardInput; - let is_keyboard_related = is_msg_keyboard_related(msg); - if !is_keyboard_related { - // We return early to avoid a deadlock from locking the window state - // when not appropriate. - return; - } - let events = { - let mut key_event_builders = - crate::platform_impl::platform::keyboard::KEY_EVENT_BUILDERS.lock(); - if let Some(key_event_builder) = key_event_builders.get_mut(&WindowId(window.0 as _)) { - key_event_builder.process_message(window, msg, wparam, lparam, &mut result) - } else { - Vec::new() - } - }; - for event in events { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: KeyboardInput { - device_id: DEVICE_ID, - event: event.event, - is_synthetic: event.is_synthetic, - }, - }); - } - }; - subclass_input - .event_loop_runner - .catch_unwind(keyboard_callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - - let ime_callback = || { - use crate::event::WindowEvent::ReceivedImeText; - let is_ime_related = is_msg_ime_related(msg); - if !is_ime_related { - return; - } - let text = { - let mut window_state = subclass_input.window_state.lock(); - window_state - .ime_handler - .process_message(window, msg, wparam, lparam, &mut result) - }; - if let Some(str) = text { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ReceivedImeText(str), - }); - } - }; - subclass_input - .event_loop_runner - .catch_unwind(ime_callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - - // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing - // the closure to catch_unwind directly so that the match body indendation wouldn't change and - // the git blame and history would be preserved. - let callback = || match msg { - win32wm::WM_ENTERSIZEMOVE => { - subclass_input - .window_state - .lock() - .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_EXITSIZEMOVE => { - let mut state = subclass_input.window_state.lock(); - if state.dragging { - state.dragging = false; - let _ = unsafe { PostMessageW(Some(window), WM_LBUTTONUP, WPARAM::default(), lparam) }; - } - state.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_NCCREATE => { - enable_non_client_dpi_scaling(window); - } - win32wm::WM_NCLBUTTONDOWN => { - if wparam.0 == HTCAPTION as _ { - let _ = PostMessageW(Some(window), WM_MOUSEMOVE, WPARAM(0), lparam); - } - - use crate::event::WindowEvent::DecorationsClick; - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: DecorationsClick, - }); - } - - win32wm::WM_CLOSE => { - use crate::event::WindowEvent::CloseRequested; - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: CloseRequested, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_DESTROY => { - use crate::event::WindowEvent::Destroyed; - let _ = RevokeDragDrop(window); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: Destroyed, - }); - subclass_input.event_loop_runner.remove_window(window); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_NCDESTROY => { - remove_window_subclass::(window); - subclass_input.subclass_removed.set(true); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_PAINT => { - if subclass_input.event_loop_runner.should_buffer() { - // this branch can happen in response to `UpdateWindow`, if win32 decides to - // redraw the window outside the normal flow of the event loop. - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - } else { - let managing_redraw = flush_paint_messages(Some(window), &subclass_input.event_loop_runner); - subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId( - window.0 as _, - )))); - if managing_redraw { - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); - } - } - } - - win32wm::WM_ERASEBKGND => { - let w = subclass_input.window_state.lock(); - if let Some(color) = w.background_color { - let hdc = HDC(wparam.0 as *mut _); - let mut rc = RECT::default(); - if GetClientRect(window, &mut rc).is_ok() { - let brush = CreateSolidBrush(util::RGB(color.0, color.1, color.2)); - FillRect(hdc, &rc, brush); - let _ = DeleteObject(brush.into()); - - result = ProcResult::Value(LRESULT(1)); - } else { - result = ProcResult::DefSubclassProc; - } - } else { - result = ProcResult::DefSubclassProc; - } - } - - win32wm::WM_WINDOWPOSCHANGING => { - let mut window_state = subclass_input.window_state.lock(); - - if let Some(ref mut fullscreen) = window_state.fullscreen { - let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); - let new_rect = RECT { - left: window_pos.x, - top: window_pos.y, - right: window_pos.x + window_pos.cx, - bottom: window_pos.y + window_pos.cy, - }; - - const NOMOVE_OR_NOSIZE: SET_WINDOW_POS_FLAGS = - SET_WINDOW_POS_FLAGS(SWP_NOMOVE.0 | SWP_NOSIZE.0); - - let new_rect = if (window_pos.flags & NOMOVE_OR_NOSIZE) != SET_WINDOW_POS_FLAGS::default() { - let cur_rect = util::get_window_rect(window) - .expect("Unexpected GetWindowRect failure; please report this error to tauri-apps/tao on GitHub"); - - match window_pos.flags & NOMOVE_OR_NOSIZE { - NOMOVE_OR_NOSIZE => None, - - SWP_NOMOVE => Some(RECT { - left: cur_rect.left, - top: cur_rect.top, - right: cur_rect.left + window_pos.cx, - bottom: cur_rect.top + window_pos.cy, - }), - - SWP_NOSIZE => Some(RECT { - left: window_pos.x, - top: window_pos.y, - right: window_pos.x - cur_rect.left + cur_rect.right, - bottom: window_pos.y - cur_rect.top + cur_rect.bottom, - }), - - _ => unreachable!(), - } - } else { - Some(new_rect) - }; - - if let Some(new_rect) = new_rect { - let new_monitor = MonitorFromRect(&new_rect, MONITOR_DEFAULTTONULL); - match fullscreen { - Fullscreen::Borderless(ref mut fullscreen_monitor) => { - if !new_monitor.is_invalid() - && fullscreen_monitor - .as_ref() - .map(|monitor| new_monitor != monitor.inner.hmonitor()) - .unwrap_or(true) - { - if let Ok(new_monitor_info) = monitor::get_monitor_info(new_monitor) { - let new_monitor_rect = new_monitor_info.monitorInfo.rcMonitor; - window_pos.x = new_monitor_rect.left; - window_pos.y = new_monitor_rect.top; - window_pos.cx = new_monitor_rect.right - new_monitor_rect.left; - window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; - } - *fullscreen_monitor = Some(crate::monitor::MonitorHandle { - inner: MonitorHandle::new(new_monitor), - }); - } - } - Fullscreen::Exclusive(ref video_mode) => { - let old_monitor = video_mode.video_mode.monitor.hmonitor(); - if let Ok(old_monitor_info) = monitor::get_monitor_info(old_monitor) { - let old_monitor_rect = old_monitor_info.monitorInfo.rcMonitor; - window_pos.x = old_monitor_rect.left; - window_pos.y = old_monitor_rect.top; - window_pos.cx = old_monitor_rect.right - old_monitor_rect.left; - window_pos.cy = old_monitor_rect.bottom - old_monitor_rect.top; - } - } - } - } - } - - let window_flags = window_state.window_flags; - if window_flags.contains(WindowFlags::ALWAYS_ON_BOTTOM) { - let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); - window_pos.hwndInsertAfter = HWND_BOTTOM; - } - - result = ProcResult::Value(LRESULT(0)); - } - - // WM_MOVE supplies client area positions, so we send Moved here instead. - win32wm::WM_WINDOWPOSCHANGED => { - use crate::event::WindowEvent::Moved; - - let windowpos = lparam.0 as *const WINDOWPOS; - if (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE { - let physical_position = PhysicalPosition::new((*windowpos).x, (*windowpos).y); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: Moved(physical_position), - }); - } - - // This is necessary for us to still get sent WM_SIZE. - result = ProcResult::DefSubclassProc; - } - - win32wm::WM_SIZE => { - use crate::event::WindowEvent::Resized; - let w = u32::from(util::LOWORD(lparam.0 as u32)); - let h = u32::from(util::HIWORD(lparam.0 as u32)); - - let physical_size = PhysicalSize::new(w, h); - let event = Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: Resized(physical_size), - }; - - { - let mut w = subclass_input.window_state.lock(); - // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. - if !w - .window_flags() - .contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) - { - let maximized = wparam.0 == win32wm::SIZE_MAXIMIZED as _; - w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); - } - } - - subclass_input.send_event(event); - result = ProcResult::Value(LRESULT(0)); - } - - // this is necessary for us to maintain minimize/restore state - win32wm::WM_SYSCOMMAND => { - if wparam.0 == SC_RESTORE as _ { - let mut w = subclass_input.window_state.lock(); - w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); - } - if wparam.0 == SC_MINIMIZE as _ { - let mut w = subclass_input.window_state.lock(); - w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); - } - // Send `WindowEvent::Minimized` here if we decide to implement one - - if wparam.0 == SC_SCREENSAVE as _ { - let window_state = subclass_input.window_state.lock(); - if window_state.fullscreen.is_some() { - result = ProcResult::Value(LRESULT(0)); - return; - } - } - - result = ProcResult::DefWindowProc; - } - - win32wm::WM_MOUSEMOVE => { - use crate::event::WindowEvent::{CursorEntered, CursorMoved}; - let mouse_was_outside_window = { - let mut w = subclass_input.window_state.lock(); - - let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); - w.mouse - .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)) - .ok(); - was_outside_window - }; - - if mouse_was_outside_window { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: CursorEntered { - device_id: DEVICE_ID, - }, - }); - - // Calling TrackMouseEvent in order to receive mouse leave events. - let _ = TrackMouseEvent(&mut TRACKMOUSEEVENT { - cbSize: mem::size_of::() as u32, - dwFlags: TME_LEAVE, - hwndTrack: window, - dwHoverTime: HOVER_DEFAULT, - }); - } - - let x = f64::from(util::GET_X_LPARAM(lparam)); - let y = f64::from(util::GET_Y_LPARAM(lparam)); - let position = PhysicalPosition::new(x, y); - let cursor_moved; - { - // handle spurious WM_MOUSEMOVE messages - // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 - // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html - let mut w = subclass_input.window_state.lock(); - cursor_moved = w.mouse.last_position != Some(position); - w.mouse.last_position = Some(position); - } - if cursor_moved { - let modifiers = update_modifiers(window, subclass_input); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: CursorMoved { - device_id: DEVICE_ID, - position, - modifiers, - }, - }); - } - - result = ProcResult::Value(LRESULT(0)); - } - - win32c::WM_MOUSELEAVE => { - use crate::event::WindowEvent::CursorLeft; - { - let mut w = subclass_input.window_state.lock(); - w.mouse - .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) - .ok(); - } - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: CursorLeft { - device_id: DEVICE_ID, - }, - }); - - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_MOUSEWHEEL => { - use crate::event::MouseScrollDelta::LineDelta; - - let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); - let value = value / WHEEL_DELTA as f32; - - let modifiers = update_modifiers(window, subclass_input); - - let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; - - let _ = SystemParametersInfoW( - SPI_GETWHEELSCROLLLINES, - 0, - Some(&mut scroll_lines as *mut isize as *mut c_void), - SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), - ); - - if scroll_lines as u32 == WHEEL_PAGESCROLL { - // TODO: figure out how to handle page scrolls - scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; - } - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta: LineDelta(0.0, value * scroll_lines as f32), - phase: TouchPhase::Moved, - modifiers, - }, - }); - - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_MOUSEHWHEEL => { - use crate::event::MouseScrollDelta::LineDelta; - - let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); - let value = value / WHEEL_DELTA as f32; - - let modifiers = update_modifiers(window, subclass_input); - - let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA; - - let _ = SystemParametersInfoW( - SPI_GETWHEELSCROLLCHARS, - 0, - Some(&mut scroll_characters as *mut isize as *mut c_void), - SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), - ); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, - delta: LineDelta(value * scroll_characters as f32, 0.0), - phase: TouchPhase::Moved, - modifiers, - }, - }); - - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { - if msg == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - result = ProcResult::DefSubclassProc; - } - } - - win32wm::WM_LBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; - - capture_mouse(window, &mut subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Pressed, - button: Left, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_LBUTTONUP => { - use crate::event::{ElementState::Released, MouseButton::Left, WindowEvent::MouseInput}; - - release_mouse(subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Released, - button: Left, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_RBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput}; - - capture_mouse(window, &mut subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Pressed, - button: Right, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_RBUTTONUP => { - use crate::event::{ElementState::Released, MouseButton::Right, WindowEvent::MouseInput}; - - release_mouse(subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Released, - button: Right, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_MBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput}; - - capture_mouse(window, &mut subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Pressed, - button: Middle, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_MBUTTONUP => { - use crate::event::{ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput}; - - release_mouse(subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Released, - button: Middle, - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_XBUTTONDOWN => { - use crate::event::{ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput}; - let xbutton = util::GET_XBUTTON_WPARAM(wparam); - - capture_mouse(window, &mut subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Pressed, - button: Other(xbutton), - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_XBUTTONUP => { - use crate::event::{ElementState::Released, MouseButton::Other, WindowEvent::MouseInput}; - let xbutton = util::GET_XBUTTON_WPARAM(wparam); - - release_mouse(subclass_input.window_state.lock()); - - let modifiers = update_modifiers(window, subclass_input); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: MouseInput { - device_id: DEVICE_ID, - state: Released, - button: Other(xbutton), - modifiers, - }, - }); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_CAPTURECHANGED => { - // lparam here is a handle to the window which is gaining mouse capture. - // If it is the same as our window, then we're essentially retaining the capture. This - // can happen if `SetCapture` is called on our window when it already has the mouse - // capture. - if lparam.0 != window.0 as _ { - subclass_input.window_state.lock().mouse.capture_count = 0; - } - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_TOUCH => { - let pcount = usize::from(util::LOWORD(wparam.0 as u32)); - let mut inputs: Vec = Vec::with_capacity(pcount); - let uninit_inputs = inputs.spare_capacity_mut(); - let htouch = HTOUCHINPUT(lparam.0 as _); - if GetTouchInputInfo( - htouch, - mem::transmute::< - &mut [std::mem::MaybeUninit], - &mut [windows::Win32::UI::Input::Touch::TOUCHINPUT], - >(uninit_inputs), - mem::size_of::() as i32, - ) - .is_ok() - { - inputs.set_len(pcount); - for input in &inputs { - let mut location = POINT { - x: input.x / 100, - y: input.y / 100, - }; - - if !ScreenToClient(window, &mut location as *mut _).as_bool() { - continue; - } - - let x = location.x as f64 + (input.x % 100) as f64 / 100f64; - let y = location.y as f64 + (input.y % 100) as f64 / 100f64; - let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: WindowEvent::Touch(Touch { - phase: if (input.dwFlags & TOUCHEVENTF_DOWN) != Default::default() { - TouchPhase::Started - } else if (input.dwFlags & TOUCHEVENTF_UP) != Default::default() { - TouchPhase::Ended - } else if (input.dwFlags & TOUCHEVENTF_MOVE) != Default::default() { - TouchPhase::Moved - } else { - continue; - }, - location, - force: None, // WM_TOUCH doesn't support pressure information - id: input.dwID as u64, - device_id: DEVICE_ID, - }), - }); - } - } - let _ = CloseTouchInputHandle(htouch); - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_POINTERDOWN | win32wm::WM_POINTERUPDATE | win32wm::WM_POINTERUP => { - if let ( - Some(GetPointerFrameInfoHistory), - Some(SkipPointerFrameMessages), - Some(GetPointerDeviceRects), - ) = ( - *GET_POINTER_FRAME_INFO_HISTORY, - *SKIP_POINTER_FRAME_MESSAGES, - *GET_POINTER_DEVICE_RECTS, - ) { - let pointer_id = u32::from(util::LOWORD(wparam.0 as u32)); - let mut entries_count = 0_u32; - let mut pointers_count = 0_u32; - if !GetPointerFrameInfoHistory( - pointer_id, - &mut entries_count as *mut _, - &mut pointers_count as *mut _, - std::ptr::null_mut(), - ) - .as_bool() - { - result = ProcResult::Value(LRESULT(0)); - return; - } - - let pointer_info_count = (entries_count * pointers_count) as usize; - let mut pointer_infos: Vec = Vec::with_capacity(pointer_info_count); - let uninit_pointer_infos = pointer_infos.spare_capacity_mut(); - if !GetPointerFrameInfoHistory( - pointer_id, - &mut entries_count as *mut _, - &mut pointers_count as *mut _, - uninit_pointer_infos.as_mut_ptr() as *mut _, - ) - .as_bool() - { - result = ProcResult::Value(LRESULT(0)); - return; - } - pointer_infos.set_len(pointer_info_count); - - // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory - // The information retrieved appears in reverse chronological order, with the most recent entry in the first - // row of the returned array - for pointer_info in pointer_infos.iter().rev() { - let mut device_rect = mem::MaybeUninit::uninit(); - let mut display_rect = mem::MaybeUninit::uninit(); - - if !(GetPointerDeviceRects( - pointer_info.sourceDevice, - device_rect.as_mut_ptr(), - display_rect.as_mut_ptr(), - )) - .as_bool() - { - continue; - } - - let device_rect = device_rect.assume_init(); - let display_rect = display_rect.assume_init(); - - // For the most precise himetric to pixel conversion we calculate the ratio between the resolution - // of the display device (pixel) and the touch device (himetric). - let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64 - / (device_rect.right - device_rect.left) as f64; - let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64 - / (device_rect.bottom - device_rect.top) as f64; - - // ptHimetricLocation's origin is 0,0 even on multi-monitor setups. - // On multi-monitor setups we need to translate the himetric location to the rect of the - // display device it's attached to. - let x = display_rect.left as f64 - + pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x; - let y = display_rect.top as f64 - + pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y; - - let mut location = POINT { - x: x.floor() as i32, - y: y.floor() as i32, - }; - - if !ScreenToClient(window, &mut location as *mut _).as_bool() { - continue; - } - - let force = match pointer_info.pointerType { - win32wm::PT_TOUCH => { - let mut touch_info = mem::MaybeUninit::uninit(); - GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { - if GetPointerTouchInfo(pointer_info.pointerId, touch_info.as_mut_ptr()).as_bool() { - normalize_pointer_pressure(touch_info.assume_init().pressure) - } else { - None - } - }) - } - win32wm::PT_PEN => { - let mut pen_info = mem::MaybeUninit::uninit(); - GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| { - if GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr()).as_bool() { - normalize_pointer_pressure(pen_info.assume_init().pressure) - } else { - None - } - }) - } - _ => None, - }; - - let x = location.x as f64 + x.fract(); - let y = location.y as f64 + y.fract(); - let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: WindowEvent::Touch(Touch { - phase: if (pointer_info.pointerFlags & POINTER_FLAG_DOWN) != Default::default() { - TouchPhase::Started - } else if (pointer_info.pointerFlags & POINTER_FLAG_UP) != Default::default() { - TouchPhase::Ended - } else if (pointer_info.pointerFlags & POINTER_FLAG_UPDATE) != Default::default() { - TouchPhase::Moved - } else { - continue; - }, - location, - force, - id: pointer_info.pointerId as u64, - device_id: DEVICE_ID, - }), - }); - } - - let _ = SkipPointerFrameMessages(pointer_id); - } - - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_NCACTIVATE => { - let is_active = wparam != WPARAM(0); - let active_focus_changed = subclass_input.window_state.lock().set_active(is_active); - if active_focus_changed { - if is_active { - gain_active_focus(window, subclass_input); - } else { - lose_active_focus(window, subclass_input); - } - } - result = ProcResult::DefWindowProc; - } - - win32wm::WM_SETFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(true); - if active_focus_changed { - gain_active_focus(window, subclass_input); - } - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_KILLFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(false); - if active_focus_changed { - lose_active_focus(window, subclass_input); - } - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_SETCURSOR => { - let set_cursor_to = { - let window_state = subclass_input.window_state.lock(); - // The return value for the preceding `WM_NCHITTEST` message is conveniently - // provided through the low-order word of lParam. We use that here since - // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. - let in_client_area = u32::from(util::LOWORD(lparam.0 as u32)) == HTCLIENT; - if in_client_area { - Some(window_state.mouse.cursor) - } else { - None - } - }; - - match set_cursor_to { - Some(cursor) => { - if let Ok(cursor) = LoadCursorW(None, cursor.to_windows_cursor()) { - SetCursor(Some(cursor)); - } - result = ProcResult::Value(LRESULT(0)); - } - None => result = ProcResult::DefWindowProc, - } - } - - win32wm::WM_GETMINMAXINFO => { - let mmi = lparam.0 as *mut MINMAXINFO; - - let window_state = subclass_input.window_state.lock(); - let is_decorated = window_state - .window_flags() - .contains(WindowFlags::MARKER_DECORATIONS); - - let size_constraints = window_state.size_constraints; - - if size_constraints.has_min() { - let min_size = PhysicalSize::new( - size_constraints - .min_width - .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMINTRACK).into())) - .to_physical(window_state.scale_factor) - .0, - size_constraints - .min_height - .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMINTRACK).into())) - .to_physical(window_state.scale_factor) - .0, - ); - let (width, height): (u32, u32) = util::adjust_size(window, min_size, is_decorated).into(); - (*mmi).ptMinTrackSize = POINT { - x: width as i32, - y: height as i32, - }; - } - if size_constraints.has_max() { - let max_size = PhysicalSize::new( - size_constraints - .max_width - .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CXMAXTRACK).into())) - .to_physical(window_state.scale_factor) - .0, - size_constraints - .max_height - .unwrap_or_else(|| PixelUnit::Physical(GetSystemMetrics(SM_CYMAXTRACK).into())) - .to_physical(window_state.scale_factor) - .0, - ); - let (width, height): (u32, u32) = util::adjust_size(window, max_size, is_decorated).into(); - (*mmi).ptMaxTrackSize = POINT { - x: width as i32, - y: height as i32, - }; - } - - result = ProcResult::Value(LRESULT(0)); - } - - // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change - // DPI, therefore all applications are closed while DPI is changing. - win32wm::WM_DPICHANGED => { - use crate::event::WindowEvent::ScaleFactorChanged; - - // This message actually provides two DPI values - x and y. However MSDN says that - // "you only need to use either the X-axis or the Y-axis value when scaling your - // application since they are the same". - // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx - let new_dpi_x = u32::from(util::LOWORD(wparam.0 as u32)); - let new_scale_factor = dpi_to_scale_factor(new_dpi_x); - let old_scale_factor: f64; - - let (allow_resize, is_decorated) = { - let mut window_state = subclass_input.window_state.lock(); - old_scale_factor = window_state.scale_factor; - window_state.scale_factor = new_scale_factor; - - if (new_scale_factor - old_scale_factor).abs() < f64::EPSILON { - result = ProcResult::Value(LRESULT(0)); - return; - } - - let window_flags = window_state.window_flags(); - ( - window_state.fullscreen.is_none() && !window_flags.contains(WindowFlags::MAXIMIZED), - window_flags.contains(WindowFlags::MARKER_DECORATIONS), - ) - }; - - let mut style = WINDOW_STYLE(GetWindowLongW(window, GWL_STYLE) as u32); - // if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so - // `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and - // calculates a correct size for the client area. - if !is_decorated { - style &= !WS_CAPTION; - style &= !WS_SIZEBOX; - } - let style_ex = WINDOW_EX_STYLE(GetWindowLongW(window, GWL_EXSTYLE) as u32); - - // New size as suggested by Windows. - let suggested_rect = *(lparam.0 as *const RECT); - - // The window rect provided is the window's outer size, not it's inner size. However, - // win32 doesn't provide an `UnadjustWindowRectEx` function to get the client rect from - // the outer rect, so we instead adjust the window rect to get the decoration margins - // and remove them from the outer size. - let margin_left: i32; - let margin_top: i32; - // let margin_right: i32; - // let margin_bottom: i32; - { - let adjusted_rect = - util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect) - .unwrap_or(suggested_rect); - margin_left = suggested_rect.left - adjusted_rect.left; - margin_top = suggested_rect.top - adjusted_rect.top; - // margin_right = adjusted_rect.right - suggested_rect.right; - // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; - } - - let old_physical_inner_rect = { - let mut old_physical_inner_rect = RECT::default(); - let _ = GetClientRect(window, &mut old_physical_inner_rect); - let mut origin = POINT::default(); - let _ = ClientToScreen(window, &mut origin); - - old_physical_inner_rect.left += origin.x; - old_physical_inner_rect.right += origin.x; - old_physical_inner_rect.top += origin.y; - old_physical_inner_rect.bottom += origin.y; - - old_physical_inner_rect - }; - let old_physical_inner_size = PhysicalSize::new( - (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, - (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, - ); - - // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after - // exiting fullscreen (the restored size is already DPI adjusted). - let mut new_physical_inner_size = match allow_resize { - // We calculate our own size because the default suggested rect doesn't do a great job - // of preserving the window's logical size. - true => old_physical_inner_size - .to_logical::(old_scale_factor) - .to_physical::(new_scale_factor), - false => old_physical_inner_size, - }; - - // When the "Show window contents while dragging" is turned off, there is no need to adjust the window size. - if !is_show_window_contents_while_dragging_enabled() { - new_physical_inner_size = old_physical_inner_size; - } - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ScaleFactorChanged { - scale_factor: new_scale_factor, - new_inner_size: &mut new_physical_inner_size, - }, - }); - - let dragging_window: bool; - - { - let window_state = subclass_input.window_state.lock(); - dragging_window = window_state - .window_flags() - .contains(WindowFlags::MARKER_IN_SIZE_MOVE); - // Unset maximized if we're changing the window's size. - if new_physical_inner_size != old_physical_inner_size { - WindowState::set_window_flags(window_state, window, |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); - } - } - - let new_outer_rect: RECT; - if util::WIN_VERSION.build < 22000 { - // The window position needs adjustment on Windows 10. - { - let suggested_ul = ( - suggested_rect.left + margin_left, - suggested_rect.top + margin_top, - ); - - let mut conservative_rect = RECT { - left: suggested_ul.0, - top: suggested_ul.1, - right: suggested_ul.0 + new_physical_inner_size.width as i32, - bottom: suggested_ul.1 + new_physical_inner_size.height as i32, - }; - - conservative_rect = - util::adjust_window_rect_with_styles(window, style, style_ex, conservative_rect) - .unwrap_or(conservative_rect); - - // If we're dragging the window, offset the window so that the cursor's - // relative horizontal position in the title bar is preserved. - if dragging_window { - let bias = { - let cursor_pos = { - let mut pos = POINT::default(); - let _ = GetCursorPos(&mut pos); - pos - }; - let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64 - / (suggested_rect.right - suggested_rect.left) as f64; - - (cursor_pos.x - - (suggested_cursor_horizontal_ratio - * (conservative_rect.right - conservative_rect.left) as f64) - as i32) - - conservative_rect.left - }; - conservative_rect.left += bias; - conservative_rect.right += bias; - } - - // Check to see if the new window rect is on the monitor with the new DPI factor. - // If it isn't, offset the window so that it is. - let new_dpi_monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - let conservative_rect_monitor = - MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL); - new_outer_rect = { - if conservative_rect_monitor != new_dpi_monitor { - let get_monitor_rect = |monitor| { - let mut monitor_info = MONITORINFO { - cbSize: mem::size_of::() as _, - ..Default::default() - }; - let _ = GetMonitorInfoW(monitor, &mut monitor_info); - monitor_info.rcMonitor - }; - let wrong_monitor = conservative_rect_monitor; - let wrong_monitor_rect = get_monitor_rect(wrong_monitor); - let new_monitor_rect = get_monitor_rect(new_dpi_monitor); - - // The direction to nudge the window in to get the window onto the monitor with - // the new DPI factor. We calculate this by seeing which monitor edges are - // shared and nudging away from the wrong monitor based on those. - let delta_nudge_to_dpi_monitor = ( - if wrong_monitor_rect.left == new_monitor_rect.right { - -1 - } else if wrong_monitor_rect.right == new_monitor_rect.left { - 1 - } else { - 0 - }, - if wrong_monitor_rect.bottom == new_monitor_rect.top { - 1 - } else if wrong_monitor_rect.top == new_monitor_rect.bottom { - -1 - } else { - 0 - }, - ); - - let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left - + new_monitor_rect.bottom - - new_monitor_rect.top; - for _ in 0..abort_after_iterations { - conservative_rect.left += delta_nudge_to_dpi_monitor.0; - conservative_rect.right += delta_nudge_to_dpi_monitor.0; - conservative_rect.top += delta_nudge_to_dpi_monitor.1; - conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; - - if MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) == new_dpi_monitor { - break; - } - } - } - - conservative_rect - }; - } - } else { - // The suggested position is fine w/o adjustment on Windows 11. - new_outer_rect = suggested_rect - } - - let _ = SetWindowPos( - window, - None, - new_outer_rect.left, - new_outer_rect.top, - new_outer_rect.right - new_outer_rect.left, - new_outer_rect.bottom - new_outer_rect.top, - SWP_NOZORDER | SWP_NOACTIVATE, - ); - - result = ProcResult::Value(LRESULT(0)); - } - - win32wm::WM_SETTINGCHANGE => { - update_theme(subclass_input, window, true); - } - - win32wm::WM_NCCALCSIZE => { - let window_flags = subclass_input.window_state.lock().window_flags(); - let is_fullscreen = subclass_input.window_state.lock().fullscreen.is_some(); - - if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - result = ProcResult::DefSubclassProc; - } else { - // adjust the maximized borderless window so it doesn't cover the taskbar - if util::is_maximized(window).unwrap_or(false) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - if let Ok(monitor_info) = - monitor::get_monitor_info(MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL)) - { - let mut rect = monitor_info.monitorInfo.rcWork; - - fn has_edge(edge: u32) -> bool { - let mut app_data = APPBARDATA { - cbSize: std::mem::size_of::() as _, - uEdge: edge, - ..Default::default() - }; - unsafe { SHAppBarMessage(ABM_GETAUTOHIDEBAR, &mut app_data) != 0 } - } - - // keep a 1px for taskbar auto-hide to work - if has_edge(ABE_BOTTOM) { - rect.bottom -= 1; - } - if has_edge(ABE_LEFT) { - rect.left += 1; - } - if has_edge(ABE_TOP) { - rect.top += 1; - } - if has_edge(ABE_RIGHT) { - rect.right -= 1; - } - - params.rgrc[0] = rect; - } - } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) && !is_fullscreen { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - - let insets = util::calculate_window_insets(window); - - params.rgrc[0].left += insets.left; - params.rgrc[0].top += insets.top; - params.rgrc[0].right -= insets.right; - params.rgrc[0].bottom -= insets.bottom; - } - result = ProcResult::Value(LRESULT(0)); // return 0 here to make the window borderless - } - } - - win32wm::WM_NCHITTEST => { - let window_state = subclass_input.window_state.lock(); - let window_flags = window_state.window_flags(); - - // Allow resizing unmaximized non-fullscreen undecorated window - if !window_flags.contains(WindowFlags::MARKER_DECORATIONS) - && window_flags.contains(WindowFlags::RESIZABLE) - && window_state.fullscreen.is_none() - && !util::is_maximized(window).unwrap_or(false) - { - // cursor location - let (cx, cy) = ( - util::GET_X_LPARAM(lparam) as i32, - util::GET_Y_LPARAM(lparam) as i32, - ); - - let dpi = hwnd_dpi(window); - let border_y = get_system_metrics_for_dpi(SM_CYFRAME, dpi); - - // if we have undecorated shadows, we only need to handle the top edge - if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { - let rect = util::client_rect(window); - let mut cursor_pt = POINT { x: cx, y: cy }; - if ScreenToClient(window, &mut cursor_pt).as_bool() - && cursor_pt.y >= 0 - && cursor_pt.y <= border_y - && cursor_pt.x >= 0 - && cursor_pt.x <= rect.right - { - result = ProcResult::Value(LRESULT(HTTOP as _)); - } - } - // otherwise do full hit testing - else { - let border_x = get_system_metrics_for_dpi(SM_CXFRAME, dpi); - let rect = util::window_rect(window); - let hit_result = crate::window::hit_test( - (rect.left, rect.top, rect.right, rect.bottom), - cx, - cy, - border_x, - border_y, - ) - .map(|d| d.to_win32()); - - result = hit_result - .map(|r| ProcResult::Value(LRESULT(r as _))) - .unwrap_or(ProcResult::DefSubclassProc); - } - } else { - result = ProcResult::DefSubclassProc; - } - } - - win32wm::WM_SYSCHAR => { - // Handle system shortcut e.g. Alt+Space for window menu - result = ProcResult::DefWindowProc; - } - - _ => { - if msg == *DESTROY_MSG_ID { - let _ = DestroyWindow(window); - result = ProcResult::Value(LRESULT(0)); - } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { - let mut window_state = subclass_input.window_state.lock(); - window_state.set_window_flags_in_place(|f| { - f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam.0 != 0) - }); - result = ProcResult::Value(LRESULT(0)); - } else if msg == *CHANGE_THEME_MSG_ID { - update_theme(subclass_input, window, false); - result = ProcResult::Value(LRESULT(0)); - } else if msg == *S_U_TASKBAR_RESTART { - let window_state = subclass_input.window_state.lock(); - let _ = set_skip_taskbar(window, window_state.skip_taskbar); - } - } - }; - - subclass_input - .event_loop_runner - .catch_unwind(callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - - match result { - ProcResult::DefSubclassProc => DefSubclassProc(window, msg, wparam, lparam), - ProcResult::DefWindowProc => DefWindowProcW(window, msg, wparam, lparam), - ProcResult::Value(val) => val, - } -} - -fn update_theme( - subclass_input: &SubclassInput, - window: HWND, - from_settings_change_event: bool, -) { - let mut window_state = subclass_input.window_state.lock(); - let preferred_theme = window_state - .preferred_theme - .or(*subclass_input.event_loop_preferred_theme.lock()); - if from_settings_change_event && preferred_theme.is_some() { - return; - } - let new_theme = try_window_theme(window, preferred_theme, !from_settings_change_event); - if window_state.current_theme != new_theme { - window_state.current_theme = new_theme; - mem::drop(window_state); - unsafe { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: WindowEvent::ThemeChanged(new_theme), - }) - }; - } -} - -fn is_show_window_contents_while_dragging_enabled() -> bool { - let mut is_enabled: BOOL = BOOL(0); - let result = unsafe { - SystemParametersInfoW( - SPI_GETDRAGFULLWINDOWS, - 0, - Option::from(&mut is_enabled as *mut _ as *mut std::ffi::c_void), - SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), - ) - }; - result.is_ok() && is_enabled.0 != 0 -} - -unsafe extern "system" fn thread_event_target_callback( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, - _: usize, - subclass_input_ptr: usize, -) -> LRESULT { - let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); - - let mut subclass_removed = false; - - // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing - // the closure to catch_unwind directly so that the match body indendation wouldn't change and - // the git blame and history would be preserved. - let callback = || match msg { - win32wm::WM_NCDESTROY => { - remove_event_target_window_subclass::(window); - subclass_removed = true; - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - LRESULT(0) - } - // Because WM_PAINT comes after all other messages, we use it during modal loops to detect - // when the event queue has been emptied. See `process_event` for more details. - win32wm::WM_PAINT => { - let _ = ValidateRect(Some(window), None); - // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw - // events, `handling_events` will return false and we won't emit a second - // `RedrawEventsCleared` event. - if subclass_input.event_loop_runner.handling_events() { - if subclass_input.event_loop_runner.should_buffer() { - // This branch can be triggered when a nested win32 event loop is triggered - // inside of the `event_handler` callback. - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - } else { - // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` - // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages( - None, - &subclass_input.event_loop_runner - )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); - } - } - - // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. - DefSubclassProc(window, msg, wparam, lparam) - } - - win32wm::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam.0 as u32 { - win32wm::GIDC_ARRIVAL => DeviceEvent::Added, - win32wm::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam.0), - event, - }); - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - - LRESULT(0) - } - - win32wm::WM_INPUT => { - if let Some(data) = raw_input::get_raw_input_data(HRAWINPUT(lparam.0 as _)) { - handle_raw_input(&subclass_input, data); - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - } - - DefSubclassProc(window, msg, wparam, lparam) - } - - // We don't process `WM_QUERYENDSESSION` yet until we introduce the same mechanism as Tauri's `ExitRequested` event - // win32wm::WM_QUERYENDSESSION => {} - win32wm::WM_ENDSESSION => { - // `wParam` is `FALSE` is for if the shutdown gets canceled, - // and we don't need to handle that case since we didn't do anything prior in response to `WM_QUERYENDSESSION` - if wparam.0 == TRUE.0 as usize { - subclass_input.event_loop_runner.loop_destroyed(); - } - // Note: after we return 0 here, Windows will shut us down - LRESULT(0) - } - - _ if msg == *USER_EVENT_MSG_ID => { - if let Ok(event) = subclass_input.user_event_receiver.recv() { - subclass_input.send_event(Event::UserEvent(event)); - } - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - LRESULT(0) - } - _ if msg == *EXEC_MSG_ID => { - let mut function: ThreadExecFn = Box::from_raw(wparam.0 as *mut _); - function(); - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - LRESULT(0) - } - _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { - let _ = PostThreadMessageW( - subclass_input.event_loop_runner.wait_thread_id(), - *CANCEL_WAIT_UNTIL_MSG_ID, - WPARAM(0), - LPARAM(0), - ); - - // if the control_flow is WaitUntil, make sure the given moment has actually passed - // before emitting NewEvents - if let ControlFlow::WaitUntil(wait_until) = subclass_input.event_loop_runner.control_flow() { - let mut msg = MSG::default(); - while Instant::now() < wait_until { - if PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE).as_bool() { - // This works around a "feature" in PeekMessageW. If the message PeekMessageW - // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't - // have an update region), PeekMessageW will remove that window from the - // redraw queue even though we told it not to remove messages from the - // queue. We fix it by re-dispatching an internal paint message to that - // window. - if msg.message == WM_PAINT { - let mut rect = RECT::default(); - if !GetUpdateRect(msg.hwnd, Some(&mut rect), false).as_bool() { - let _ = RedrawWindow(Some(msg.hwnd), None, None, RDW_INTERNALPAINT); - } - } - - break; - } - } - } - subclass_input.event_loop_runner.poll(); - let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); - LRESULT(0) - } - _ => DefSubclassProc(window, msg, wparam, lparam), - }; - - let result = subclass_input - .event_loop_runner - .catch_unwind(callback) - .unwrap_or(LRESULT(-1)); - if subclass_removed { - mem::drop(subclass_input); - } else { - // FIXME: this seems to leak intentionally? - #[allow(unused_must_use)] - Box::into_raw(subclass_input); - } - result -} - -unsafe fn handle_raw_input( - subclass_input: &ThreadMsgTargetSubclassInput, - data: RAWINPUT, -) { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - - let device_id = wrap_device_id(data.header.hDevice.0 as _); - - if data.header.dwType == RIM_TYPEMOUSE.0 { - let mouse = data.data.mouse; - - if util::has_flag(mouse.usFlags.0, MOUSE_MOVE_RELATIVE.0) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); - } - } - - if util::has_flag( - mouse.Anonymous.Anonymous.usButtonFlags, - RI_MOUSE_WHEEL as u16, - ) { - // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. - let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 / WHEEL_DELTA as f32; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta), - }, - }); - } - - let button_state = - raw_input::get_raw_mouse_button_state(mouse.Anonymous.Anonymous.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } - } - } else if data.header.dwType == RIM_TYPEKEYBOARD.0 { - let keyboard = data.data.keyboard; - - let pressed = keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; - let released = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; - - if !pressed && !released { - return; - } - - let state = if pressed { Pressed } else { Released }; - let extension = { - if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) { - 0xE000 - } else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) { - 0xE100 - } else { - 0x0000 - } - }; - let scancode = if keyboard.MakeCode == 0 { - // In some cases (often with media keys) the device reports a scancode of 0 but a - // valid virtual key. In these cases we obtain the scancode from the virtual key. - MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 - } else { - keyboard.MakeCode | extension - }; - if scancode == 0xE11D || scancode == 0xE02A { - // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing - // Ctrl+NumLock. - // This equvalence means that if the user presses Pause, the keyboard will emit two - // subsequent keypresses: - // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) - // 2, 0x0045 - Which on its own can be interpreted as Pause - // - // There's another combination which isn't quite an equivalence: - // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing - // PrtSc (print screen) produces the following sequence: - // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) - // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on - // its own it can be interpreted as PrtSc - // - // For this reason, if we encounter the first keypress, we simply ignore it, trusting - // that there's going to be another event coming, from which we can extract the - // appropriate key. - // For more on this, read the article by Raymond Chen, titled: - // "Why does Ctrl+ScrollLock cancel dialogs?" - // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 - return; - } - let code = if VIRTUAL_KEY(keyboard.VKey) == VK_NUMLOCK { - // Historically, the NumLock and the Pause key were one and the same physical key. - // The user could trigger Pause by pressing Ctrl+NumLock. - // Now these are often physically separate and the two keys can be differentiated by - // checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045. - // - // However in this event, both keys are reported as 0x0045 even on modern hardware. - // Therefore we use the virtual key instead to determine whether it's a NumLock and - // set the KeyCode accordingly. - // - // For more on this, read the article by Raymond Chen, titled: - // "Why does Ctrl+ScrollLock cancel dialogs?" - // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 - KeyCode::NumLock - } else { - KeyCode::from_scancode(scancode as u32) - }; - if VIRTUAL_KEY(keyboard.VKey) == VK_SHIFT { - match code { - KeyCode::NumpadDecimal - | KeyCode::Numpad0 - | KeyCode::Numpad1 - | KeyCode::Numpad2 - | KeyCode::Numpad3 - | KeyCode::Numpad4 - | KeyCode::Numpad5 - | KeyCode::Numpad6 - | KeyCode::Numpad7 - | KeyCode::Numpad8 - | KeyCode::Numpad9 => { - // On Windows, holding the Shift key makes numpad keys behave as if NumLock - // wasn't active. The way this is exposed to applications by the system is that - // the application receives a fake key release event for the shift key at the - // moment when the numpad key is pressed, just before receiving the numpad key - // as well. - // - // The issue is that in the raw device event (here), the fake shift release - // event reports the numpad key as the scancode. Unfortunately, the event doesn't - // have any information to tell whether it's the left shift or the right shift - // that needs to get the fake release (or press) event so we don't forward this - // event to the application at all. - // - // For more on this, read the article by Raymond Chen, titled: - // "The shift key overrides NumLock" - // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 - return; - } - _ => (), - } - } - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(RawKeyEvent { - physical_key: code, - state, - }), - }); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs deleted file mode 100644 index ff865de3a..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - any::Any, - cell::{Cell, RefCell}, - collections::{HashSet, VecDeque}, - mem, panic, - rc::Rc, - time::Instant, -}; - -use windows::Win32::{ - Foundation::HWND, - Graphics::Gdi::{RedrawWindow, RDW_INTERNALPAINT}, -}; - -use crate::{ - dpi::PhysicalSize, - event::{Event, StartCause, WindowEvent}, - event_loop::ControlFlow, - platform_impl::platform::util, - window::WindowId, -}; - -pub(crate) type EventLoopRunnerShared = Rc>; -pub(crate) struct EventLoopRunner { - // The event loop's win32 handles - thread_msg_target: HWND, - wait_thread_id: u32, - - control_flow: Cell, - runner_state: Cell, - last_events_cleared: Cell, - - event_handler: Cell, &mut ControlFlow)>>>, - event_buffer: RefCell>>, - - owned_windows: Cell>, - - panic_error: Cell>, -} - -pub type PanicError = Box; - -/// See `move_state_to` function for details on how the state loop works. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum RunnerState { - /// The event loop has just been created, and an `Init` event must be sent. - Uninitialized, - /// The event loop is idling. - Idle, - /// The event loop is handling the OS's events and sending them to the user's callback. - /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. - HandlingMainEvents, - /// The event loop is handling the redraw events and sending them to the user's callback. - /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. - HandlingRedrawEvents, - /// The event loop has been destroyed. No other events will be emitted. - Destroyed, -} - -enum BufferedEvent { - Event(Event<'static, T>), - ScaleFactorChanged(WindowId, f64, PhysicalSize), -} - -impl EventLoopRunner { - pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: u32) -> EventLoopRunner { - EventLoopRunner { - thread_msg_target, - wait_thread_id, - runner_state: Cell::new(RunnerState::Uninitialized), - control_flow: Cell::new(ControlFlow::Poll), - panic_error: Cell::new(None), - last_events_cleared: Cell::new(Instant::now()), - event_handler: Cell::new(None), - event_buffer: RefCell::new(VecDeque::new()), - owned_windows: Cell::new(HashSet::new()), - } - } - - pub(crate) unsafe fn set_event_handler(&self, f: F) - where - F: FnMut(Event<'_, T>, &mut ControlFlow), - { - let old_event_handler = self.event_handler.replace(mem::transmute::< - Option, &mut ControlFlow)>>, - Option, &mut ControlFlow)>>, - >(Some(Box::new(f)))); - assert!(old_event_handler.is_none()); - } - - pub(crate) fn reset_runner(&self) { - let EventLoopRunner { - thread_msg_target: _, - wait_thread_id: _, - runner_state, - panic_error, - control_flow, - last_events_cleared: _, - event_handler, - event_buffer: _, - owned_windows: _, - } = self; - runner_state.set(RunnerState::Uninitialized); - panic_error.set(None); - control_flow.set(ControlFlow::Poll); - event_handler.set(None); - } -} - -/// State retrieval functions. -impl EventLoopRunner { - pub fn thread_msg_target(&self) -> HWND { - self.thread_msg_target - } - - pub fn wait_thread_id(&self) -> u32 { - self.wait_thread_id - } - - pub fn redrawing(&self) -> bool { - self.runner_state.get() == RunnerState::HandlingRedrawEvents - } - - pub fn take_panic_error(&self) -> Result<(), PanicError> { - match self.panic_error.take() { - Some(err) => Err(err), - None => Ok(()), - } - } - - pub fn control_flow(&self) -> ControlFlow { - self.control_flow.get() - } - - pub fn handling_events(&self) -> bool { - self.runner_state.get() != RunnerState::Idle - } - - pub fn should_buffer(&self) -> bool { - let handler = self.event_handler.take(); - let should_buffer = handler.is_none(); - self.event_handler.set(handler); - should_buffer - } -} - -/// Misc. functions -impl EventLoopRunner { - pub fn catch_unwind(&self, f: impl FnOnce() -> R) -> Option { - let panic_error = self.panic_error.take(); - if panic_error.is_none() { - let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); - - // Check to see if the panic error was set in a re-entrant call to catch_unwind inside - // of `f`. If it was, that error takes priority. If it wasn't, check if our call to - // catch_unwind caught any panics and set panic_error appropriately. - match self.panic_error.take() { - None => match result { - Ok(r) => Some(r), - Err(e) => { - self.panic_error.set(Some(e)); - None - } - }, - Some(e) => { - self.panic_error.set(Some(e)); - None - } - } - } else { - self.panic_error.set(panic_error); - None - } - } - pub fn register_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.insert(window.0 as _); - self.owned_windows.set(owned_windows); - } - - pub fn remove_window(&self, window: HWND) { - let mut owned_windows = self.owned_windows.take(); - owned_windows.remove(&(window.0 as _)); - self.owned_windows.set(owned_windows); - } - - pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { - let mut owned_windows = self.owned_windows.take(); - for hwnd in &owned_windows { - f(HWND(*hwnd as _)); - } - let new_owned_windows = self.owned_windows.take(); - owned_windows.extend(&new_owned_windows); - self.owned_windows.set(owned_windows); - } -} - -/// Event dispatch functions. -impl EventLoopRunner { - pub(crate) unsafe fn poll(&self) { - self.move_state_to(RunnerState::HandlingMainEvents); - } - - pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { - if let Event::RedrawRequested(_) = event { - if self.runner_state.get() != RunnerState::HandlingRedrawEvents { - // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. - debug!("RedrawRequested dispatched without explicit MainEventsCleared"); - self.move_state_to(RunnerState::HandlingRedrawEvents); - } - self.call_event_handler(event); - } else if self.should_buffer() { - // If the runner is already borrowed, we're in the middle of an event loop invocation. Add - // the event to a buffer to be processed later. - self - .event_buffer - .borrow_mut() - .push_back(BufferedEvent::from_event(event)) - } else { - self.move_state_to(RunnerState::HandlingMainEvents); - self.call_event_handler(event); - self.dispatch_buffered_events(); - } - } - - pub(crate) unsafe fn main_events_cleared(&self) { - self.move_state_to(RunnerState::HandlingRedrawEvents); - } - - pub(crate) unsafe fn redraw_events_cleared(&self) { - self.move_state_to(RunnerState::Idle); - } - - pub(crate) unsafe fn loop_destroyed(&self) { - self.move_state_to(RunnerState::Destroyed); - } - - unsafe fn call_event_handler(&self, event: Event<'_, T>) { - self.catch_unwind(|| { - let mut control_flow = self.control_flow.take(); - let mut event_handler = self.event_handler.take() - .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); - - if let ControlFlow::ExitWithCode(code) = control_flow { - event_handler(event, &mut ControlFlow::ExitWithCode(code)); - } else { - event_handler(event, &mut control_flow); - } - - assert!(self.event_handler.replace(Some(event_handler)).is_none()); - self.control_flow.set(control_flow); - }); - } - - unsafe fn dispatch_buffered_events(&self) { - loop { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - let buffered_event_opt = self.event_buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(e) => e.dispatch_event(|e| self.call_event_handler(e)), - None => break, - } - } - } - - /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, `RedrawEventsCleared`, and - /// `LoopDestroyed`) as necessary to bring the internal `RunnerState` to the new runner state. - /// - /// The state transitions are defined as follows: - /// - /// ```text - /// Uninitialized - /// | - /// V - /// HandlingMainEvents - /// ^ | - /// | V - /// Idle <--- HandlingRedrawEvents - /// | - /// V - /// Destroyed - /// ``` - /// - /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to - /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current - /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the - /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and - /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. - unsafe fn move_state_to(&self, new_runner_state: RunnerState) { - use RunnerState::{Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; - - match ( - self.runner_state.replace(new_runner_state), - new_runner_state, - ) { - (Uninitialized, Uninitialized) - | (Idle, Idle) - | (HandlingMainEvents, HandlingMainEvents) - | (HandlingRedrawEvents, HandlingRedrawEvents) - | (Destroyed, Destroyed) => (), - - // State transitions that initialize the event loop. - (Uninitialized, HandlingMainEvents) => { - self.call_new_events(true); - } - (Uninitialized, HandlingRedrawEvents) => { - self.call_new_events(true); - self.call_event_handler(Event::MainEventsCleared); - } - (Uninitialized, Idle) => { - self.call_new_events(true); - self.call_event_handler(Event::MainEventsCleared); - self.call_redraw_events_cleared(); - } - (Uninitialized, Destroyed) => { - self.call_new_events(true); - self.call_event_handler(Event::MainEventsCleared); - self.call_redraw_events_cleared(); - self.call_event_handler(Event::LoopDestroyed); - } - (_, Uninitialized) => panic!("cannot move state to Uninitialized"), - - // State transitions that start the event handling process. - (Idle, HandlingMainEvents) => { - self.call_new_events(false); - } - (Idle, HandlingRedrawEvents) => { - self.call_new_events(false); - self.call_event_handler(Event::MainEventsCleared); - } - (Idle, Destroyed) => { - self.call_event_handler(Event::LoopDestroyed); - } - - (HandlingMainEvents, HandlingRedrawEvents) => { - self.call_event_handler(Event::MainEventsCleared); - } - (HandlingMainEvents, Idle) => { - // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. - debug!("RedrawEventsCleared emitted without explicit MainEventsCleared"); - self.call_event_handler(Event::MainEventsCleared); - self.call_redraw_events_cleared(); - } - (HandlingMainEvents, Destroyed) => { - self.call_event_handler(Event::MainEventsCleared); - self.call_redraw_events_cleared(); - self.call_event_handler(Event::LoopDestroyed); - } - - (HandlingRedrawEvents, Idle) => { - self.call_redraw_events_cleared(); - } - (HandlingRedrawEvents, HandlingMainEvents) => { - // TODO: Consider removing log once https://github.com/rust-windowing/winit/pull/2767 gets adopted. - debug!("NewEvents emitted without explicit RedrawEventsCleared"); - self.call_redraw_events_cleared(); - self.call_new_events(false); - } - (HandlingRedrawEvents, Destroyed) => { - self.call_redraw_events_cleared(); - self.call_event_handler(Event::LoopDestroyed); - } - - (Destroyed, _) => panic!("cannot move state from Destroyed"), - } - } - - unsafe fn call_new_events(&self, init: bool) { - let start_cause = match (init, self.control_flow()) { - (true, _) => StartCause::Init, - (false, ControlFlow::Poll) => StartCause::Poll, - (false, ControlFlow::ExitWithCode(_)) | (false, ControlFlow::Wait) => { - StartCause::WaitCancelled { - requested_resume: None, - start: self.last_events_cleared.get(), - } - } - (false, ControlFlow::WaitUntil(requested_resume)) => { - if Instant::now() < requested_resume { - StartCause::WaitCancelled { - requested_resume: Some(requested_resume), - start: self.last_events_cleared.get(), - } - } else { - StartCause::ResumeTimeReached { - requested_resume, - start: self.last_events_cleared.get(), - } - } - } - }; - self.call_event_handler(Event::NewEvents(start_cause)); - self.dispatch_buffered_events(); - let _ = RedrawWindow(Some(self.thread_msg_target), None, None, RDW_INTERNALPAINT); - } - - unsafe fn call_redraw_events_cleared(&self) { - self.call_event_handler(Event::RedrawEventsCleared); - self.last_events_cleared.set(Instant::now()); - } -} - -impl BufferedEvent { - pub fn from_event(event: Event<'_, T>) -> BufferedEvent { - match event { - Event::WindowEvent { - event: - WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - }, - window_id, - } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), - event => BufferedEvent::Event(event.to_static().unwrap()), - } - } - - pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { - match self { - Self::Event(event) => dispatch(event), - Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { - let os_inner_size = new_inner_size; - - dispatch(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - - if new_inner_size != os_inner_size { - util::set_inner_size_physical( - HWND(window_id.0 .0 as _), - new_inner_size.width as _, - new_inner_size.height as _, - true, - ); - } - } - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs deleted file mode 100644 index b0d0e1bff..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{fmt, mem, path::Path, sync::Arc}; - -use windows::{ - core::PCWSTR, - Win32::{ - Foundation::{HWND, LPARAM, WPARAM}, - System::LibraryLoader::*, - UI::WindowsAndMessaging::*, - }, -}; -use windows_core::Owned; - -use crate::{dpi::PhysicalSize, icon::*, platform_impl::platform::util}; - -impl Pixel { - fn to_bgra(&mut self) { - mem::swap(&mut self.r, &mut self.b); - } -} - -impl RgbaIcon { - fn into_windows_icon(self) -> Result { - let mut rgba = self.rgba; - let pixel_count = rgba.len() / PIXEL_SIZE; - let mut and_mask = Vec::with_capacity(pixel_count); - let pixels = - unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) }; - for pixel in pixels { - and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel - pixel.to_bgra(); - } - assert_eq!(and_mask.len(), pixel_count); - let handle = unsafe { - CreateIcon( - None, - self.width as i32, - self.height as i32, - 1, - (PIXEL_SIZE * 8) as u8, - and_mask.as_ptr(), - rgba.as_ptr(), - ) - } - .map_err(|error| BadIcon::OsError(error.into()))?; - Ok(WinIcon::from_handle(HICON(handle.0))) - } -} - -#[non_exhaustive] -#[derive(Debug)] -pub enum IconType { - Small = ICON_SMALL as isize, - Big = ICON_BIG as isize, -} - -#[derive(Clone)] -pub struct WinIcon { - inner: Arc>, -} - -unsafe impl Send for WinIcon {} - -impl WinIcon { - pub fn as_raw_handle(&self) -> HICON { - **self.inner - } - - pub fn from_path>( - path: P, - size: Option>, - ) -> Result { - let wide_path = util::encode_wide(path.as_ref()); - - // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size - let (width, height) = size.map(Into::into).unwrap_or((0, 0)); - - let handle = unsafe { - LoadImageW( - None, - PCWSTR::from_raw(wide_path.as_ptr()), - IMAGE_ICON, - width, - height, - LR_DEFAULTSIZE | LR_LOADFROMFILE, - ) - } - .map_err(|error| BadIcon::OsError(error.into()))?; - Ok(WinIcon::from_handle(HICON(handle.0))) - } - - pub fn from_resource(resource_id: u16, size: Option>) -> Result { - // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size - let (width, height) = size.map(Into::into).unwrap_or((0, 0)); - let handle = unsafe { - LoadImageW( - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), - PCWSTR::from_raw(resource_id as usize as *const u16), - IMAGE_ICON, - width, - height, - LR_DEFAULTSIZE, - ) - } - .map_err(|error| BadIcon::OsError(error.into()))?; - Ok(WinIcon::from_handle(HICON(handle.0))) - } - - pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; - rgba_icon.into_windows_icon() - } - - pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { - unsafe { - SendMessageW( - hwnd, - WM_SETICON, - Some(WPARAM(icon_type as _)), - Some(LPARAM(self.as_raw_handle().0 as _)), - ); - } - } - - fn from_handle(handle: HICON) -> Self { - Self { - inner: Arc::new(unsafe { Owned::new(handle) }), - } - } -} - -impl fmt::Debug for WinIcon { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - (*self.inner).fmt(formatter) - } -} - -pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { - unsafe { - SendMessageW( - hwnd, - WM_SETICON, - Some(WPARAM(icon_type as _)), - Some(LPARAM(0)), - ); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs deleted file mode 100644 index 3e85d6a2f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs +++ /dev/null @@ -1,816 +0,0 @@ -use parking_lot::{Mutex, MutexGuard}; -use std::{ - char, - collections::{HashMap, HashSet}, - ffi::OsString, - mem::MaybeUninit, - os::windows::ffi::OsStringExt, -}; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::{ - Input::KeyboardAndMouse::{self as win32km, *}, - WindowsAndMessaging::{self as win32wm, *}, - }, -}; - -use once_cell::sync::Lazy; -use unicode_segmentation::UnicodeSegmentation; - -use crate::{ - event::{ElementState, KeyEvent}, - keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, - platform_impl::{ - platform::{ - event_loop::ProcResult, - keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, - KeyEventExtra, - }, - WindowId, - }, -}; - -pub fn is_msg_keyboard_related(msg: u32) -> bool { - let is_keyboard_msg = (WM_KEYFIRST..=WM_KEYLAST).contains(&msg); - - is_keyboard_msg || msg == WM_SETFOCUS || msg == WM_KILLFOCUS -} - -pub type ExScancode = u16; - -pub struct MessageAsKeyEvent { - pub event: KeyEvent, - pub is_synthetic: bool, -} - -pub(crate) static KEY_EVENT_BUILDERS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -/// Stores information required to make `KeyEvent`s. -/// -/// A single Tao `KeyEvent` contains information which the Windows API passes to the application -/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single -/// window message. Therefore, this type keeps track of certain information from previous events so -/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. -/// -/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the -/// current keypress. If it doesn't and the current state represents a key event waiting to be -/// dispatched, then said event is considered complete and is dispatched. -/// -/// The sequence of window messages for a key press event is the following: -/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN -/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR -/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when -/// put together in the sequence they arrived in, forms the text which is the result of pressing the -/// key. -/// -/// Key release messages are a bit different due to the fact that they don't contribute to -/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. -#[derive(Default)] -pub struct KeyEventBuilder { - event_info: Option, -} -impl KeyEventBuilder { - /// Call this function for every window message. - /// Returns Some() if this window message completes a KeyEvent. - /// Returns None otherwise. - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - lparam: LPARAM, - result: &mut ProcResult, - ) -> Vec { - match msg_kind { - win32wm::WM_SETFOCUS => { - // synthesize keydown events - let kbd_state = get_async_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Pressed, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KILLFOCUS => { - // sythesize keyup events - let kbd_state = get_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Released, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { - if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - // Don't dispatch Alt+F4 to the application. - // This is handled in `event_loop.rs` - return vec![]; - } - - if msg_kind == win32wm::WM_SYSKEYDOWN { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Pressed, &mut layouts); - - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - self.event_info = None; - let mut finished_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let next_msg_kind = next_msg.message; - let next_belongs_to_this = !matches!( - next_msg_kind, - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP - ); - if next_belongs_to_this { - self.event_info = finished_event_info.take(); - } else { - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let curr_event = finished_event_info.as_ref().unwrap(); - is_current_fake(curr_event, next_msg, layout) - }; - if is_fake { - finished_event_info = None; - } - } - } - if let Some(event_info) = finished_event_info { - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_DEADCHAR | win32wm::WM_SYSDEADCHAR => { - *result = ProcResult::Value(LRESULT(0)); - // At this point, we know that there isn't going to be any more events related to - // this key press - let event_info = self.event_info.take().unwrap(); - let mut layouts = LAYOUT_CACHE.lock(); - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - if self.event_info.is_none() { - trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); - return vec![]; - } - *result = ProcResult::Value(LRESULT(0)); - let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam.0); - let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam.0); - - let is_utf16 = is_high_surrogate || is_low_surrogate; - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - - if is_utf16 { - if let Some(ev_info) = self.event_info.as_mut() { - ev_info.utf16parts.push(wparam.0 as u16); - } - } else { - // In this case, wparam holds a UTF-32 character. - // Let's encode it as UTF-16 and append it to the end of `utf16parts` - let utf16parts = match self.event_info.as_mut() { - Some(ev_info) => &mut ev_info.utf16parts, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let start_offset = utf16parts.len(); - let new_size = utf16parts.len() + 2; - utf16parts.resize(new_size, 0); - if let Some(ch) = char::from_u32(wparam.0 as u32) { - let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); - let new_size = start_offset + encode_len; - utf16parts.resize(new_size, 0); - } - } - if !more_char_coming { - let mut event_info = match self.event_info.take() { - Some(ev_info) => ev_info, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let mut layouts = LAYOUT_CACHE.lock(); - // It's okay to call `ToUnicode` here, because at this point the dead key - // is already consumed by the character. - let kbd_state = get_kbd_state(); - let mod_state = WindowsModifiers::active_modifiers(&kbd_state); - - let (_, layout) = layouts.get_current_layout(); - let ctrl_on = if layout.has_alt_graph { - let alt_on = mod_state.contains(WindowsModifiers::ALT); - !alt_on && mod_state.contains(WindowsModifiers::CONTROL) - } else { - mod_state.contains(WindowsModifiers::CONTROL) - }; - - // If Ctrl is not pressed, just use the text with all - // modifiers because that already consumed the dead key. Otherwise, - // we would interpret the character incorrectly, missing the dead key. - if !ctrl_on { - event_info.text = PartialText::System(event_info.utf16parts.clone()); - } else { - let mod_no_ctrl = mod_state.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - let vkey = event_info.vkey; - let scancode = event_info.scancode; - let keycode = event_info.code; - let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); - event_info.text = PartialText::Text(key.to_text()); - } - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - if msg_kind == win32wm::WM_SYSKEYUP { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - let mut valid_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let event_info = valid_event_info.as_ref().unwrap(); - is_current_fake(event_info, next_msg, layout) - }; - if is_fake { - valid_event_info = None; - } - } - if let Some(event_info) = valid_event_info { - let event = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event, - is_synthetic: false, - }]; - } - } - _ => (), - } - - Vec::new() - } - - fn synthesize_kbd_state( - &mut self, - key_state: ElementState, - kbd_state: &[u8; 256], - ) -> Vec { - let mut key_events = Vec::new(); - - let mut layouts = LAYOUT_CACHE.lock(); - let (locale_id, _) = layouts.get_current_layout(); - - let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; - - // Is caps-lock active? Note that this is different from caps-lock - // being held down. - let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // We are synthesizing the press event for caps-lock first for the following reasons: - // 1. If caps-lock is *not* held down but *is* active, then we have to - // synthesize all printable keys, respecting the caps-lock state. - // 2. If caps-lock is held down, we could choose to sythesize its - // keypress after every other key, in which case all other keys *must* - // be sythesized as if the caps-lock state was be the opposite - // of what it currently is. - // -- - // For the sake of simplicity we are choosing to always sythesize - // caps-lock first, and always use the current caps-lock state - // to determine the produced text - if is_key_pressed(VK_CAPITAL) { - let event = self.create_synthetic( - VK_CAPITAL, - key_state, - caps_lock_on, - num_lock_on, - locale_id, - &mut layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - for vk in 0..256 { - let vk = VIRTUAL_KEY(vk); - match vk { - win32km::VK_CONTROL - | win32km::VK_LCONTROL - | win32km::VK_RCONTROL - | win32km::VK_SHIFT - | win32km::VK_LSHIFT - | win32km::VK_RSHIFT - | win32km::VK_MENU - | win32km::VK_LMENU - | win32km::VK_RMENU - | win32km::VK_CAPITAL => continue, - _ => (), - } - if !is_key_pressed(vk) { - continue; - } - let event = self.create_synthetic( - vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - }; - let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ - VK_LCONTROL, - VK_LSHIFT, - VK_LMENU, - VK_RCONTROL, - VK_RSHIFT, - VK_RMENU, - ]; - for vk in CLEAR_MODIFIER_VKS.iter() { - if is_key_pressed(*vk) { - let event = self.create_synthetic( - *vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - } - }; - - // Be cheeky and sequence modifier and non-modifier - // key events such that non-modifier keys are not affected - // by modifiers (except for caps-lock) - match key_state { - ElementState::Pressed => { - do_non_modifier(&mut key_events, &mut layouts); - do_modifier(&mut key_events, &mut layouts); - } - ElementState::Released => { - do_modifier(&mut key_events, &mut layouts); - do_non_modifier(&mut key_events, &mut layouts); - } - } - - key_events - } - - fn create_synthetic( - &self, - vk: VIRTUAL_KEY, - key_state: ElementState, - caps_lock_on: bool, - num_lock_on: bool, - locale_id: HKL, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Option { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; - if scancode == 0 { - return None; - } - let scancode = scancode as ExScancode; - let code = KeyCode::from_scancode(scancode as u32); - let mods = if caps_lock_on { - WindowsModifiers::CAPS_LOCK - } else { - WindowsModifiers::empty() - }; - let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); - let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); - let key_without_modifiers = - layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); - let text = if key_state == ElementState::Pressed { - logical_key.to_text() - } else { - None - }; - let event_info = PartialKeyEventInfo { - vkey: vk, - logical_key: PartialLogicalKey::This(logical_key.clone()), - key_without_modifiers, - key_state, - scancode, - is_repeat: false, - code, - location: get_location(scancode, locale_id), - utf16parts: Vec::with_capacity(8), - text: PartialText::Text(text), - }; - - let mut event = event_info.finalize(&mut layouts.strings); - event.logical_key = logical_key; - event.platform_specific.text_with_all_modifiers = text; - Some(MessageAsKeyEvent { - event, - is_synthetic: true, - }) - } -} - -enum PartialText { - // Unicode - System(Vec), - Text(Option<&'static str>), -} - -enum PartialLogicalKey { - /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If - /// the text consists of multiple grapheme clusters (user-precieved characters) that means that - /// dead key could not be combined with the second input, and in that case we should fall back - /// to using what would have without a dead-key input. - TextOr(Key<'static>), - - /// Use the value directly provided by this variant - This(Key<'static>), -} - -struct PartialKeyEventInfo { - vkey: VIRTUAL_KEY, - scancode: ExScancode, - key_state: ElementState, - is_repeat: bool, - code: KeyCode, - location: KeyLocation, - logical_key: PartialLogicalKey, - - key_without_modifiers: Key<'static>, - - /// The UTF-16 code units of the text that was produced by the keypress event. - /// This take all modifiers into account. Including CTRL - utf16parts: Vec, - - text: PartialText, -} - -impl PartialKeyEventInfo { - fn from_message( - wparam: WPARAM, - lparam: LPARAM, - state: ElementState, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Self { - const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); - - let (_, layout) = layouts.get_current_layout(); - let lparam_struct = destructure_key_lparam(lparam); - let vkey = VIRTUAL_KEY(wparam.0 as u16); - let scancode = if lparam_struct.scancode == 0 { - // In some cases (often with media keys) the device reports a scancode of 0 but a - // valid virtual key. In these cases we obtain the scancode from the virtual key. - unsafe { - MapVirtualKeyExW( - u32::from(vkey.0), - MAPVK_VK_TO_VSC_EX, - Some(HKL(layout.hkl as _)), - ) as u16 - } - } else { - new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) - }; - let code = KeyCode::from_scancode(scancode as u32); - let location = get_location(scancode, HKL(layout.hkl as _)); - - let kbd_state = get_kbd_state(); - let mods = WindowsModifiers::active_modifiers(&kbd_state); - let mods_without_ctrl = mods.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases - // the KeyCode still stores the real key, so in the name of consistency across platforms, we - // circumvent this mapping and force the key values to match the keycode. - // For more on this, read the article by Raymond Chen, titled: - // "Why does Ctrl+ScrollLock cancel dialogs?" - // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 - let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { - match code { - KeyCode::NumLock => Some(Key::NumLock), - KeyCode::Pause => Some(Key::Pause), - _ => None, - } - } else { - None - }; - - let preliminary_logical_key = - layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); - let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); - let is_pressed = state == ElementState::Pressed; - - let logical_key = if let Some(key) = code_as_key.clone() { - PartialLogicalKey::This(key) - } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { - // In some cases we want to use the UNICHAR text for logical_key in order to allow - // dead keys to have an effect on the character reported by `logical_key`. - PartialLogicalKey::TextOr(preliminary_logical_key) - } else { - PartialLogicalKey::This(preliminary_logical_key) - }; - let key_without_modifiers = if let Some(key) = code_as_key { - key - } else { - match layout.get_key(NO_MODS, false, vkey, scancode, code) { - // We convert dead keys into their character. - // The reason for this is that `key_without_modifiers` is designed for key-bindings, - // but the US International layout treats `'` (apostrophe) as a dead key and the - // reguar US layout treats it a character. In order for a single binding - // configuration to work with both layouts, we forward each dead key as a character. - Key::Dead(k) => { - if let Some(ch) = k { - // I'm avoiding the heap allocation. I don't want to talk about it :( - let mut utf8 = [0; 4]; - let s = ch.encode_utf8(&mut utf8); - let static_str = get_or_insert_str(&mut layouts.strings, s); - Key::Character(static_str) - } else { - Key::Unidentified(NativeKeyCode::Unidentified) - } - } - key => key, - } - }; - - PartialKeyEventInfo { - vkey, - scancode, - key_state: state, - logical_key, - key_without_modifiers, - is_repeat: lparam_struct.is_repeat, - code, - location, - utf16parts: Vec::with_capacity(8), - text: PartialText::System(Vec::new()), - } - } - - fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { - let mut char_with_all_modifiers = None; - if !self.utf16parts.is_empty() { - let os_string = OsString::from_wide(&self.utf16parts); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - char_with_all_modifiers = Some(static_str); - } - } - - // The text without Ctrl - let mut text = None; - match self.text { - PartialText::System(wide) => { - if !wide.is_empty() { - let os_string = OsString::from_wide(&wide); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - text = Some(static_str); - } - } - } - PartialText::Text(s) => { - text = s; - } - } - - let logical_key = match self.logical_key { - PartialLogicalKey::TextOr(fallback) => match text { - Some(s) => { - if s.grapheme_indices(true).count() > 1 { - fallback - } else { - Key::Character(s) - } - } - None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), - }, - PartialLogicalKey::This(v) => v, - }; - - KeyEvent { - physical_key: self.code, - logical_key, - text, - location: self.location, - state: self.key_state, - repeat: self.is_repeat, - platform_specific: KeyEventExtra { - text_with_all_modifiers: char_with_all_modifiers, - key_without_modifiers: self.key_without_modifiers, - }, - } - } -} - -#[derive(Debug, Copy, Clone)] -struct KeyLParam { - pub scancode: u8, - pub extended: bool, - - /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. - pub is_repeat: bool, -} - -fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { - let previous_state = (lparam.0 >> 30) & 0x01; - let transition_state = (lparam.0 >> 31) & 0x01; - KeyLParam { - scancode: ((lparam.0 >> 16) & 0xFF) as u8, - extended: ((lparam.0 >> 24) & 0x01) != 0, - is_repeat: (previous_state ^ transition_state) != 0, - } -} - -#[inline] -fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { - (scancode as u16) | (if extended { 0xE000 } else { 0 }) -} - -#[inline] -fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { - let lparam = destructure_key_lparam(lparam); - new_ex_scancode(lparam.scancode, lparam.extended) -} - -/// Gets the keyboard state as reported by messages that have been removed from the event queue. -/// See also: get_async_kbd_state -fn get_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); - let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); - kbd_state.assume_init() - } -} - -/// Gets the current keyboard state regardless of whether the corresponding keyboard events have -/// been removed from the event queue. See also: get_kbd_state -fn get_async_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: [u8; 256] = [0; 256]; - for (vk, state) in kbd_state.iter_mut().enumerate() { - let vk = VIRTUAL_KEY(vk as u16); - let async_state = GetAsyncKeyState(i32::from(vk.0)); - let is_down = (async_state & (1 << 15)) != 0; - if is_down { - *state = 0x80; - } - - if matches!( - vk, - win32km::VK_CAPITAL | win32km::VK_NUMLOCK | win32km::VK_SCROLL - ) { - // Toggle states aren't reported by `GetAsyncKeyState` - let toggle_state = GetKeyState(i32::from(vk.0)); - let is_active = (toggle_state & 1) != 0; - *state |= if is_active { 1 } else { 0 }; - } - } - kbd_state - } -} - -/// On windows, AltGr == Ctrl + Alt -/// -/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceeding -/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if -/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the -/// fake Ctrl event. -fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { - let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); - if layout.has_alt_graph { - let next_code = ex_scancode_from_lparam(next_msg.lParam); - let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt - if curr_is_ctrl && next_is_altgr { - return true; - } - } - false -} - -fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { - const VK_ABNT_C2: VIRTUAL_KEY = win32km::VK_ABNT_C2; - - let extension = 0xE000; - let extended = (scancode & extension) == extension; - let vkey = - VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); - - // Use the native VKEY and the extended flag to cover most cases - // This is taken from the `druid` GUI library, specifically - // druid-shell/src/platform/windows/keyboard.rs - match vkey { - win32km::VK_LSHIFT | win32km::VK_LCONTROL | win32km::VK_LMENU | win32km::VK_LWIN => { - KeyLocation::Left - } - win32km::VK_RSHIFT | win32km::VK_RCONTROL | win32km::VK_RMENU | win32km::VK_RWIN => { - KeyLocation::Right - } - win32km::VK_RETURN if extended => KeyLocation::Numpad, - win32km::VK_INSERT - | win32km::VK_DELETE - | win32km::VK_END - | win32km::VK_DOWN - | win32km::VK_NEXT - | win32km::VK_LEFT - | win32km::VK_CLEAR - | win32km::VK_RIGHT - | win32km::VK_HOME - | win32km::VK_UP - | win32km::VK_PRIOR => { - if extended { - KeyLocation::Standard - } else { - KeyLocation::Numpad - } - } - win32km::VK_NUMPAD0 - | win32km::VK_NUMPAD1 - | win32km::VK_NUMPAD2 - | win32km::VK_NUMPAD3 - | win32km::VK_NUMPAD4 - | win32km::VK_NUMPAD5 - | win32km::VK_NUMPAD6 - | win32km::VK_NUMPAD7 - | win32km::VK_NUMPAD8 - | win32km::VK_NUMPAD9 - | win32km::VK_DECIMAL - | win32km::VK_DIVIDE - | win32km::VK_MULTIPLY - | win32km::VK_SUBTRACT - | win32km::VK_ADD - | VK_ABNT_C2 => KeyLocation::Numpad, - _ => KeyLocation::Standard, - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs deleted file mode 100644 index 9862c5532..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs +++ /dev/null @@ -1,979 +0,0 @@ -use once_cell::sync::Lazy; -use parking_lot::Mutex; -use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, - ffi::OsString, - os::windows::ffi::OsStringExt, -}; - -use windows::Win32::{ - System::SystemServices::{LANG_JAPANESE, LANG_KOREAN}, - UI::Input::KeyboardAndMouse::{self as win32km, *}, -}; - -use super::keyboard::ExScancode; -use crate::{ - keyboard::{Key, KeyCode, ModifiersState, NativeKeyCode}, - platform_impl::platform::util, -}; - -pub(crate) static LAYOUT_CACHE: Lazy> = - Lazy::new(|| Mutex::new(LayoutCache::default())); - -fn key_pressed(vkey: VIRTUAL_KEY) -> bool { - unsafe { (GetKeyState(u32::from(vkey.0) as i32) & (1 << 15)) == (1 << 15) } -} - -const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ - VK_NUMPAD0, - VK_NUMPAD1, - VK_NUMPAD2, - VK_NUMPAD3, - VK_NUMPAD4, - VK_NUMPAD5, - VK_NUMPAD6, - VK_NUMPAD7, - VK_NUMPAD8, - VK_NUMPAD9, - VK_MULTIPLY, - VK_ADD, - VK_SEPARATOR, - VK_SUBTRACT, - VK_DECIMAL, - VK_DIVIDE, -]; - -static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { - let mut keycodes = HashSet::new(); - keycodes.insert(KeyCode::Numpad0); - keycodes.insert(KeyCode::Numpad1); - keycodes.insert(KeyCode::Numpad2); - keycodes.insert(KeyCode::Numpad3); - keycodes.insert(KeyCode::Numpad4); - keycodes.insert(KeyCode::Numpad5); - keycodes.insert(KeyCode::Numpad6); - keycodes.insert(KeyCode::Numpad7); - keycodes.insert(KeyCode::Numpad8); - keycodes.insert(KeyCode::Numpad9); - keycodes.insert(KeyCode::NumpadMultiply); - keycodes.insert(KeyCode::NumpadAdd); - keycodes.insert(KeyCode::NumpadComma); - keycodes.insert(KeyCode::NumpadSubtract); - keycodes.insert(KeyCode::NumpadDecimal); - keycodes.insert(KeyCode::NumpadDivide); - keycodes -}); - -bitflags! { - #[derive(Clone, Copy, Eq, PartialEq, Hash)] - pub struct WindowsModifiers : u8 { - const SHIFT = 1 << 0; - const CONTROL = 1 << 1; - const ALT = 1 << 2; - const CAPS_LOCK = 1 << 3; - const FLAGS_END = 1 << 4; - } -} - -impl WindowsModifiers { - pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers { - let shift = key_state[usize::from(VK_SHIFT.0)] & 0x80 != 0; - let lshift = key_state[usize::from(VK_LSHIFT.0)] & 0x80 != 0; - let rshift = key_state[usize::from(VK_RSHIFT.0)] & 0x80 != 0; - - let control = key_state[usize::from(VK_CONTROL.0)] & 0x80 != 0; - let lcontrol = key_state[usize::from(VK_LCONTROL.0)] & 0x80 != 0; - let rcontrol = key_state[usize::from(VK_RCONTROL.0)] & 0x80 != 0; - - let alt = key_state[usize::from(VK_MENU.0)] & 0x80 != 0; - let lalt = key_state[usize::from(VK_LMENU.0)] & 0x80 != 0; - let ralt = key_state[usize::from(VK_RMENU.0)] & 0x80 != 0; - - let caps = key_state[usize::from(VK_CAPITAL.0)] & 0x01 != 0; - - let mut result = WindowsModifiers::empty(); - if shift || lshift || rshift { - result.insert(WindowsModifiers::SHIFT); - } - if control || lcontrol || rcontrol { - result.insert(WindowsModifiers::CONTROL); - } - if alt || lalt || ralt { - result.insert(WindowsModifiers::ALT); - } - if caps { - result.insert(WindowsModifiers::CAPS_LOCK); - } - - result - } - - pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) { - if self.intersects(Self::SHIFT) { - key_state[usize::from(VK_SHIFT.0)] |= 0x80; - } else { - key_state[usize::from(VK_SHIFT.0)] &= !0x80; - key_state[usize::from(VK_LSHIFT.0)] &= !0x80; - key_state[usize::from(VK_RSHIFT.0)] &= !0x80; - } - if self.intersects(Self::CONTROL) { - key_state[usize::from(VK_CONTROL.0)] |= 0x80; - } else { - key_state[usize::from(VK_CONTROL.0)] &= !0x80; - key_state[usize::from(VK_LCONTROL.0)] &= !0x80; - key_state[usize::from(VK_RCONTROL.0)] &= !0x80; - } - if self.intersects(Self::ALT) { - key_state[usize::from(VK_MENU.0)] |= 0x80; - } else { - key_state[usize::from(VK_MENU.0)] &= !0x80; - key_state[usize::from(VK_LMENU.0)] &= !0x80; - key_state[usize::from(VK_RMENU.0)] &= !0x80; - } - if self.intersects(Self::CAPS_LOCK) { - key_state[usize::from(VK_CAPITAL.0)] |= 0x01; - } else { - key_state[usize::from(VK_CAPITAL.0)] &= !0x01; - } - } - - /// Removes the control modifier if the alt modifier is not present. - /// This is useful because on Windows: (Control + Alt) == AltGr - /// but we don't want to interfere with the AltGr state. - pub fn remove_only_ctrl(mut self) -> WindowsModifiers { - if !self.contains(WindowsModifiers::ALT) { - self.remove(WindowsModifiers::CONTROL); - } - self - } -} - -pub(crate) struct Layout { - pub hkl: isize, - - /// Maps numpad keys from Windows virtual key to a `Key`. - /// - /// This is useful because some numpad keys generate different charcaters based on the locale. - /// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual - /// keys are only produced by Windows when the NumLock is active. - /// - /// Making this field separate from the `keys` field saves having to add NumLock as a modifier - /// to `WindowsModifiers`, which would double the number of items in keys. - pub numlock_on_keys: HashMap>, - /// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was - /// off. The keys of this map are identical to the keys of `numlock_on_keys`. - pub numlock_off_keys: HashMap>, - - /// Maps a modifier state to group of key strings - /// We're not using `ModifiersState` here because that object cannot express caps lock, - /// but we need to handle caps lock too. - /// - /// This map shouldn't need to exist. - /// However currently this seems to be the only good way - /// of getting the label for the pressed key. Note that calling `ToUnicode` - /// just when the key is pressed/released would be enough if `ToUnicode` wouldn't - /// change the keyboard state (it clears the dead key). There is a flag to prevent - /// changing the state, but that flag requires Windows 10, version 1607 or newer) - pub keys: HashMap>>, - pub has_alt_graph: bool, -} - -impl Layout { - pub fn get_key( - &self, - mods: WindowsModifiers, - num_lock_on: bool, - vkey: VIRTUAL_KEY, - scancode: ExScancode, - keycode: KeyCode, - ) -> Key<'static> { - let native_code = NativeKeyCode::Windows(scancode); - - let unknown_alt = vkey == VK_MENU; - if !unknown_alt { - // Here we try using the virtual key directly but if the virtual key doesn't distinguish - // between left and right alt, we can't report AltGr. Therefore, we only do this if the - // key is not the "unknown alt" key. - // - // The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when - // building the keys map) sometimes maps virtual keys to odd scancodes that don't match - // the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT` - // is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`. - let key_from_vkey = - vkey_to_non_char_key(vkey, native_code, HKL(self.hkl as _), self.has_alt_graph); - - if !matches!(key_from_vkey, Key::Unidentified(_)) { - return key_from_vkey; - } - } - if num_lock_on { - if let Some(key) = self.numlock_on_keys.get(&vkey.0) { - return key.clone(); - } - } else if let Some(key) = self.numlock_off_keys.get(&vkey.0) { - return key.clone(); - } - - if let Some(keys) = self.keys.get(&mods) { - if let Some(key) = keys.get(&keycode) { - return key.clone(); - } - } - Key::Unidentified(native_code) - } -} - -#[derive(Default)] -pub(crate) struct LayoutCache { - /// Maps locale identifiers (HKL) to layouts - pub layouts: HashMap, - pub strings: HashSet<&'static str>, -} - -impl LayoutCache { - /// Checks whether the current layout is already known and - /// prepares the layout if it isn't known. - /// The current layout is then returned. - pub fn get_current_layout<'a>(&'a mut self) -> (HKL, &'a Layout) { - let locale_id = unsafe { GetKeyboardLayout(0) }; - match self.layouts.entry(locale_id.0 as _) { - Entry::Occupied(entry) => (locale_id, entry.into_mut()), - Entry::Vacant(entry) => { - let layout = Self::prepare_layout(&mut self.strings, locale_id); - (locale_id, entry.insert(layout)) - } - } - } - - pub fn get_agnostic_mods(&mut self) -> ModifiersState { - let (_, layout) = self.get_current_layout(); - let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); - mods.set( - ModifiersState::CONTROL, - key_pressed(VK_CONTROL) && !filter_out_altgr, - ); - mods.set( - ModifiersState::ALT, - key_pressed(VK_MENU) && !filter_out_altgr, - ); - mods.set( - ModifiersState::SUPER, - key_pressed(VK_LWIN) || key_pressed(VK_RWIN), - ); - mods - } - - fn prepare_layout(strings: &mut HashSet<&'static str>, locale_id: HKL) -> Layout { - let mut layout = Layout { - hkl: locale_id.0 as _, - numlock_on_keys: Default::default(), - numlock_off_keys: Default::default(), - keys: Default::default(), - has_alt_graph: false, - }; - - // We initialize the keyboard state with all zeros to - // simulate a scenario when no modifier is active. - let mut key_state = [0u8; 256]; - - // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock - // was off. We rely on this behavior to find all virtual keys which are not numpad-specific - // but map to the numpad. - // - // src_vkey: VK ==> scancode: u16 (on the numpad) - // - // Then we convert the source virtual key into a `Key` and the scancode into a virtual key - // to get the reverse mapping. - // - // src_vkey: VK ==> scancode: u16 (on the numpad) - // || || - // \/ \/ - // map_value: Key <- map_vkey: VK - layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); - for vk in 0_u16..256 { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk), MAPVK_VK_TO_VSC_EX, Some(locale_id as HKL)) }; - if scancode == 0 { - continue; - } - let vk = VIRTUAL_KEY(vk); - let keycode = KeyCode::from_scancode(scancode); - if !is_numpad_specific(vk) && NUMPAD_KEYCODES.contains(&keycode) { - let native_code = NativeKeyCode::Windows(scancode as u16); - let map_vkey = keycode_to_vkey(keycode, locale_id); - if map_vkey == Default::default() { - continue; - } - let map_value = vkey_to_non_char_key(vk, native_code, locale_id, false); - if matches!(map_value, Key::Unidentified(_)) { - continue; - } - layout.numlock_off_keys.insert(map_vkey.0, map_value); - } - } - - layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); - for vk in NUMPAD_VKEYS.iter() { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id as HKL)) }; - let unicode = Self::to_unicode_string(&key_state, *vk, scancode, locale_id); - if let ToUnicodeResult::Str(s) = unicode { - let static_str = get_or_insert_str(strings, s); - layout - .numlock_on_keys - .insert(vk.0, Key::Character(static_str)); - } - } - - // Iterate through every combination of modifiers - let mods_end = WindowsModifiers::FLAGS_END.bits(); - for mod_state in 0..mods_end { - let mut keys_for_this_mod = HashMap::with_capacity(256); - - let mod_state = WindowsModifiers::from_bits_truncate(mod_state); - mod_state.apply_to_kbd_state(&mut key_state); - - // Virtual key values are in the domain [0, 255]. - // This is reinforced by the fact that the keyboard state array has 256 - // elements. This array is allowed to be indexed by virtual key values - // giving the key state for the virtual key used for indexing. - for vk in 0_u16..256 { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; - if scancode == 0 { - continue; - } - let vk = VIRTUAL_KEY(vk); - let native_code = NativeKeyCode::Windows(scancode as ExScancode); - let key_code = KeyCode::from_scancode(scancode); - // Let's try to get the key from just the scancode and vk - // We don't necessarily know yet if AltGraph is present on this layout so we'll - // assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to - // "AltGr" in case we find out that there's an AltGraph. - let preliminary_key = vkey_to_non_char_key(vk, native_code, locale_id, false); - match preliminary_key { - Key::Unidentified(_) => (), - _ => { - keys_for_this_mod.insert(key_code, preliminary_key); - continue; - } - } - - let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); - let key = match unicode { - ToUnicodeResult::Str(str) => { - let static_str = get_or_insert_str(strings, str); - Key::Character(static_str) - } - ToUnicodeResult::Dead(dead_char) => { - //#[cfg(debug_assertions)] println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char); - Key::Dead(dead_char) - } - ToUnicodeResult::None => { - let has_alt = mod_state.contains(WindowsModifiers::ALT); - let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); - // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad - // divide key, so we handle that explicitly here - if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { - Key::Character("/") - } else { - // Just use the unidentified key, we got earlier - preliminary_key - } - } - }; - - // Check for alt graph. - // The logic is that if a key pressed with no modifier produces - // a different `Character` from when it's pressed with CTRL+ALT then the layout - // has AltGr. - let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; - let is_in_ctrl_alt = mod_state == ctrl_alt; - if !layout.has_alt_graph && is_in_ctrl_alt { - // Unwrapping here because if we are in the ctrl+alt modifier state - // then the alt modifier state must have come before. - let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); - if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { - if let Key::Character(key) = key.clone() { - layout.has_alt_graph = key != *key_no_altgr; - } - } - } - - keys_for_this_mod.insert(key_code, key); - } - layout.keys.insert(mod_state, keys_for_this_mod); - } - - // Second pass: replace right alt keys with AltGr if the layout has alt graph - if layout.has_alt_graph { - for mod_state in 0..mods_end { - let mod_state = WindowsModifiers::from_bits_truncate(mod_state); - if let Some(keys) = layout.keys.get_mut(&mod_state) { - if let Some(key) = keys.get_mut(&KeyCode::AltRight) { - *key = Key::AltGraph; - } - } - } - } - - layout - } - - fn to_unicode_string( - key_state: &[u8; 256], - vkey: VIRTUAL_KEY, - scancode: u32, - locale_id: HKL, - ) -> ToUnicodeResult { - unsafe { - let mut label_wide = [0u16; 8]; - let mut wide_len = ToUnicodeEx( - u32::from(vkey.0), - scancode, - key_state, - &mut label_wide, - 0, - Some(locale_id), - ); - if wide_len < 0 { - // If it's dead, we run `ToUnicode` again to consume the dead-key - wide_len = ToUnicodeEx( - u32::from(vkey.0), - scancode, - key_state, - &mut label_wide, - 0, - Some(locale_id), - ); - if wide_len > 0 { - let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); - if let Ok(label_str) = os_string.into_string() { - if let Some(ch) = label_str.chars().next() { - return ToUnicodeResult::Dead(Some(ch)); - } - } - } - return ToUnicodeResult::Dead(None); - } - if wide_len > 0 { - let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); - if let Ok(label_str) = os_string.into_string() { - return ToUnicodeResult::Str(label_str); - } - } - } - ToUnicodeResult::None - } -} - -pub fn get_or_insert_str(strings: &mut HashSet<&'static str>, string: T) -> &'static str -where - T: AsRef, - String: From, -{ - { - let str_ref = string.as_ref(); - if let Some(&existing) = strings.get(str_ref) { - return existing; - } - } - let leaked = Box::leak(Box::from(String::from(string))); - strings.insert(leaked); - leaked -} - -#[derive(Debug, Clone, Eq, PartialEq)] -enum ToUnicodeResult { - Str(String), - Dead(Option), - None, -} - -fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { - matches!( - vk, - win32km::VK_NUMPAD0 - | win32km::VK_NUMPAD1 - | win32km::VK_NUMPAD2 - | win32km::VK_NUMPAD3 - | win32km::VK_NUMPAD4 - | win32km::VK_NUMPAD5 - | win32km::VK_NUMPAD6 - | win32km::VK_NUMPAD7 - | win32km::VK_NUMPAD8 - | win32km::VK_NUMPAD9 - | win32km::VK_ADD - | win32km::VK_SUBTRACT - | win32km::VK_DIVIDE - | win32km::VK_DECIMAL - | win32km::VK_SEPARATOR - ) -} - -fn keycode_to_vkey(keycode: KeyCode, hkl: HKL) -> VIRTUAL_KEY { - let primary_lang_id = util::PRIMARYLANGID(hkl); - let is_korean = primary_lang_id == LANG_KOREAN; - let is_japanese = primary_lang_id == LANG_JAPANESE; - - match keycode { - KeyCode::Backquote => VIRTUAL_KEY::default(), - KeyCode::Backslash => VIRTUAL_KEY::default(), - KeyCode::BracketLeft => VIRTUAL_KEY::default(), - KeyCode::BracketRight => VIRTUAL_KEY::default(), - KeyCode::Comma => VIRTUAL_KEY::default(), - KeyCode::Digit0 => VIRTUAL_KEY::default(), - KeyCode::Digit1 => VIRTUAL_KEY::default(), - KeyCode::Digit2 => VIRTUAL_KEY::default(), - KeyCode::Digit3 => VIRTUAL_KEY::default(), - KeyCode::Digit4 => VIRTUAL_KEY::default(), - KeyCode::Digit5 => VIRTUAL_KEY::default(), - KeyCode::Digit6 => VIRTUAL_KEY::default(), - KeyCode::Digit7 => VIRTUAL_KEY::default(), - KeyCode::Digit8 => VIRTUAL_KEY::default(), - KeyCode::Digit9 => VIRTUAL_KEY::default(), - KeyCode::Equal => VIRTUAL_KEY::default(), - KeyCode::IntlBackslash => VIRTUAL_KEY::default(), - KeyCode::IntlRo => VIRTUAL_KEY::default(), - KeyCode::IntlYen => VIRTUAL_KEY::default(), - KeyCode::KeyA => VIRTUAL_KEY::default(), - KeyCode::KeyB => VIRTUAL_KEY::default(), - KeyCode::KeyC => VIRTUAL_KEY::default(), - KeyCode::KeyD => VIRTUAL_KEY::default(), - KeyCode::KeyE => VIRTUAL_KEY::default(), - KeyCode::KeyF => VIRTUAL_KEY::default(), - KeyCode::KeyG => VIRTUAL_KEY::default(), - KeyCode::KeyH => VIRTUAL_KEY::default(), - KeyCode::KeyI => VIRTUAL_KEY::default(), - KeyCode::KeyJ => VIRTUAL_KEY::default(), - KeyCode::KeyK => VIRTUAL_KEY::default(), - KeyCode::KeyL => VIRTUAL_KEY::default(), - KeyCode::KeyM => VIRTUAL_KEY::default(), - KeyCode::KeyN => VIRTUAL_KEY::default(), - KeyCode::KeyO => VIRTUAL_KEY::default(), - KeyCode::KeyP => VIRTUAL_KEY::default(), - KeyCode::KeyQ => VIRTUAL_KEY::default(), - KeyCode::KeyR => VIRTUAL_KEY::default(), - KeyCode::KeyS => VIRTUAL_KEY::default(), - KeyCode::KeyT => VIRTUAL_KEY::default(), - KeyCode::KeyU => VIRTUAL_KEY::default(), - KeyCode::KeyV => VIRTUAL_KEY::default(), - KeyCode::KeyW => VIRTUAL_KEY::default(), - KeyCode::KeyX => VIRTUAL_KEY::default(), - KeyCode::KeyY => VIRTUAL_KEY::default(), - KeyCode::KeyZ => VIRTUAL_KEY::default(), - KeyCode::Minus => VIRTUAL_KEY::default(), - KeyCode::Period => VIRTUAL_KEY::default(), - KeyCode::Quote => VIRTUAL_KEY::default(), - KeyCode::Semicolon => VIRTUAL_KEY::default(), - KeyCode::Slash => VIRTUAL_KEY::default(), - KeyCode::AltLeft => VK_LMENU, - KeyCode::AltRight => VK_RMENU, - KeyCode::Backspace => VK_BACK, - KeyCode::CapsLock => VK_CAPITAL, - KeyCode::ContextMenu => VK_APPS, - KeyCode::ControlLeft => VK_LCONTROL, - KeyCode::ControlRight => VK_RCONTROL, - KeyCode::Enter => VK_RETURN, - KeyCode::SuperLeft => VK_LWIN, - KeyCode::SuperRight => VK_RWIN, - KeyCode::ShiftLeft => VK_RSHIFT, - KeyCode::ShiftRight => VK_LSHIFT, - KeyCode::Space => VK_SPACE, - KeyCode::Tab => VK_TAB, - KeyCode::Convert => VK_CONVERT, - KeyCode::KanaMode => VK_KANA, - KeyCode::Lang1 if is_korean => VK_HANGUL, - KeyCode::Lang1 if is_japanese => VK_KANA, - KeyCode::Lang2 if is_korean => VK_HANJA, - KeyCode::Lang2 if is_japanese => VIRTUAL_KEY::default(), - KeyCode::Lang3 if is_japanese => VK_OEM_FINISH, - KeyCode::Lang4 if is_japanese => VIRTUAL_KEY::default(), - KeyCode::Lang5 if is_japanese => VIRTUAL_KEY::default(), - KeyCode::NonConvert => VK_NONCONVERT, - KeyCode::Delete => VK_DELETE, - KeyCode::End => VK_END, - KeyCode::Help => VK_HELP, - KeyCode::Home => VK_HOME, - KeyCode::Insert => VK_INSERT, - KeyCode::PageDown => VK_NEXT, - KeyCode::PageUp => VK_PRIOR, - KeyCode::ArrowDown => VK_DOWN, - KeyCode::ArrowLeft => VK_LEFT, - KeyCode::ArrowRight => VK_RIGHT, - KeyCode::ArrowUp => VK_UP, - KeyCode::NumLock => VK_NUMLOCK, - KeyCode::Numpad0 => VK_NUMPAD0, - KeyCode::Numpad1 => VK_NUMPAD1, - KeyCode::Numpad2 => VK_NUMPAD2, - KeyCode::Numpad3 => VK_NUMPAD3, - KeyCode::Numpad4 => VK_NUMPAD4, - KeyCode::Numpad5 => VK_NUMPAD5, - KeyCode::Numpad6 => VK_NUMPAD6, - KeyCode::Numpad7 => VK_NUMPAD7, - KeyCode::Numpad8 => VK_NUMPAD8, - KeyCode::Numpad9 => VK_NUMPAD9, - KeyCode::NumpadAdd => VK_ADD, - KeyCode::NumpadBackspace => VK_BACK, - KeyCode::NumpadClear => VK_CLEAR, - KeyCode::NumpadClearEntry => VIRTUAL_KEY::default(), - KeyCode::NumpadComma => VK_SEPARATOR, - KeyCode::NumpadDecimal => VK_DECIMAL, - KeyCode::NumpadDivide => VK_DIVIDE, - KeyCode::NumpadEnter => VK_RETURN, - KeyCode::NumpadEqual => VIRTUAL_KEY::default(), - KeyCode::NumpadHash => VIRTUAL_KEY::default(), - KeyCode::NumpadMemoryAdd => VIRTUAL_KEY::default(), - KeyCode::NumpadMemoryClear => VIRTUAL_KEY::default(), - KeyCode::NumpadMemoryRecall => VIRTUAL_KEY::default(), - KeyCode::NumpadMemoryStore => VIRTUAL_KEY::default(), - KeyCode::NumpadMemorySubtract => VIRTUAL_KEY::default(), - KeyCode::NumpadMultiply => VK_MULTIPLY, - KeyCode::NumpadParenLeft => VIRTUAL_KEY::default(), - KeyCode::NumpadParenRight => VIRTUAL_KEY::default(), - KeyCode::NumpadStar => VIRTUAL_KEY::default(), - KeyCode::NumpadSubtract => VK_SUBTRACT, - KeyCode::Escape => VK_ESCAPE, - KeyCode::Fn => VIRTUAL_KEY::default(), - KeyCode::FnLock => VIRTUAL_KEY::default(), - KeyCode::PrintScreen => VK_SNAPSHOT, - KeyCode::ScrollLock => VK_SCROLL, - KeyCode::Pause => VK_PAUSE, - KeyCode::BrowserBack => VK_BROWSER_BACK, - KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES, - KeyCode::BrowserForward => VK_BROWSER_FORWARD, - KeyCode::BrowserHome => VK_BROWSER_HOME, - KeyCode::BrowserRefresh => VK_BROWSER_REFRESH, - KeyCode::BrowserSearch => VK_BROWSER_SEARCH, - KeyCode::BrowserStop => VK_BROWSER_STOP, - KeyCode::Eject => VIRTUAL_KEY::default(), - KeyCode::LaunchApp1 => VK_LAUNCH_APP1, - KeyCode::LaunchApp2 => VK_LAUNCH_APP2, - KeyCode::LaunchMail => VK_LAUNCH_MAIL, - KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, - KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT, - KeyCode::MediaStop => VK_MEDIA_STOP, - KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK, - KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK, - KeyCode::Power => VIRTUAL_KEY::default(), - KeyCode::Sleep => VIRTUAL_KEY::default(), - KeyCode::AudioVolumeDown => VK_VOLUME_DOWN, - KeyCode::AudioVolumeMute => VK_VOLUME_MUTE, - KeyCode::AudioVolumeUp => VK_VOLUME_UP, - KeyCode::WakeUp => VIRTUAL_KEY::default(), - KeyCode::Hyper => VIRTUAL_KEY::default(), - KeyCode::Turbo => VIRTUAL_KEY::default(), - KeyCode::Abort => VIRTUAL_KEY::default(), - KeyCode::Resume => VIRTUAL_KEY::default(), - KeyCode::Suspend => VIRTUAL_KEY::default(), - KeyCode::Again => VIRTUAL_KEY::default(), - KeyCode::Copy => VIRTUAL_KEY::default(), - KeyCode::Cut => VIRTUAL_KEY::default(), - KeyCode::Find => VIRTUAL_KEY::default(), - KeyCode::Open => VIRTUAL_KEY::default(), - KeyCode::Paste => VIRTUAL_KEY::default(), - KeyCode::Props => VIRTUAL_KEY::default(), - KeyCode::Select => VK_SELECT, - KeyCode::Undo => VIRTUAL_KEY::default(), - KeyCode::Hiragana => VIRTUAL_KEY::default(), - KeyCode::Katakana => VIRTUAL_KEY::default(), - KeyCode::F1 => VK_F1, - KeyCode::F2 => VK_F2, - KeyCode::F3 => VK_F3, - KeyCode::F4 => VK_F4, - KeyCode::F5 => VK_F5, - KeyCode::F6 => VK_F6, - KeyCode::F7 => VK_F7, - KeyCode::F8 => VK_F8, - KeyCode::F9 => VK_F9, - KeyCode::F10 => VK_F10, - KeyCode::F11 => VK_F11, - KeyCode::F12 => VK_F12, - KeyCode::F13 => VK_F13, - KeyCode::F14 => VK_F14, - KeyCode::F15 => VK_F15, - KeyCode::F16 => VK_F16, - KeyCode::F17 => VK_F17, - KeyCode::F18 => VK_F18, - KeyCode::F19 => VK_F19, - KeyCode::F20 => VK_F20, - KeyCode::F21 => VK_F21, - KeyCode::F22 => VK_F22, - KeyCode::F23 => VK_F23, - KeyCode::F24 => VK_F24, - KeyCode::F25 => VIRTUAL_KEY::default(), - KeyCode::F26 => VIRTUAL_KEY::default(), - KeyCode::F27 => VIRTUAL_KEY::default(), - KeyCode::F28 => VIRTUAL_KEY::default(), - KeyCode::F29 => VIRTUAL_KEY::default(), - KeyCode::F30 => VIRTUAL_KEY::default(), - KeyCode::F31 => VIRTUAL_KEY::default(), - KeyCode::F32 => VIRTUAL_KEY::default(), - KeyCode::F33 => VIRTUAL_KEY::default(), - KeyCode::F34 => VIRTUAL_KEY::default(), - KeyCode::F35 => VIRTUAL_KEY::default(), - KeyCode::Unidentified(_) => VIRTUAL_KEY::default(), - _ => VIRTUAL_KEY::default(), - } -} - -/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to -/// a `Key`, with only the information passed in as arguments, are converted. -/// -/// In other words: this function does not need to "prepare" the current layout in order to do -/// the conversion, but as such it cannot convert certain keys, like language-specific character keys. -/// -/// The result includes all non-character keys defined within `Key` plus characters from numpad keys. -/// For example, backspace and tab are included. -fn vkey_to_non_char_key( - vkey: VIRTUAL_KEY, - native_code: NativeKeyCode, - hkl: HKL, - has_alt_graph: bool, -) -> Key<'static> { - // List of the Web key names and their corresponding platform-native key names: - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - - let primary_lang_id = util::PRIMARYLANGID(hkl); - let is_korean = primary_lang_id == LANG_KOREAN; - let is_japanese = primary_lang_id == LANG_JAPANESE; - - match vkey { - win32km::VK_LBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse - win32km::VK_RBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse - - // I don't think this can be represented with a Key - win32km::VK_CANCEL => Key::Unidentified(native_code), - - win32km::VK_MBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse - win32km::VK_XBUTTON1 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse - win32km::VK_XBUTTON2 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse - win32km::VK_BACK => Key::Backspace, - win32km::VK_TAB => Key::Tab, - win32km::VK_CLEAR => Key::Clear, - win32km::VK_RETURN => Key::Enter, - win32km::VK_SHIFT => Key::Shift, - win32km::VK_CONTROL => Key::Control, - win32km::VK_MENU => Key::Alt, - win32km::VK_PAUSE => Key::Pause, - win32km::VK_CAPITAL => Key::CapsLock, - - //win32km::VK_HANGEUL => Key::HangulMode, // Deprecated in favour of VK_HANGUL - - // VK_HANGUL and VK_KANA are defined as the same constant, therefore - // we use appropriate conditions to differentate between them - win32km::VK_HANGUL if is_korean => Key::HangulMode, - win32km::VK_KANA if is_japanese => Key::KanaMode, - - win32km::VK_JUNJA => Key::JunjaMode, - win32km::VK_FINAL => Key::FinalMode, - - // VK_HANJA and VK_KANJI are defined as the same constant, therefore - // we use appropriate conditions to differentate between them - win32km::VK_HANJA if is_korean => Key::HanjaMode, - win32km::VK_KANJI if is_japanese => Key::KanjiMode, - - win32km::VK_ESCAPE => Key::Escape, - win32km::VK_CONVERT => Key::Convert, - win32km::VK_NONCONVERT => Key::NonConvert, - win32km::VK_ACCEPT => Key::Accept, - win32km::VK_MODECHANGE => Key::ModeChange, - win32km::VK_SPACE => Key::Space, - win32km::VK_PRIOR => Key::PageUp, - win32km::VK_NEXT => Key::PageDown, - win32km::VK_END => Key::End, - win32km::VK_HOME => Key::Home, - win32km::VK_LEFT => Key::ArrowLeft, - win32km::VK_UP => Key::ArrowUp, - win32km::VK_RIGHT => Key::ArrowRight, - win32km::VK_DOWN => Key::ArrowDown, - win32km::VK_SELECT => Key::Select, - win32km::VK_PRINT => Key::Print, - win32km::VK_EXECUTE => Key::Execute, - win32km::VK_SNAPSHOT => Key::PrintScreen, - win32km::VK_INSERT => Key::Insert, - win32km::VK_DELETE => Key::Delete, - win32km::VK_HELP => Key::Help, - win32km::VK_LWIN => Key::Super, - win32km::VK_RWIN => Key::Super, - win32km::VK_APPS => Key::ContextMenu, - win32km::VK_SLEEP => Key::Standby, - - // Numpad keys produce characters - win32km::VK_NUMPAD0 => Key::Unidentified(native_code), - win32km::VK_NUMPAD1 => Key::Unidentified(native_code), - win32km::VK_NUMPAD2 => Key::Unidentified(native_code), - win32km::VK_NUMPAD3 => Key::Unidentified(native_code), - win32km::VK_NUMPAD4 => Key::Unidentified(native_code), - win32km::VK_NUMPAD5 => Key::Unidentified(native_code), - win32km::VK_NUMPAD6 => Key::Unidentified(native_code), - win32km::VK_NUMPAD7 => Key::Unidentified(native_code), - win32km::VK_NUMPAD8 => Key::Unidentified(native_code), - win32km::VK_NUMPAD9 => Key::Unidentified(native_code), - win32km::VK_MULTIPLY => Key::Unidentified(native_code), - win32km::VK_ADD => Key::Unidentified(native_code), - win32km::VK_SEPARATOR => Key::Unidentified(native_code), - win32km::VK_SUBTRACT => Key::Unidentified(native_code), - win32km::VK_DECIMAL => Key::Unidentified(native_code), - win32km::VK_DIVIDE => Key::Unidentified(native_code), - - win32km::VK_F1 => Key::F1, - win32km::VK_F2 => Key::F2, - win32km::VK_F3 => Key::F3, - win32km::VK_F4 => Key::F4, - win32km::VK_F5 => Key::F5, - win32km::VK_F6 => Key::F6, - win32km::VK_F7 => Key::F7, - win32km::VK_F8 => Key::F8, - win32km::VK_F9 => Key::F9, - win32km::VK_F10 => Key::F10, - win32km::VK_F11 => Key::F11, - win32km::VK_F12 => Key::F12, - win32km::VK_F13 => Key::F13, - win32km::VK_F14 => Key::F14, - win32km::VK_F15 => Key::F15, - win32km::VK_F16 => Key::F16, - win32km::VK_F17 => Key::F17, - win32km::VK_F18 => Key::F18, - win32km::VK_F19 => Key::F19, - win32km::VK_F20 => Key::F20, - win32km::VK_F21 => Key::F21, - win32km::VK_F22 => Key::F22, - win32km::VK_F23 => Key::F23, - win32km::VK_F24 => Key::F24, - win32km::VK_NAVIGATION_VIEW => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_MENU => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_UP => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_DOWN => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_LEFT => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), - win32km::VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), - win32km::VK_NUMLOCK => Key::NumLock, - win32km::VK_SCROLL => Key::ScrollLock, - win32km::VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), - //win32km::VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` - win32km::VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), - win32km::VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), - win32km::VK_OEM_FJ_LOYA => Key::Unidentified(native_code), - win32km::VK_OEM_FJ_ROYA => Key::Unidentified(native_code), - win32km::VK_LSHIFT => Key::Shift, - win32km::VK_RSHIFT => Key::Shift, - win32km::VK_LCONTROL => Key::Control, - win32km::VK_RCONTROL => Key::Control, - win32km::VK_LMENU => Key::Alt, - win32km::VK_RMENU => { - if has_alt_graph { - Key::AltGraph - } else { - Key::Alt - } - } - win32km::VK_BROWSER_BACK => Key::BrowserBack, - win32km::VK_BROWSER_FORWARD => Key::BrowserForward, - win32km::VK_BROWSER_REFRESH => Key::BrowserRefresh, - win32km::VK_BROWSER_STOP => Key::BrowserStop, - win32km::VK_BROWSER_SEARCH => Key::BrowserSearch, - win32km::VK_BROWSER_FAVORITES => Key::BrowserFavorites, - win32km::VK_BROWSER_HOME => Key::BrowserHome, - win32km::VK_VOLUME_MUTE => Key::AudioVolumeMute, - win32km::VK_VOLUME_DOWN => Key::AudioVolumeDown, - win32km::VK_VOLUME_UP => Key::AudioVolumeUp, - win32km::VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext, - win32km::VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious, - win32km::VK_MEDIA_STOP => Key::MediaStop, - win32km::VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause, - win32km::VK_LAUNCH_MAIL => Key::LaunchMail, - win32km::VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer, - win32km::VK_LAUNCH_APP1 => Key::LaunchApplication1, - win32km::VK_LAUNCH_APP2 => Key::LaunchApplication2, - - // This function only converts "non-printable" - win32km::VK_OEM_1 => Key::Unidentified(native_code), - win32km::VK_OEM_PLUS => Key::Unidentified(native_code), - win32km::VK_OEM_COMMA => Key::Unidentified(native_code), - win32km::VK_OEM_MINUS => Key::Unidentified(native_code), - win32km::VK_OEM_PERIOD => Key::Unidentified(native_code), - win32km::VK_OEM_2 => Key::Unidentified(native_code), - win32km::VK_OEM_3 => Key::Unidentified(native_code), - - win32km::VK_GAMEPAD_A => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_B => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_X => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_Y => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_MENU => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_VIEW => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), - win32km::VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), - - // This function only converts "non-printable" - win32km::VK_OEM_4 => Key::Unidentified(native_code), - win32km::VK_OEM_5 => Key::Unidentified(native_code), - win32km::VK_OEM_6 => Key::Unidentified(native_code), - win32km::VK_OEM_7 => Key::Unidentified(native_code), - win32km::VK_OEM_8 => Key::Unidentified(native_code), - win32km::VK_OEM_AX => Key::Unidentified(native_code), - win32km::VK_OEM_102 => Key::Unidentified(native_code), - - win32km::VK_ICO_HELP => Key::Unidentified(native_code), - win32km::VK_ICO_00 => Key::Unidentified(native_code), - - win32km::VK_PROCESSKEY => Key::Process, - - win32km::VK_ICO_CLEAR => Key::Unidentified(native_code), - win32km::VK_PACKET => Key::Unidentified(native_code), - win32km::VK_OEM_RESET => Key::Unidentified(native_code), - win32km::VK_OEM_JUMP => Key::Unidentified(native_code), - win32km::VK_OEM_PA1 => Key::Unidentified(native_code), - win32km::VK_OEM_PA2 => Key::Unidentified(native_code), - win32km::VK_OEM_PA3 => Key::Unidentified(native_code), - win32km::VK_OEM_WSCTRL => Key::Unidentified(native_code), - win32km::VK_OEM_CUSEL => Key::Unidentified(native_code), - - win32km::VK_OEM_ATTN => Key::Attn, - win32km::VK_OEM_FINISH => { - if is_japanese { - Key::Katakana - } else { - // This matches IE and Firefox behaviour according to - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - // At the time of writing, there is no `Key::Finish` variant as - // Finish is not mentionned at https://w3c.github.io/uievents-key/ - // Also see: https://github.com/pyfisch/keyboard-types/issues/9 - Key::Unidentified(native_code) - } - } - win32km::VK_OEM_COPY => Key::Copy, - win32km::VK_OEM_AUTO => Key::Hankaku, - win32km::VK_OEM_ENLW => Key::Zenkaku, - win32km::VK_OEM_BACKTAB => Key::Romaji, - win32km::VK_ATTN => Key::KanaMode, - win32km::VK_CRSEL => Key::CrSel, - win32km::VK_EXSEL => Key::ExSel, - win32km::VK_EREOF => Key::EraseEof, - win32km::VK_PLAY => Key::Play, - win32km::VK_ZOOM => Key::ZoomToggle, - win32km::VK_NONAME => Key::Unidentified(native_code), - win32km::VK_PA1 => Key::Unidentified(native_code), - win32km::VK_OEM_CLEAR => Key::Clear, - _ => Key::Unidentified(native_code), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs deleted file mode 100644 index df8855bb0..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs +++ /dev/null @@ -1,338 +0,0 @@ -use crate::{ - keyboard::{KeyCode, NativeKeyCode}, - platform_impl::platform::util, -}; -use windows::Win32::{ - System::SystemServices::LANG_KOREAN, UI::Input::KeyboardAndMouse::GetKeyboardLayout, -}; - -pub fn keycode_to_scancode(code: KeyCode) -> Option { - // See `from_scancode` for more info - let hkl = unsafe { GetKeyboardLayout(0) }; - - let primary_lang_id = util::PRIMARYLANGID(hkl); - let is_korean = primary_lang_id == LANG_KOREAN; - - match code { - KeyCode::Backquote => Some(0x0029), - KeyCode::Backslash => Some(0x002B), - KeyCode::Backspace => Some(0x000E), - KeyCode::BracketLeft => Some(0x001A), - KeyCode::BracketRight => Some(0x001B), - KeyCode::Comma => Some(0x0033), - KeyCode::Digit0 => Some(0x000B), - KeyCode::Digit1 => Some(0x0002), - KeyCode::Digit2 => Some(0x0003), - KeyCode::Digit3 => Some(0x0004), - KeyCode::Digit4 => Some(0x0005), - KeyCode::Digit5 => Some(0x0006), - KeyCode::Digit6 => Some(0x0007), - KeyCode::Digit7 => Some(0x0008), - KeyCode::Digit8 => Some(0x0009), - KeyCode::Digit9 => Some(0x000A), - KeyCode::Equal => Some(0x000D), - KeyCode::IntlBackslash => Some(0x0056), - KeyCode::IntlRo => Some(0x0073), - KeyCode::IntlYen => Some(0x007D), - KeyCode::KeyA => Some(0x001E), - KeyCode::KeyB => Some(0x0030), - KeyCode::KeyC => Some(0x002E), - KeyCode::KeyD => Some(0x0020), - KeyCode::KeyE => Some(0x0012), - KeyCode::KeyF => Some(0x0021), - KeyCode::KeyG => Some(0x0022), - KeyCode::KeyH => Some(0x0023), - KeyCode::KeyI => Some(0x0017), - KeyCode::KeyJ => Some(0x0024), - KeyCode::KeyK => Some(0x0025), - KeyCode::KeyL => Some(0x0026), - KeyCode::KeyM => Some(0x0032), - KeyCode::KeyN => Some(0x0031), - KeyCode::KeyO => Some(0x0018), - KeyCode::KeyP => Some(0x0019), - KeyCode::KeyQ => Some(0x0010), - KeyCode::KeyR => Some(0x0013), - KeyCode::KeyS => Some(0x001F), - KeyCode::KeyT => Some(0x0014), - KeyCode::KeyU => Some(0x0016), - KeyCode::KeyV => Some(0x002F), - KeyCode::KeyW => Some(0x0011), - KeyCode::KeyX => Some(0x002D), - KeyCode::KeyY => Some(0x0015), - KeyCode::KeyZ => Some(0x002C), - KeyCode::Minus => Some(0x000C), - KeyCode::Period => Some(0x0034), - KeyCode::Quote => Some(0x0028), - KeyCode::Semicolon => Some(0x0027), - KeyCode::Slash => Some(0x0035), - KeyCode::AltLeft => Some(0x0038), - KeyCode::AltRight => Some(0xE038), - KeyCode::CapsLock => Some(0x003A), - KeyCode::ContextMenu => Some(0xE05D), - KeyCode::ControlLeft => Some(0x001D), - KeyCode::ControlRight => Some(0xE01D), - KeyCode::Enter => Some(0x001C), - KeyCode::SuperLeft => Some(0xE05B), - KeyCode::SuperRight => Some(0xE05C), - KeyCode::ShiftLeft => Some(0x002A), - KeyCode::ShiftRight => Some(0x0036), - KeyCode::Space => Some(0x0039), - KeyCode::Tab => Some(0x000F), - KeyCode::Convert => Some(0x0079), - KeyCode::Lang1 => { - if is_korean { - Some(0xE0F2) - } else { - Some(0x0072) - } - } - KeyCode::Lang2 => { - if is_korean { - Some(0xE0F1) - } else { - Some(0x0071) - } - } - KeyCode::KanaMode => Some(0x0070), - KeyCode::NonConvert => Some(0x007B), - KeyCode::Delete => Some(0xE053), - KeyCode::End => Some(0xE04F), - KeyCode::Home => Some(0xE047), - KeyCode::Insert => Some(0xE052), - KeyCode::PageDown => Some(0xE051), - KeyCode::PageUp => Some(0xE049), - KeyCode::ArrowDown => Some(0xE050), - KeyCode::ArrowLeft => Some(0xE04B), - KeyCode::ArrowRight => Some(0xE04D), - KeyCode::ArrowUp => Some(0xE048), - KeyCode::NumLock => Some(0xE045), - KeyCode::Numpad0 => Some(0x0052), - KeyCode::Numpad1 => Some(0x004F), - KeyCode::Numpad2 => Some(0x0050), - KeyCode::Numpad3 => Some(0x0051), - KeyCode::Numpad4 => Some(0x004B), - KeyCode::Numpad5 => Some(0x004C), - KeyCode::Numpad6 => Some(0x004D), - KeyCode::Numpad7 => Some(0x0047), - KeyCode::Numpad8 => Some(0x0048), - KeyCode::Numpad9 => Some(0x0049), - KeyCode::NumpadAdd => Some(0x004E), - KeyCode::NumpadComma => Some(0x007E), - KeyCode::NumpadDecimal => Some(0x0053), - KeyCode::NumpadDivide => Some(0xE035), - KeyCode::NumpadEnter => Some(0xE01C), - KeyCode::NumpadEqual => Some(0x0059), - KeyCode::NumpadMultiply => Some(0x0037), - KeyCode::NumpadSubtract => Some(0x004A), - KeyCode::Escape => Some(0x0001), - KeyCode::F1 => Some(0x003B), - KeyCode::F2 => Some(0x003C), - KeyCode::F3 => Some(0x003D), - KeyCode::F4 => Some(0x003E), - KeyCode::F5 => Some(0x003F), - KeyCode::F6 => Some(0x0040), - KeyCode::F7 => Some(0x0041), - KeyCode::F8 => Some(0x0042), - KeyCode::F9 => Some(0x0043), - KeyCode::F10 => Some(0x0044), - KeyCode::F11 => Some(0x0057), - KeyCode::F12 => Some(0x0058), - KeyCode::F13 => Some(0x0064), - KeyCode::F14 => Some(0x0065), - KeyCode::F15 => Some(0x0066), - KeyCode::F16 => Some(0x0067), - KeyCode::F17 => Some(0x0068), - KeyCode::F18 => Some(0x0069), - KeyCode::F19 => Some(0x006A), - KeyCode::F20 => Some(0x006B), - KeyCode::F21 => Some(0x006C), - KeyCode::F22 => Some(0x006D), - KeyCode::F23 => Some(0x006E), - KeyCode::F24 => Some(0x0076), - KeyCode::PrintScreen => Some(0xE037), - //KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen - KeyCode::ScrollLock => Some(0x0046), - KeyCode::Pause => Some(0x0045), - //KeyCode::Pause => Some(0xE046), // Ctrl + Pause - KeyCode::BrowserBack => Some(0xE06A), - KeyCode::BrowserFavorites => Some(0xE066), - KeyCode::BrowserForward => Some(0xE069), - KeyCode::BrowserHome => Some(0xE032), - KeyCode::BrowserRefresh => Some(0xE067), - KeyCode::BrowserSearch => Some(0xE065), - KeyCode::BrowserStop => Some(0xE068), - KeyCode::LaunchApp1 => Some(0xE06B), - KeyCode::LaunchApp2 => Some(0xE021), - KeyCode::LaunchMail => Some(0xE06C), - KeyCode::MediaPlayPause => Some(0xE022), - KeyCode::MediaSelect => Some(0xE06D), - KeyCode::MediaStop => Some(0xE024), - KeyCode::MediaTrackNext => Some(0xE019), - KeyCode::MediaTrackPrevious => Some(0xE010), - KeyCode::Power => Some(0xE05E), - KeyCode::AudioVolumeDown => Some(0xE02E), - KeyCode::AudioVolumeMute => Some(0xE020), - KeyCode::AudioVolumeUp => Some(0xE030), - KeyCode::Unidentified(NativeKeyCode::Windows(scancode)) => Some(scancode as u32), - _ => None, - } -} - -pub fn keycode_from_scancode(scancode: u32) -> KeyCode { - // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html - // and: https://www.w3.org/TR/uievents-code/ - // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source - - match scancode { - 0x0029 => KeyCode::Backquote, - 0x002B => KeyCode::Backslash, - 0x000E => KeyCode::Backspace, - 0x001A => KeyCode::BracketLeft, - 0x001B => KeyCode::BracketRight, - 0x0033 => KeyCode::Comma, - 0x000B => KeyCode::Digit0, - 0x0002 => KeyCode::Digit1, - 0x0003 => KeyCode::Digit2, - 0x0004 => KeyCode::Digit3, - 0x0005 => KeyCode::Digit4, - 0x0006 => KeyCode::Digit5, - 0x0007 => KeyCode::Digit6, - 0x0008 => KeyCode::Digit7, - 0x0009 => KeyCode::Digit8, - 0x000A => KeyCode::Digit9, - 0x000D => KeyCode::Equal, - 0x0056 => KeyCode::IntlBackslash, - 0x0073 => KeyCode::IntlRo, - 0x007D => KeyCode::IntlYen, - 0x001E => KeyCode::KeyA, - 0x0030 => KeyCode::KeyB, - 0x002E => KeyCode::KeyC, - 0x0020 => KeyCode::KeyD, - 0x0012 => KeyCode::KeyE, - 0x0021 => KeyCode::KeyF, - 0x0022 => KeyCode::KeyG, - 0x0023 => KeyCode::KeyH, - 0x0017 => KeyCode::KeyI, - 0x0024 => KeyCode::KeyJ, - 0x0025 => KeyCode::KeyK, - 0x0026 => KeyCode::KeyL, - 0x0032 => KeyCode::KeyM, - 0x0031 => KeyCode::KeyN, - 0x0018 => KeyCode::KeyO, - 0x0019 => KeyCode::KeyP, - 0x0010 => KeyCode::KeyQ, - 0x0013 => KeyCode::KeyR, - 0x001F => KeyCode::KeyS, - 0x0014 => KeyCode::KeyT, - 0x0016 => KeyCode::KeyU, - 0x002F => KeyCode::KeyV, - 0x0011 => KeyCode::KeyW, - 0x002D => KeyCode::KeyX, - 0x0015 => KeyCode::KeyY, - 0x002C => KeyCode::KeyZ, - 0x000C => KeyCode::Minus, - 0x0034 => KeyCode::Period, - 0x0028 => KeyCode::Quote, - 0x0027 => KeyCode::Semicolon, - 0x0035 => KeyCode::Slash, - 0x0038 => KeyCode::AltLeft, - 0xE038 => KeyCode::AltRight, - 0x003A => KeyCode::CapsLock, - 0xE05D => KeyCode::ContextMenu, - 0x001D => KeyCode::ControlLeft, - 0xE01D => KeyCode::ControlRight, - 0x001C => KeyCode::Enter, - 0xE05B => KeyCode::SuperLeft, - 0xE05C => KeyCode::SuperRight, - 0x002A => KeyCode::ShiftLeft, - 0x0036 => KeyCode::ShiftRight, - 0x0039 => KeyCode::Space, - 0x000F => KeyCode::Tab, - 0x0079 => KeyCode::Convert, - 0x0072 => KeyCode::Lang1, // for non-Korean layout - 0xE0F2 => KeyCode::Lang1, // for Korean layout - 0x0071 => KeyCode::Lang2, // for non-Korean layout - 0xE0F1 => KeyCode::Lang2, // for Korean layout - 0x0070 => KeyCode::KanaMode, - 0x007B => KeyCode::NonConvert, - 0xE053 => KeyCode::Delete, - 0xE04F => KeyCode::End, - 0xE047 => KeyCode::Home, - 0xE052 => KeyCode::Insert, - 0xE051 => KeyCode::PageDown, - 0xE049 => KeyCode::PageUp, - 0xE050 => KeyCode::ArrowDown, - 0xE04B => KeyCode::ArrowLeft, - 0xE04D => KeyCode::ArrowRight, - 0xE048 => KeyCode::ArrowUp, - 0xE045 => KeyCode::NumLock, - 0x0052 => KeyCode::Numpad0, - 0x004F => KeyCode::Numpad1, - 0x0050 => KeyCode::Numpad2, - 0x0051 => KeyCode::Numpad3, - 0x004B => KeyCode::Numpad4, - 0x004C => KeyCode::Numpad5, - 0x004D => KeyCode::Numpad6, - 0x0047 => KeyCode::Numpad7, - 0x0048 => KeyCode::Numpad8, - 0x0049 => KeyCode::Numpad9, - 0x004E => KeyCode::NumpadAdd, - 0x007E => KeyCode::NumpadComma, - 0x0053 => KeyCode::NumpadDecimal, - 0xE035 => KeyCode::NumpadDivide, - 0xE01C => KeyCode::NumpadEnter, - 0x0059 => KeyCode::NumpadEqual, - 0x0037 => KeyCode::NumpadMultiply, - 0x004A => KeyCode::NumpadSubtract, - 0x0001 => KeyCode::Escape, - 0x003B => KeyCode::F1, - 0x003C => KeyCode::F2, - 0x003D => KeyCode::F3, - 0x003E => KeyCode::F4, - 0x003F => KeyCode::F5, - 0x0040 => KeyCode::F6, - 0x0041 => KeyCode::F7, - 0x0042 => KeyCode::F8, - 0x0043 => KeyCode::F9, - 0x0044 => KeyCode::F10, - 0x0057 => KeyCode::F11, - 0x0058 => KeyCode::F12, - 0x0064 => KeyCode::F13, - 0x0065 => KeyCode::F14, - 0x0066 => KeyCode::F15, - 0x0067 => KeyCode::F16, - 0x0068 => KeyCode::F17, - 0x0069 => KeyCode::F18, - 0x006A => KeyCode::F19, - 0x006B => KeyCode::F20, - 0x006C => KeyCode::F21, - 0x006D => KeyCode::F22, - 0x006E => KeyCode::F23, - 0x0076 => KeyCode::F24, - 0xE037 => KeyCode::PrintScreen, - 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen - 0x0046 => KeyCode::ScrollLock, - 0x0045 => KeyCode::Pause, - 0xE046 => KeyCode::Pause, // Ctrl + Pause - 0xE06A => KeyCode::BrowserBack, - 0xE066 => KeyCode::BrowserFavorites, - 0xE069 => KeyCode::BrowserForward, - 0xE032 => KeyCode::BrowserHome, - 0xE067 => KeyCode::BrowserRefresh, - 0xE065 => KeyCode::BrowserSearch, - 0xE068 => KeyCode::BrowserStop, - 0xE06B => KeyCode::LaunchApp1, - 0xE021 => KeyCode::LaunchApp2, - 0xE06C => KeyCode::LaunchMail, - 0xE022 => KeyCode::MediaPlayPause, - 0xE06D => KeyCode::MediaSelect, - 0xE024 => KeyCode::MediaStop, - 0xE019 => KeyCode::MediaTrackNext, - 0xE010 => KeyCode::MediaTrackPrevious, - 0xE05E => KeyCode::Power, - 0xE02E => KeyCode::AudioVolumeDown, - 0xE020 => KeyCode::AudioVolumeMute, - 0xE030 => KeyCode::AudioVolumeUp, - _ => KeyCode::Unidentified(NativeKeyCode::Windows(scancode as u16)), - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs deleted file mode 100644 index 0e0f1d218..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::mem::MaybeUninit; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::WindowsAndMessaging::{self as win32wm, *}, -}; - -use crate::platform_impl::platform::event_loop::ProcResult; - -pub fn is_msg_ime_related(msg_kind: u32) -> bool { - matches!( - msg_kind, - win32wm::WM_IME_COMPOSITION - | win32wm::WM_IME_COMPOSITIONFULL - | win32wm::WM_IME_STARTCOMPOSITION - | win32wm::WM_IME_ENDCOMPOSITION - | win32wm::WM_IME_CHAR - | win32wm::WM_CHAR - | win32wm::WM_SYSCHAR - ) -} - -pub struct MinimalIme { - // True if we're currently receiving messages belonging to a finished IME session. - getting_ime_text: bool, - - utf16parts: Vec, -} -impl Default for MinimalIme { - fn default() -> Self { - MinimalIme { - getting_ime_text: false, - utf16parts: Vec::with_capacity(16), - } - } -} -impl MinimalIme { - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - _lparam: LPARAM, - result: &mut ProcResult, - ) -> Option { - match msg_kind { - win32wm::WM_IME_ENDCOMPOSITION => { - self.getting_ime_text = true; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - *result = ProcResult::Value(LRESULT(0)); - if self.getting_ime_text { - self.utf16parts.push(wparam.0 as u16); - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - if !more_char_coming { - let result = String::from_utf16(&self.utf16parts).ok(); - self.utf16parts.clear(); - self.getting_ime_text = false; - return result; - } - } else { - return String::from_utf16(&[wparam.0 as u16]).ok(); - } - } - _ => (), - } - - None - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs deleted file mode 100644 index 8cd4d97a1..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use windows::Win32::{ - Foundation::{HANDLE, HWND}, - UI::WindowsAndMessaging::HMENU, -}; - -pub(crate) use self::{ - event_loop::{ - EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, - }, - icon::WinIcon, - keycode::{keycode_from_scancode, keycode_to_scancode}, - monitor::{MonitorHandle, VideoMode}, - window::Window, -}; - -pub use self::icon::WinIcon as PlatformIcon; - -use crate::{event::DeviceId as RootDeviceId, icon::Icon, keyboard::Key}; -mod keycode; - -#[non_exhaustive] -#[derive(Clone)] -pub enum Parent { - None, - ChildOf(HWND), - OwnedBy(HWND), -} - -#[derive(Clone)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub parent: Parent, - pub menu: Option, - pub taskbar_icon: Option, - pub skip_taskbar: bool, - pub window_classname: String, - pub no_redirection_bitmap: bool, - pub drag_and_drop: bool, - pub decoration_shadow: bool, - pub rtl: bool, -} - -impl Default for PlatformSpecificWindowBuilderAttributes { - fn default() -> Self { - Self { - parent: Parent::None, - menu: None, - taskbar_icon: None, - no_redirection_bitmap: false, - drag_and_drop: true, - skip_taskbar: false, - window_classname: "Window Class".to_string(), - decoration_shadow: true, - rtl: false, - } - } -} - -unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} -unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(isize); - -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId(0) - } -} - -impl DeviceId { - pub fn persistent_identifier(&self) -> Option { - if self.0 != 0 { - raw_input::get_raw_input_device_name(HANDLE(self.0 as _)) - } else { - None - } - } -} - -#[non_exhaustive] -#[derive(Debug)] -pub enum OsError { - #[allow(unused)] - CreationError(&'static str), - IoError(std::io::Error), -} -impl std::error::Error for OsError {} - -impl std::fmt::Display for OsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - OsError::CreationError(e) => f.pad(e), - OsError::IoError(e) => f.pad(&e.to_string()), - } - } -} - -impl From for OsError { - fn from(value: windows::core::Error) -> Self { - OsError::IoError(value.into()) - } -} - -impl From for crate::error::OsError { - fn from(value: windows::core::Error) -> Self { - os_error!(OsError::IoError(value.into())) - } -} - -impl From for crate::error::ExternalError { - fn from(value: windows::core::Error) -> Self { - crate::error::ExternalError::Os(os_error!(OsError::IoError(value.into()))) - } -} - -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); - -fn wrap_device_id(id: isize) -> RootDeviceId { - RootDeviceId(DeviceId(id)) -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct KeyEventExtra { - pub text_with_all_modifiers: Option<&'static str>, - pub key_without_modifiers: Key<'static>, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(isize); -unsafe impl Send for WindowId {} -unsafe impl Sync for WindowId {} - -impl WindowId { - pub unsafe fn dummy() -> Self { - WindowId(0) - } -} - -#[macro_use] -mod util; -mod dark_mode; -mod dpi; -mod drop_handler; -mod event_loop; -mod icon; -mod keyboard; -mod keyboard_layout; -mod minimal_ime; -mod monitor; -mod raw_input; -mod window; -mod window_state; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs deleted file mode 100644 index dc62064cf..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use windows::{ - core::{BOOL, PCWSTR}, - Win32::{ - Foundation::{HWND, LPARAM, POINT, RECT}, - Graphics::Gdi::*, - UI::WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI, - }, -}; - -use std::{ - collections::{BTreeSet, VecDeque}, - io, mem, -}; - -use super::util; -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::{ - dpi::{dpi_to_scale_factor, get_monitor_dpi}, - window::Window, - }, -}; - -#[derive(Clone)] -pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, - pub(crate) monitor: MonitorHandle, - pub(crate) native_video_mode: DEVMODEW, -} - -impl PartialEq for VideoMode { - fn eq(&self, other: &Self) -> bool { - self.size == other.size - && self.bit_depth == other.bit_depth - && self.refresh_rate == other.refresh_rate - && self.monitor == other.monitor - } -} - -impl Eq for VideoMode {} - -impl std::hash::Hash for VideoMode { - fn hash(&self, state: &mut H) { - self.size.hash(state); - self.bit_depth.hash(state); - self.refresh_rate.hash(state); - self.monitor.hash(state); - } -} - -impl std::fmt::Debug for VideoMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("VideoMode") - .field("size", &self.size) - .field("bit_depth", &self.bit_depth) - .field("refresh_rate", &self.refresh_rate) - .field("monitor", &self.monitor) - .finish() - } -} - -impl VideoMode { - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - pub fn monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: self.monitor.clone(), - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub struct MonitorHandle(isize); - -unsafe extern "system" fn monitor_enum_proc( - hmonitor: HMONITOR, - _hdc: HDC, - _place: *mut RECT, - data: LPARAM, -) -> BOOL { - let monitors = data.0 as *mut VecDeque; - (*monitors).push_back(MonitorHandle::new(hmonitor)); - true.into() // continue enumeration -} - -pub fn available_monitors() -> VecDeque { - let mut monitors: VecDeque = VecDeque::new(); - unsafe { - let _ = EnumDisplayMonitors( - None, - None, - Some(monitor_enum_proc), - LPARAM(&mut monitors as *mut _ as _), - ); - } - monitors -} - -pub fn primary_monitor() -> MonitorHandle { - const ORIGIN: POINT = POINT { x: 0, y: 0 }; - let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) }; - MonitorHandle::new(hmonitor) -} - -pub fn current_monitor(hwnd: HWND) -> MonitorHandle { - let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; - MonitorHandle::new(hmonitor) -} - -pub fn from_point(x: f64, y: f64) -> Option { - let hmonitor = unsafe { - MonitorFromPoint( - POINT { - x: x as i32, - y: y as i32, - }, - MONITOR_DEFAULTTONULL, - ) - }; - if !hmonitor.is_invalid() { - Some(MonitorHandle::new(hmonitor)) - } else { - None - } -} - -impl Window { - pub fn available_monitors(&self) -> VecDeque { - available_monitors() - } - - pub fn primary_monitor(&self) -> Option { - let monitor = primary_monitor(); - Some(RootMonitorHandle { inner: monitor }) - } - - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - from_point(x, y).map(|inner| RootMonitorHandle { inner }) - } -} - -pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { - let mut monitor_info = MONITORINFOEXW::default(); - monitor_info.monitorInfo.cbSize = mem::size_of::() as u32; - let status = unsafe { - GetMonitorInfoW( - hmonitor, - &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO, - ) - }; - if !status.as_bool() { - Err(io::Error::last_os_error()) - } else { - Ok(monitor_info) - } -} - -impl MonitorHandle { - pub(crate) fn new(hmonitor: HMONITOR) -> Self { - MonitorHandle(hmonitor.0 as _) - } - - #[inline] - pub fn name(&self) -> Option { - let monitor_info = get_monitor_info(self.hmonitor()).ok()?; - Some(util::wchar_ptr_to_string(PCWSTR::from_raw( - monitor_info.szDevice.as_ptr(), - ))) - } - - #[inline] - pub fn native_identifier(&self) -> String { - self.name().unwrap() - } - - #[inline] - pub fn hmonitor(&self) -> HMONITOR { - HMONITOR(self.0 as _) - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { - return PhysicalSize { - width: 0, - height: 0, - }; - }; - PhysicalSize { - width: (monitor_info.monitorInfo.rcMonitor.right - monitor_info.monitorInfo.rcMonitor.left) - as u32, - height: (monitor_info.monitorInfo.rcMonitor.bottom - monitor_info.monitorInfo.rcMonitor.top) - as u32, - } - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { - return PhysicalPosition { x: 0, y: 0 }; - }; - PhysicalPosition { - x: monitor_info.monitorInfo.rcMonitor.left, - y: monitor_info.monitorInfo.rcMonitor.top, - } - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - dpi_to_scale_factor(self.dpi()) - } - - pub fn dpi(&self) -> u32 { - get_monitor_dpi(self.hmonitor()).unwrap_or(USER_DEFAULT_SCREEN_DPI) - } - - #[inline] - pub fn video_modes(&self) -> impl Iterator { - // EnumDisplaySettingsExW can return duplicate values (or some of the - // fields are probably changing, but we aren't looking at those fields - // anyway), so we're using a BTreeSet deduplicate - let mut modes = BTreeSet::new(); - - let Ok(monitor_info) = get_monitor_info(self.hmonitor()) else { - return modes.into_iter(); - }; - - let device_name = PCWSTR::from_raw(monitor_info.szDevice.as_ptr()); - let mut i = 0; - loop { - unsafe { - let mut mode: DEVMODEW = mem::zeroed(); - mode.dmSize = mem::size_of_val(&mode) as u16; - if !EnumDisplaySettingsExW( - device_name, - ENUM_DISPLAY_SETTINGS_MODE(i), - &mut mode, - ENUM_DISPLAY_SETTINGS_FLAGS(0), - ) - .as_bool() - { - break; - } - i += 1; - - let required_fields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; - assert!(mode.dmFields & required_fields == required_fields); - - modes.insert(RootVideoMode { - video_mode: VideoMode { - size: (mode.dmPelsWidth, mode.dmPelsHeight), - bit_depth: mode.dmBitsPerPel as u16, - refresh_rate: mode.dmDisplayFrequency as u16, - monitor: self.clone(), - native_video_mode: mode, - }, - }); - } - } - - modes.into_iter() - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs deleted file mode 100644 index 2ca23b5e4..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::mem::{self, size_of}; - -use windows::Win32::{ - Devices::HumanInterfaceDevice::*, - Foundation::{HANDLE, HWND}, - UI::{ - Input::{self as win32i, *}, - WindowsAndMessaging::*, - }, -}; - -use crate::{event::ElementState, event_loop::DeviceEventFilter, platform_impl::platform::util}; - -#[allow(dead_code)] -pub fn get_raw_input_device_list() -> Option> { - let list_size = size_of::() as u32; - - let mut num_devices = 0; - let status = unsafe { GetRawInputDeviceList(None, &mut num_devices, list_size) }; - - if status == u32::MAX { - return None; - } - - let mut buffer = Vec::with_capacity(num_devices as _); - - let num_stored = - unsafe { GetRawInputDeviceList(Some(buffer.as_ptr() as _), &mut num_devices, list_size) }; - - if num_stored == u32::MAX { - return None; - } - - debug_assert_eq!(num_devices, num_stored); - - unsafe { buffer.set_len(num_devices as _) }; - - Some(buffer) -} - -#[non_exhaustive] -#[allow(dead_code)] -pub enum RawDeviceInfo { - Mouse(RID_DEVICE_INFO_MOUSE), - Keyboard(RID_DEVICE_INFO_KEYBOARD), - Hid(RID_DEVICE_INFO_HID), -} - -impl From for RawDeviceInfo { - fn from(info: RID_DEVICE_INFO) -> Self { - unsafe { - match info.dwType { - win32i::RIM_TYPEMOUSE => RawDeviceInfo::Mouse(info.Anonymous.mouse), - win32i::RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard), - win32i::RIM_TYPEHID => RawDeviceInfo::Hid(info.Anonymous.hid), - _ => unreachable!(), - } - } - } -} - -#[allow(dead_code)] -pub fn get_raw_input_device_info(handle: HANDLE) -> Option { - let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; - let info_size = size_of::() as u32; - - info.cbSize = info_size; - - let mut minimum_size = 0; - let status = unsafe { - GetRawInputDeviceInfoW( - Some(handle), - RIDI_DEVICEINFO, - Some(&mut info as *mut _ as _), - &mut minimum_size, - ) - }; - - if status == u32::MAX || status == 0 { - return None; - } - - debug_assert_eq!(info_size, status); - - Some(info.into()) -} - -pub fn get_raw_input_device_name(handle: HANDLE) -> Option { - let mut minimum_size = 0; - let status = - unsafe { GetRawInputDeviceInfoW(Some(handle), RIDI_DEVICENAME, None, &mut minimum_size) }; - - if status != 0 { - return None; - } - - let mut name: Vec = Vec::with_capacity(minimum_size as _); - - let status = unsafe { - GetRawInputDeviceInfoW( - Some(handle), - RIDI_DEVICENAME, - Some(name.as_ptr() as _), - &mut minimum_size, - ) - }; - - if status == u32::MAX || status == 0 { - return None; - } - - debug_assert_eq!(minimum_size, status); - - unsafe { name.set_len(minimum_size as _) }; - - Some(util::wchar_to_string(&name)) -} - -pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { - let device_size = size_of::() as u32; - unsafe { RegisterRawInputDevices(devices, device_size) }.is_ok() -} - -pub fn register_all_mice_and_keyboards_for_raw_input( - mut window_handle: HWND, - filter: DeviceEventFilter, -) -> bool { - // RIDEV_DEVNOTIFY: receive hotplug events - // RIDEV_INPUTSINK: receive events even if we're not in the foreground - // RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget) - let flags = match filter { - DeviceEventFilter::Always => { - window_handle = HWND(std::ptr::null_mut()); - RIDEV_REMOVE - } - DeviceEventFilter::Unfocused => RIDEV_DEVNOTIFY, - DeviceEventFilter::Never => RIDEV_DEVNOTIFY | RIDEV_INPUTSINK, - }; - - let devices: [RAWINPUTDEVICE; 2] = [ - RAWINPUTDEVICE { - usUsagePage: HID_USAGE_PAGE_GENERIC, - usUsage: HID_USAGE_GENERIC_MOUSE, - dwFlags: flags, - hwndTarget: window_handle, - }, - RAWINPUTDEVICE { - usUsagePage: HID_USAGE_PAGE_GENERIC, - usUsage: HID_USAGE_GENERIC_KEYBOARD, - dwFlags: flags, - hwndTarget: window_handle, - }, - ]; - - register_raw_input_devices(&devices) -} - -pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { - let mut data: RAWINPUT = unsafe { mem::zeroed() }; - let mut data_size = size_of::() as u32; - let header_size = size_of::() as u32; - - let status = unsafe { - GetRawInputData( - handle, - RID_INPUT, - Some(&mut data as *mut _ as _), - &mut data_size, - header_size, - ) - }; - - if status == u32::MAX || status == 0 { - return None; - } - - Some(data) -} - -fn button_flags_to_element_state( - button_flags: u16, - down_flag: u32, - up_flag: u32, -) -> Option { - // We assume the same button won't be simultaneously pressed and released. - if util::has_flag(button_flags, down_flag as u16) { - Some(ElementState::Pressed) - } else if util::has_flag(button_flags, up_flag as u16) { - Some(ElementState::Released) - } else { - None - } -} - -pub fn get_raw_mouse_button_state(button_flags: u16) -> [Option; 3] { - [ - button_flags_to_element_state( - button_flags, - RI_MOUSE_LEFT_BUTTON_DOWN, - RI_MOUSE_LEFT_BUTTON_UP, - ), - button_flags_to_element_state( - button_flags, - RI_MOUSE_MIDDLE_BUTTON_DOWN, - RI_MOUSE_MIDDLE_BUTTON_UP, - ), - button_flags_to_element_state( - button_flags, - RI_MOUSE_RIGHT_BUTTON_DOWN, - RI_MOUSE_RIGHT_BUTTON_UP, - ), - ] -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs deleted file mode 100644 index b4bc6e066..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs +++ /dev/null @@ -1,501 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - io, - iter::once, - mem, - ops::BitAnd, - os::windows::prelude::OsStrExt, - slice, - sync::atomic::{AtomicBool, Ordering}, -}; - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - window::CursorIcon, -}; - -use once_cell::sync::Lazy; -use windows::{ - core::{BOOL, HRESULT, PCSTR, PCWSTR}, - Win32::{ - Foundation::{COLORREF, FARPROC, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, - Globalization::lstrlenW, - Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, - System::LibraryLoader::*, - UI::{ - HiDpi::*, - Input::KeyboardAndMouse::*, - WindowsAndMessaging::{self as win32wm, *}, - }, - }, -}; - -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} - -pub fn wchar_to_string(wchar: &[u16]) -> String { - String::from_utf16_lossy(wchar) -} - -pub fn wchar_ptr_to_string(wchar: PCWSTR) -> String { - let len = unsafe { lstrlenW(wchar) } as usize; - let wchar_slice = unsafe { slice::from_raw_parts(wchar.0, len) }; - wchar_to_string(wchar_slice) -} - -pub fn encode_wide(string: impl AsRef) -> Vec { - string.as_ref().encode_wide().chain(once(0)).collect() -} - -pub unsafe fn get_window_rect(hwnd: HWND) -> Option { - let mut rect = std::mem::zeroed(); - GetWindowRect(hwnd, &mut rect).ok().map(|_| rect) -} - -pub fn get_client_rect(hwnd: HWND) -> Result { - let mut rect = RECT::default(); - let mut top_left = POINT::default(); - - unsafe { - ClientToScreen(hwnd, &mut top_left).ok()?; - GetClientRect(hwnd, &mut rect)?; - } - - rect.left += top_left.x; - rect.top += top_left.y; - rect.right += top_left.x; - rect.bottom += top_left.y; - - Ok(rect) -} - -pub fn adjust_size(hwnd: HWND, size: PhysicalSize, is_decorated: bool) -> PhysicalSize { - let (width, height): (u32, u32) = size.into(); - let rect = RECT { - left: 0, - right: width as i32, - top: 0, - bottom: height as i32, - }; - let rect = adjust_window_rect(hwnd, rect, is_decorated).unwrap_or(rect); - PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _) -} - -pub(crate) fn set_inner_size_physical(window: HWND, x: i32, y: i32, is_decorated: bool) { - unsafe { - let rect = adjust_window_rect( - window, - RECT { - top: 0, - left: 0, - bottom: y, - right: x, - }, - is_decorated, - ) - .expect("adjust_window_rect failed"); - - let outer_x = (rect.right - rect.left).abs(); - let outer_y = (rect.top - rect.bottom).abs(); - let _ = SetWindowPos( - window, - None, - 0, - 0, - outer_x, - outer_y, - SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, - ); - let _ = InvalidateRgn(window, None, false); - } -} - -pub fn adjust_window_rect(hwnd: HWND, rect: RECT, is_decorated: bool) -> Option { - unsafe { - let mut style = WINDOW_STYLE(GetWindowLongW(hwnd, GWL_STYLE) as u32); - // if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so - // `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and - // calculates a correct size for the client area. - if !is_decorated { - style &= !WS_CAPTION; - style &= !WS_SIZEBOX; - } - let style_ex = WINDOW_EX_STYLE(GetWindowLongW(hwnd, GWL_EXSTYLE) as u32); - adjust_window_rect_with_styles(hwnd, style, style_ex, rect) - } -} - -pub fn adjust_window_rect_with_styles( - hwnd: HWND, - style: WINDOW_STYLE, - style_ex: WINDOW_EX_STYLE, - mut rect: RECT, -) -> Option { - let b_menu = !unsafe { GetMenu(hwnd) }.is_invalid(); - - if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = - (*GET_DPI_FOR_WINDOW, *ADJUST_WINDOW_RECT_EX_FOR_DPI) - { - let dpi = unsafe { get_dpi_for_window(hwnd) }; - if unsafe { adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi) } - .as_bool() - { - Some(rect) - } else { - None - } - } else { - unsafe { AdjustWindowRectEx(&mut rect, style, b_menu, style_ex) } - .ok() - .map(|_| rect) - } -} - -pub fn set_cursor_hidden(hidden: bool) { - static HIDDEN: AtomicBool = AtomicBool::new(false); - let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; - if changed { - unsafe { ShowCursor(!hidden) }; - } -} - -pub fn get_cursor_clip() -> windows::core::Result { - unsafe { - let mut rect = RECT::default(); - GetClipCursor(&mut rect).map(|_| rect) - } -} - -/// Sets the cursor's clip rect. -/// -/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event. -pub fn set_cursor_clip(rect: Option) -> windows::core::Result<()> { - unsafe { - let rect_ptr = rect.as_ref().map(|r| r as *const RECT); - ClipCursor(rect_ptr) - } -} - -pub fn get_desktop_rect() -> RECT { - unsafe { - let left = GetSystemMetrics(SM_XVIRTUALSCREEN); - let top = GetSystemMetrics(SM_YVIRTUALSCREEN); - RECT { - left, - top, - right: left + GetSystemMetrics(SM_CXVIRTUALSCREEN), - bottom: top + GetSystemMetrics(SM_CYVIRTUALSCREEN), - } - } -} - -pub fn is_focused(window: HWND) -> bool { - window == unsafe { GetActiveWindow() } -} - -pub fn is_visible(window: HWND) -> bool { - unsafe { IsWindowVisible(window).as_bool() } -} - -pub fn is_maximized(window: HWND) -> windows::core::Result { - let mut placement = WINDOWPLACEMENT { - length: mem::size_of::() as u32, - ..WINDOWPLACEMENT::default() - }; - unsafe { GetWindowPlacement(window, &mut placement)? }; - Ok(placement.showCmd == SW_MAXIMIZE.0 as u32) -} - -pub fn cursor_position() -> windows::core::Result> { - let mut pt = POINT { x: 0, y: 0 }; - unsafe { GetCursorPos(&mut pt)? }; - Ok((pt.x, pt.y).into()) -} - -impl CursorIcon { - pub(crate) fn to_windows_cursor(self) -> PCWSTR { - match self { - CursorIcon::Arrow | CursorIcon::Default => IDC_ARROW, - CursorIcon::Hand => IDC_HAND, - CursorIcon::Crosshair => IDC_CROSS, - CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM, - CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO, - CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => { - IDC_SIZEALL - } - CursorIcon::EResize | CursorIcon::WResize | CursorIcon::EwResize | CursorIcon::ColResize => { - IDC_SIZEWE - } - CursorIcon::NResize | CursorIcon::SResize | CursorIcon::NsResize | CursorIcon::RowResize => { - IDC_SIZENS - } - CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW, - CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE, - CursorIcon::Wait => IDC_WAIT, - CursorIcon::Progress => IDC_APPSTARTING, - CursorIcon::Help => IDC_HELP, - _ => IDC_ARROW, // use arrow for the missing cases. - } - } -} - -// Helper function to dynamically load function pointer. -// `library` and `function` must be zero-terminated. -pub(super) fn get_function_impl(library: &str, function: &str) -> FARPROC { - let library = encode_wide(library); - assert_eq!(function.chars().last(), Some('\0')); - - // Library names we will use are ASCII so we can use the A version to avoid string conversion. - let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default(); - if module.is_invalid() { - return None; - } - - unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) } -} - -macro_rules! get_function { - ($lib:expr, $func:ident) => { - crate::platform_impl::platform::util::get_function_impl($lib, concat!(stringify!($func), '\0')) - .map(|f| unsafe { std::mem::transmute::<_, $func>(f) }) - }; -} - -pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL; -pub type SetProcessDpiAwareness = - unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT; -pub type SetProcessDpiAwarenessContext = - unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; -pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32; -pub type GetDpiForMonitor = unsafe extern "system" fn( - hmonitor: HMONITOR, - dpi_type: MONITOR_DPI_TYPE, - dpi_x: *mut u32, - dpi_y: *mut u32, -) -> HRESULT; -type GetSystemMetricsForDpi = - unsafe extern "system" fn(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32; -pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; -#[allow(non_snake_case)] -pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( - rect: *mut RECT, - dwStyle: WINDOW_STYLE, - bMenu: BOOL, - dwExStyle: WINDOW_EX_STYLE, - dpi: u32, -) -> BOOL; - -pub static GET_DPI_FOR_WINDOW: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); -pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); -pub static GET_DPI_FOR_MONITOR: Lazy> = - Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); -pub static GET_SYSTEM_METRICS_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); -pub static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = - Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); -pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); -pub static SET_PROCESS_DPI_AWARENESS: Lazy> = - Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); -pub static SET_PROCESS_DPI_AWARE: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); - -#[allow(non_snake_case)] -#[cfg(target_pointer_width = "32")] -pub fn SetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { - unsafe { win32wm::SetWindowLongW(window, index, value as _) as _ } -} - -#[allow(non_snake_case)] -#[cfg(target_pointer_width = "64")] -pub fn SetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { - unsafe { win32wm::SetWindowLongPtrW(window, index, value) } -} - -#[allow(non_snake_case)] -#[cfg(target_pointer_width = "32")] -pub fn GetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { - unsafe { win32wm::GetWindowLongW(window, index) as _ } -} - -#[allow(non_snake_case)] -#[cfg(target_pointer_width = "64")] -pub fn GetWindowLongPtrW(window: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { - unsafe { win32wm::GetWindowLongPtrW(window, index) } -} - -/// Implementation of the `LOWORD` macro. -#[allow(non_snake_case)] -#[inline] -pub fn LOWORD(dword: u32) -> u16 { - (dword & 0xFFFF) as u16 -} - -/// Implementation of the `HIWORD` macro. -#[allow(non_snake_case)] -#[inline] -pub fn HIWORD(dword: u32) -> u16 { - ((dword & 0xFFFF_0000) >> 16) as u16 -} - -/// Implementation of the `GET_X_LPARAM` macro. -#[allow(non_snake_case)] -#[inline] -pub fn GET_X_LPARAM(lparam: LPARAM) -> i16 { - ((lparam.0 as usize) & 0xFFFF) as u16 as i16 -} - -/// Implementation of the `GET_Y_LPARAM` macro. -#[allow(non_snake_case)] -#[inline] -pub fn GET_Y_LPARAM(lparam: LPARAM) -> i16 { - (((lparam.0 as usize) & 0xFFFF_0000) >> 16) as u16 as i16 -} - -/// Implementation of the `MAKELPARAM` macro. -/// Inverse of [GET_X_LPARAM] and [GET_Y_LPARAM] to put the (`x`, `y`) signed -/// coordinates/values back into an [LPARAM]. -#[allow(non_snake_case)] -#[inline] -pub fn MAKELPARAM(x: i16, y: i16) -> LPARAM { - LPARAM(((x as u16 as u32) | ((y as u16 as u32) << 16)) as usize as _) -} - -/// Implementation of the `GET_WHEEL_DELTA_WPARAM` macro. -#[allow(non_snake_case)] -#[inline] -pub fn GET_WHEEL_DELTA_WPARAM(wparam: WPARAM) -> i16 { - ((wparam.0 & 0xFFFF_0000) >> 16) as u16 as i16 -} - -/// Implementation of the `GET_XBUTTON_WPARAM` macro. -#[allow(non_snake_case)] -#[inline] -pub fn GET_XBUTTON_WPARAM(wparam: WPARAM) -> u16 { - ((wparam.0 & 0xFFFF_0000) >> 16) as u16 -} - -/// Implementation of the `PRIMARYLANGID` macro. -#[allow(non_snake_case)] -#[inline] -pub fn PRIMARYLANGID(hkl: HKL) -> u32 { - ((hkl.0 as usize) & 0x3FF) as u32 -} - -/// Implementation of the `RGB` macro. -#[allow(non_snake_case)] -#[inline] -pub fn RGB>(r: T, g: T, b: T) -> COLORREF { - COLORREF(r.into() | (g.into() << 8) | (b.into() << 16)) -} - -pub unsafe extern "system" fn call_default_window_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - DefWindowProcW(hwnd, msg, wparam, lparam) -} - -pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE { - // Gets the instance handle by taking the address of the - // pseudo-variable created by the microsoft linker: - // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 - - // This is preferred over GetModuleHandle(NULL) because it also works in DLLs: - // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance - - extern "C" { - static __ImageBase: windows::Win32::System::SystemServices::IMAGE_DOS_HEADER; - } - - windows::Win32::Foundation::HMODULE(unsafe { &__ImageBase as *const _ as _ }) -} - -pub static WIN_VERSION: Lazy = - Lazy::new(windows_version::OsVersion::current); - -pub fn get_frame_thickness(dpi: u32) -> i32 { - let resize_frame_thickness = unsafe { get_system_metrics_for_dpi(SM_CXSIZEFRAME, dpi) }; - let padding_thickness = unsafe { get_system_metrics_for_dpi(SM_CXPADDEDBORDER, dpi) }; - resize_frame_thickness + padding_thickness -} - -pub fn calculate_insets_for_dpi(dpi: u32) -> RECT { - // - On Windows 10 - // The top inset must be zero, since if there is any nonclient area, Windows will draw - // a full native titlebar outside the client area. (This doesn't occur in the maximized - // case.) - // - // - On Windows 11 - // The top inset is calculated using an empirical formula that I derived through various - // tests. Without this, the top 1-2 rows of pixels in our window would be obscured. - - let frame_thickness = get_frame_thickness(dpi); - - let top_inset = match WIN_VERSION.build { - v if v >= 22000 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32, - _ => 0, - }; - - RECT { - left: frame_thickness, - top: top_inset, - right: frame_thickness, - bottom: frame_thickness, - } -} - -/// Calcuclate window insets, used in WM_NCCALCSIZE -/// -/// Derived of GPUI implementation -/// see -pub fn calculate_window_insets(window: HWND) -> RECT { - let dpi = unsafe { super::dpi::hwnd_dpi(window) }; - calculate_insets_for_dpi(dpi) -} - -pub fn window_rect(hwnd: HWND) -> RECT { - unsafe { - let mut rect = RECT::default(); - if GetWindowRect(hwnd, &mut rect).is_err() { - panic!( - "Unexpected GetWindowRect failure: please report this error to \ - tauri-apps/tao" - ) - } - rect - } -} - -pub fn client_rect(hwnd: HWND) -> RECT { - unsafe { - let mut rect = RECT::default(); - if GetClientRect(hwnd, &mut rect).is_err() { - panic!( - "Unexpected GetClientRect failure: please report this error to \ - tauri-apps/tao" - ) - } - rect - } -} - -pub unsafe fn get_system_metrics_for_dpi(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32 { - #[allow(non_snake_case)] - if let Some(GetSystemMetricsForDpi) = *GET_SYSTEM_METRICS_FOR_DPI { - GetSystemMetricsForDpi(nindex, dpi) - } else { - GetSystemMetrics(nindex) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs deleted file mode 100644 index 170de3668..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs +++ /dev/null @@ -1,1554 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -#![cfg(target_os = "windows")] - -use mem::MaybeUninit; -use parking_lot::Mutex; -use std::{ - cell::{Cell, RefCell}, - ffi::OsStr, - io, mem, - os::windows::ffi::OsStrExt, - sync::Arc, -}; - -use crossbeam_channel as channel; -use windows::{ - core::PCWSTR, - Win32::{ - Foundation::{ - self as win32f, HINSTANCE, HMODULE, HWND, LPARAM, LRESULT, POINT, POINTS, RECT, WPARAM, - }, - Graphics::{ - Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, - Gdi::*, - }, - System::{Com::*, LibraryLoader::*, Ole::*}, - UI::{ - Input::{Ime::*, KeyboardAndMouse::*, Touch::*}, - Shell::{ITaskbarList4 as ITaskbarList, TaskbarList, *}, - WindowsAndMessaging::{self as win32wm, *}, - }, - }, -}; - -use crate::{ - dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::platform::{ - dark_mode::try_window_theme, - dpi::{dpi_to_scale_factor, hwnd_dpi}, - drop_handler::FileDropHandler, - event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, - icon::{self, IconType}, - monitor::{self}, - util, - window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, - OsError, Parent, PlatformSpecificWindowBuilderAttributes, WindowId, - }, - window::{ - CursorIcon, Fullscreen, ProgressBarState, ProgressState, ResizeDirection, Theme, - UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, - }, -}; - -use super::{ - event_loop::CHANGE_THEME_MSG_ID, - keyboard::{KeyEventBuilder, KEY_EVENT_BUILDERS}, - util::calculate_insets_for_dpi, -}; - -/// A simple non-owning wrapper around a window. -#[derive(Clone, Copy)] -pub struct WindowWrapper(pub HWND); - -// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually. -// For more info see: -// https://github.com/retep998/winapi-rs/issues/360 -// https://github.com/retep998/winapi-rs/issues/396 -unsafe impl Sync for WindowWrapper {} -unsafe impl Send for WindowWrapper {} - -/// The Win32 implementation of the main `Window` object. -pub struct Window { - /// Main handle for the window. - window: WindowWrapper, - - /// The current window state. - window_state: Arc>, - - // The events loop proxy. - thread_executor: event_loop::EventLoopThreadExecutor, -} - -impl Window { - pub fn new( - event_loop: &EventLoopWindowTarget, - w_attr: WindowAttributes, - pl_attr: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - // We dispatch an `init` function because of code style. - // First person to remove the need for cloning here gets a cookie! - // - // done. you owe me -- ossi - unsafe { - let drag_and_drop = pl_attr.drag_and_drop; - init(w_attr, pl_attr, event_loop).map(|win| { - let file_drop_handler = if drag_and_drop { - // It is ok if the initialize result is `S_FALSE` because it might happen that - // multiple windows are created on the same thread. - if let Err(error) = OleInitialize(None) { - match error.code() { - win32f::OLE_E_WRONGCOMPOBJ => { - panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") - } - win32f::RPC_E_CHANGED_MODE => panic!( - "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ - Make sure other crates are not using multithreaded COM library \ - on the same thread or disable drag and drop support." - ), - _ => (), - }; - } - - let file_drop_runner = event_loop.runner_shared.clone(); - let file_drop_handler: IDropTarget = FileDropHandler::new( - win.window.0, - Box::new(move |event| { - if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) - } - }), - ) - .into(); - - assert!(RegisterDragDrop(win.window.0, &file_drop_handler).is_ok()); - Some(file_drop_handler) - } else { - None - }; - - let subclass_input = event_loop::SubclassInput { - window_state: win.window_state.clone(), - event_loop_runner: event_loop.runner_shared.clone(), - _file_drop_handler: file_drop_handler, - subclass_removed: Cell::new(false), - recurse_depth: Cell::new(0), - event_loop_preferred_theme: event_loop.preferred_theme.clone(), - }; - - event_loop::subclass_window(win.window.0, subclass_input); - win - }) - } - } - - pub fn set_title(&self, text: &str) { - let text = util::encode_wide(text); - unsafe { - let _ = SetWindowTextW(self.window.0, PCWSTR::from_raw(text.as_ptr())); - } - } - - pub fn title(&self) -> String { - let len = unsafe { GetWindowTextLengthW(self.window.0) }; - let mut buf = vec![0; (len + 1) as usize]; - unsafe { GetWindowTextW(self.window.0, &mut buf) }; - String::from_utf16_lossy(&buf[..len as _]) - } - #[inline] - pub fn set_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::VISIBLE, visible) - }); - }); - } - - #[inline] - pub fn set_focus(&self) { - let window = self.window; - let window_flags = self.window_state.lock().window_flags(); - - let is_visible = window_flags.contains(WindowFlags::VISIBLE); - let is_minimized = window_flags.contains(WindowFlags::MINIMIZED); - let is_foreground = window.0 == unsafe { GetForegroundWindow() }; - - if is_visible && !is_minimized && !is_foreground { - unsafe { force_window_active(window.0) }; - } - } - - #[inline] - pub fn set_focusable(&self, focusable: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::FOCUSABLE, focusable) - }); - }); - } - - #[inline] - pub fn is_focused(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.has_active_focus() - } - - #[inline] - pub fn request_redraw(&self) { - unsafe { - let _ = RedrawWindow(Some(self.window.0), None, None, RDW_INTERNALPAINT); - } - } - - #[inline] - pub fn outer_position(&self) -> Result, NotSupportedError> { - unsafe { util::get_window_rect(self.window.0) } - .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) - .expect("Unexpected GetWindowRect failure") - } - - #[inline] - pub fn inner_position(&self) -> Result, NotSupportedError> { - let mut position = POINT::default(); - if !unsafe { ClientToScreen(self.window.0, &mut position) }.as_bool() { - panic!("Unexpected ClientToScreen failure") - } - Ok(PhysicalPosition::new(position.x, position.y)) - } - - #[inline] - pub fn set_outer_position(&self, position: Position) { - let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); - - let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); - }); - - unsafe { - let _ = SetWindowPos( - self.window.0, - None, - x, - y, - 0, - 0, - SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, - ); - let _ = InvalidateRgn(self.window.0, None, false); - } - } - - #[inline] - pub fn inner_size(&self) -> PhysicalSize { - let client_rect = util::client_rect(self.hwnd()); - PhysicalSize::new( - (client_rect.right - client_rect.left) as u32, - (client_rect.bottom - client_rect.top) as u32, - ) - } - - #[inline] - pub fn outer_size(&self) -> PhysicalSize { - let window_rect = util::window_rect(self.hwnd()); - PhysicalSize::new( - (window_rect.right - window_rect.left) as u32, - (window_rect.bottom - window_rect.top) as u32, - ) - } - - #[inline] - pub fn set_inner_size(&self, size: Size) { - let scale_factor = self.scale_factor(); - - let (mut desired_width, mut desired_height) = size.to_physical::(scale_factor).into(); - - let window_flags = self.window_state.lock().window_flags; - - // undecorated windows with shadows have hidden offsets - // we need to calculate them and account for them in new size - // - // implementation derived from GPUI - // see - if window_flags.undecorated_with_shadows() { - let hwnd = self.hwnd(); - - let client_rect = util::client_rect(hwnd); - let window_rect = util::window_rect(hwnd); - - let width_offset = - (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left); - let height_offset = - (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top); - - desired_width += width_offset; - desired_height += height_offset; - } - - let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); - }); - - util::set_inner_size_physical( - self.window.0, - desired_width, - desired_height, - window_flags.contains(WindowFlags::MARKER_DECORATIONS), - ); - } - - #[inline] - pub fn set_min_inner_size(&self, size: Option) { - let (width, height) = size.map(crate::extract_width_height).unzip(); - - { - let mut window_state = self.window_state.lock(); - window_state.size_constraints.min_width = width; - window_state.size_constraints.min_height = height; - } - - // Make windows re-check the window size bounds. - let size = self.inner_size(); - self.set_inner_size(size.into()); - } - - #[inline] - pub fn set_max_inner_size(&self, size: Option) { - let (width, height) = size.map(crate::extract_width_height).unzip(); - - { - let mut window_state = self.window_state.lock(); - window_state.size_constraints.max_width = width; - window_state.size_constraints.max_height = height; - } - - // Make windows re-check the window size bounds. - let size = self.inner_size(); - self.set_inner_size(size.into()); - } - - #[inline] - pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - self.window_state.lock().size_constraints = constraints; - // Make windows re-check the window size bounds. - let size = self.inner_size(); - self.set_inner_size(size.into()); - } - - #[inline] - pub fn set_resizable(&self, resizable: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::RESIZABLE, resizable) - }); - }); - } - - #[inline] - pub fn set_minimizable(&self, minimizable: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MINIMIZABLE, minimizable) - }); - }); - } - - #[inline] - pub fn set_maximizable(&self, maximizable: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MAXIMIZABLE, maximizable) - }); - }); - } - - #[inline] - pub fn set_closable(&self, closable: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::CLOSABLE, closable) - }); - }); - } - - /// Returns the `hwnd` of this window. - #[inline] - pub fn hwnd(&self) -> HWND { - self.window.0 - } - - #[inline] - pub fn hinstance(&self) -> HMODULE { - util::get_instance_handle() - } - - #[cfg(feature = "rwh_04")] - #[inline] - pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { - let mut window_handle = rwh_04::Win32Handle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; - let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); - window_handle.hinstance = hinstance as *mut _; - rwh_04::RawWindowHandle::Win32(window_handle) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { - let mut window_handle = rwh_05::Win32WindowHandle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; - let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); - window_handle.hinstance = hinstance as *mut _; - rwh_05::RawWindowHandle::Win32(window_handle) - } - - #[cfg(feature = "rwh_05")] - #[inline] - pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { - rwh_05::RawDisplayHandle::Windows(rwh_05::WindowsDisplayHandle::empty()) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_window_handle_rwh_06(&self) -> Result { - let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { - // SAFETY: Handle will never be zero. - let window = self.window.0 .0; - std::num::NonZeroIsize::new_unchecked(window as _) - }); - let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); - window_handle.hinstance = std::num::NonZeroIsize::new(hinstance); - Ok(rwh_06::RawWindowHandle::Win32(window_handle)) - } - - #[cfg(feature = "rwh_06")] - #[inline] - pub fn raw_display_handle_rwh_06(&self) -> Result { - Ok(rwh_06::RawDisplayHandle::Windows( - rwh_06::WindowsDisplayHandle::new(), - )) - } - - #[inline] - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.window_state.lock().mouse.cursor = cursor; - self.thread_executor.execute_in_thread(move || unsafe { - let cursor = LoadCursorW(None, cursor.to_windows_cursor()).ok(); - SetCursor(cursor); - }); - } - - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - let (tx, rx) = channel::unbounded(); - - self.thread_executor.execute_in_thread(move || { - let result = window_state - .lock() - .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::GRABBED, grab)) - .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e)))); - let _ = tx.send(result); - }); - rx.recv().unwrap() - } - - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - let (tx, rx) = channel::unbounded(); - - self.thread_executor.execute_in_thread(move || { - let result = window_state - .lock() - .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::HIDDEN, !visible)) - .map_err(|e| e.to_string()); - let _ = tx.send(result); - }); - rx.recv().unwrap().ok(); - } - - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - util::cursor_position().map_err(Into::into) - } - - #[inline] - pub fn scale_factor(&self) -> f64 { - self.window_state.lock().scale_factor - } - - #[inline] - pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let scale_factor = self.scale_factor(); - let (x, y) = position.to_physical::(scale_factor).into(); - - let mut point = POINT { x, y }; - unsafe { - if !ClientToScreen(self.window.0, &mut point).as_bool() { - return Err(ExternalError::Os(os_error!(OsError::IoError( - io::Error::last_os_error() - )))); - } - SetCursorPos(point.x, point.y) - .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e.into())))) - } - } - - fn handle_os_dragging(&self, wparam: WPARAM) -> Result<(), ExternalError> { - let points = { - let mut pos = unsafe { mem::zeroed() }; - unsafe { GetCursorPos(&mut pos)? }; - pos - }; - let points = POINTS { - x: points.x as i16, - y: points.y as i16, - }; - unsafe { ReleaseCapture()? }; - - self.window_state.lock().dragging = true; - - unsafe { - PostMessageW( - Some(self.hwnd()), - WM_NCLBUTTONDOWN, - wparam, - LPARAM(&points as *const _ as _), - )? - }; - - Ok(()) - } - - #[inline] - pub fn drag_window(&self) -> Result<(), ExternalError> { - self.handle_os_dragging(WPARAM(HTCAPTION as _)) - } - - #[inline] - pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { - self.handle_os_dragging(WPARAM(direction.to_win32() as _)) - } - - #[inline] - pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::IGNORE_CURSOR_EVENT, ignore) - }); - }); - - Ok(()) - } - - #[inline] - pub fn id(&self) -> WindowId { - WindowId(self.window.0 .0 as _) - } - - #[inline] - pub fn set_minimized(&self, minimized: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - let is_minimized = self.is_minimized(); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags_in_place(&mut window_state.lock(), |f| { - f.set(WindowFlags::MINIMIZED, is_minimized) - }); - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MINIMIZED, minimized) - }); - }); - } - - #[inline] - pub fn set_maximized(&self, maximized: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MAXIMIZED, maximized) - }); - }); - } - - #[inline] - pub fn is_maximized(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::MAXIMIZED) - } - - #[inline] - pub fn is_always_on_top(&self) -> bool { - let window_state = self.window_state.lock(); - window_state - .window_flags - .contains(WindowFlags::ALWAYS_ON_TOP) - } - - #[inline] - pub fn is_minimized(&self) -> bool { - unsafe { IsIconic(self.hwnd()) }.as_bool() - } - - #[inline] - pub fn is_resizable(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::RESIZABLE) - } - - #[inline] - pub fn is_minimizable(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::MINIMIZABLE) - } - - #[inline] - pub fn is_maximizable(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) - } - - #[inline] - pub fn is_closable(&self) -> bool { - let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::CLOSABLE) - } - - #[inline] - pub fn is_decorated(&self) -> bool { - let window_state = self.window_state.lock(); - window_state - .window_flags - .contains(WindowFlags::MARKER_DECORATIONS) - } - - #[inline] - pub fn is_visible(&self) -> bool { - util::is_visible(self.window.0) - } - - #[inline] - pub fn fullscreen(&self) -> Option { - let window_state = self.window_state.lock(); - window_state.fullscreen.clone() - } - - #[inline] - pub fn set_fullscreen(&self, fullscreen: Option) { - let window = self.window; - let window_state = Arc::clone(&self.window_state); - - let mut window_state_lock = window_state.lock(); - let old_fullscreen = window_state_lock.fullscreen.clone(); - - match (&old_fullscreen, &fullscreen) { - // Return if we already in the same fullscreen mode - _ if old_fullscreen == fullscreen => return, - // Return if saved Borderless(monitor) is the same as current monitor when requested fullscreen is Borderless(None) - (Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None))) - if monitor.inner == monitor::current_monitor(window.0) => - { - return - } - _ => {} - } - - window_state_lock.fullscreen = fullscreen.clone(); - drop(window_state_lock); - - let window_isize = window.0 .0 as isize; - self.thread_executor.execute_in_thread(move || { - let hwnd = HWND(window_isize as _); - // Change video mode if we're transitioning to or from exclusive - // fullscreen - match (&old_fullscreen, &fullscreen) { - (&None, &Some(Fullscreen::Exclusive(ref video_mode))) - | (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(ref video_mode))) - | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) => { - let monitor = video_mode.monitor(); - - let mut display_name = OsStr::new(&monitor.inner.native_identifier()) - .encode_wide() - .collect::>(); - // `encode_wide` does not add a null-terminator but - // `ChangeDisplaySettingsExW` requires a null-terminated - // string, so add it - display_name.push(0); - - let native_video_mode = video_mode.video_mode.native_video_mode; - - let res = unsafe { - ChangeDisplaySettingsExW( - PCWSTR::from_raw(display_name.as_ptr()), - Some(&native_video_mode), - None, - CDS_FULLSCREEN, - None, - ) - }; - - debug_assert!(res != DISP_CHANGE_BADFLAGS); - debug_assert!(res != DISP_CHANGE_BADMODE); - debug_assert!(res != DISP_CHANGE_BADPARAM); - debug_assert!(res != DISP_CHANGE_FAILED); - assert_eq!(res, DISP_CHANGE_SUCCESSFUL); - } - (&Some(Fullscreen::Exclusive(_)), &None) - | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { - let res = - unsafe { ChangeDisplaySettingsExW(PCWSTR::null(), None, None, CDS_FULLSCREEN, None) }; - - debug_assert!(res != DISP_CHANGE_BADFLAGS); - debug_assert!(res != DISP_CHANGE_BADMODE); - debug_assert!(res != DISP_CHANGE_BADPARAM); - debug_assert!(res != DISP_CHANGE_FAILED); - assert_eq!(res, DISP_CHANGE_SUCCESSFUL); - } - _ => (), - } - - unsafe { - // There are some scenarios where calling `ChangeDisplaySettingsExW` takes long - // enough to execute that the DWM thinks our program has frozen and takes over - // our program's window. When that happens, the `SetWindowPos` call below gets - // eaten and the window doesn't get set to the proper fullscreen position. - // - // Calling `PeekMessageW` here notifies Windows that our process is still running - // fine, taking control back from the DWM and ensuring that the `SetWindowPos` call - // below goes through. - let mut msg = MSG::default(); - let _ = PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE); - } - - // Update window style - WindowState::set_window_flags(window_state.lock(), HWND(window_isize as _), |f| { - f.set( - WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, - matches!(fullscreen, Some(Fullscreen::Exclusive(_))), - ); - f.set( - WindowFlags::MARKER_BORDERLESS_FULLSCREEN, - matches!(fullscreen, Some(Fullscreen::Borderless(_))), - ); - }); - - // Update window bounds - match &fullscreen { - Some(fullscreen) => { - // Save window bounds before entering fullscreen - let placement = unsafe { - let mut placement = WINDOWPLACEMENT::default(); - let _ = GetWindowPlacement(hwnd, &mut placement); - placement - }; - - window_state.lock().saved_window = Some(SavedWindow { placement }); - - let monitor = match &fullscreen { - Fullscreen::Exclusive(video_mode) => video_mode.monitor(), - Fullscreen::Borderless(Some(monitor)) => monitor.clone(), - Fullscreen::Borderless(None) => RootMonitorHandle { - inner: monitor::current_monitor(hwnd), - }, - }; - - let position: (i32, i32) = monitor.position().into(); - let size: (u32, u32) = monitor.size().into(); - - unsafe { - let _ = SetWindowPos( - hwnd, - None, - position.0, - position.1, - size.0 as i32, - size.1 as i32, - SWP_ASYNCWINDOWPOS | SWP_NOZORDER, - ); - let _ = InvalidateRgn(hwnd, None, false); - } - } - None => { - let mut window_state_lock = window_state.lock(); - if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { - drop(window_state_lock); - unsafe { - let _ = SetWindowPlacement(hwnd, &placement); - let _ = InvalidateRgn(hwnd, None, false); - } - } - } - } - - unsafe { - taskbar_mark_fullscreen(hwnd, fullscreen.is_some()); - } - }); - } - - #[inline] - pub fn set_decorations(&self, decorations: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::MARKER_DECORATIONS, decorations) - }); - }); - } - - #[inline] - pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::ALWAYS_ON_BOTTOM, always_on_bottom) - }); - }); - } - - #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) - }); - }); - } - - pub fn set_rtl(&self, rtl: bool) { - let window = self.window.0 .0 as isize; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { - f.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, rtl) - }); - }); - } - - #[inline] - pub fn current_monitor(&self) -> Option { - Some(RootMonitorHandle { - inner: monitor::current_monitor(self.window.0), - }) - } - - #[inline] - pub fn set_window_icon(&self, window_icon: Option) { - if let Some(ref window_icon) = window_icon { - window_icon - .inner - .set_for_window(self.window.0, IconType::Small); - } else { - icon::unset_for_window(self.window.0, IconType::Small); - } - self.window_state.lock().window_icon = window_icon; - } - - #[inline] - pub fn set_taskbar_icon(&self, taskbar_icon: Option) { - if let Some(ref taskbar_icon) = taskbar_icon { - taskbar_icon - .inner - .set_for_window(self.window.0, IconType::Big); - } else { - icon::unset_for_window(self.window.0, IconType::Big); - } - self.window_state.lock().taskbar_icon = taskbar_icon; - } - - pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { - if unsafe { GetSystemMetrics(SM_IMMENABLED) } != 0 { - let composition_form = COMPOSITIONFORM { - dwStyle: CFS_POINT, - ptCurrentPos: POINT { x, y }, - rcArea: RECT::default(), - }; - unsafe { - let himc = ImmGetContext(self.window.0); - let _ = ImmSetCompositionWindow(himc, &composition_form); - let _ = ImmReleaseContext(self.window.0, himc); - } - } - } - - #[inline] - pub fn set_ime_position(&self, spot: Position) { - let (x, y) = spot.to_physical::(self.scale_factor()).into(); - self.set_ime_position_physical(x, y); - } - - #[inline] - pub fn request_user_attention(&self, request_type: Option) { - let window = self.window; - let active_window_handle = unsafe { GetActiveWindow() }; - if window.0 == active_window_handle { - // active window could be minimized, so we skip requesting attention - // if it is not minimized - let window_flags = self.window_state.lock().window_flags(); - let is_minimized = window_flags.contains(WindowFlags::MINIMIZED); - if !is_minimized { - return; - } - } - - let window_isize = window.0 .0 as isize; - - self.thread_executor.execute_in_thread(move || unsafe { - let (flags, count) = request_type - .map(|ty| match ty { - UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX), - UserAttentionType::Informational => (FLASHW_TRAY, 4), - }) - .unwrap_or((FLASHW_STOP, 0)); - - let flash_info = FLASHWINFO { - cbSize: mem::size_of::() as u32, - hwnd: HWND(window_isize as _), - dwFlags: flags, - uCount: count, - dwTimeout: 0, - }; - let _ = FlashWindowEx(&flash_info); - }); - } - - #[inline] - pub fn theme(&self) -> Theme { - self.window_state.lock().current_theme - } - - pub fn set_theme(&self, theme: Option) { - { - let mut window_state = self.window_state.lock(); - if window_state.preferred_theme == theme { - return; - } - window_state.preferred_theme = theme; - } - unsafe { SendMessageW(self.hwnd(), *CHANGE_THEME_MSG_ID, None, None) }; - } - - #[inline] - pub fn reset_dead_keys(&self) { - // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) - // key input which we can call `ToUnicode` with. - unsafe { - let vk = u32::from(VK_SPACE.0); - let scancode = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC); - let kbd_state = [0; 256]; - let mut char_buff: [MaybeUninit; 8] = [MaybeUninit::uninit(); 8]; - ToUnicode( - vk, - scancode, - Some(&kbd_state), - mem::transmute::<&mut [std::mem::MaybeUninit], &mut [u16]>(char_buff.as_mut()), - 0, - ); - } - } - - #[inline] - pub fn begin_resize_drag(&self, edge: isize, button: u32, x: i32, y: i32) { - unsafe { - let w_param = WPARAM(edge as _); - let l_param = util::MAKELPARAM(x as i16, y as i16); - - let _ = ReleaseCapture(); - let _ = PostMessageW(Some(self.hwnd()), button, w_param, l_param); - } - } - - #[inline] - pub(crate) fn set_skip_taskbar(&self, skip: bool) -> Result<(), ExternalError> { - self.window_state.lock().skip_taskbar = skip; - unsafe { set_skip_taskbar(self.hwnd(), skip) } - } - - #[inline] - pub fn set_background_color(&self, color: Option) { - self.window_state.lock().background_color = color; - - unsafe { - let _ = InvalidateRect(Some(self.hwnd()), None, true); - let _ = UpdateWindow(self.hwnd()); - } - } - - #[inline] - pub fn set_progress_bar(&self, progress: ProgressBarState) { - unsafe { - let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap(); - let handle = self.window.0; - - if let Some(state) = progress.state { - let taskbar_state = { - match state { - ProgressState::None => TBPF_NOPROGRESS, - ProgressState::Indeterminate => TBPF_INDETERMINATE, - ProgressState::Normal => TBPF_NORMAL, - ProgressState::Error => TBPF_ERROR, - ProgressState::Paused => TBPF_PAUSED, - } - }; - - taskbar_list - .SetProgressState(handle, taskbar_state) - .unwrap_or(()); - } - if let Some(value) = progress.progress { - let value = if value > 100 { 100 } else { value }; - - taskbar_list - .SetProgressValue(handle, value, 100) - .unwrap_or(()); - } - } - } - - #[inline] - pub fn set_overlay_icon(&self, icon: Option<&Icon>) { - let taskbar: ITaskbarList = - unsafe { CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap() }; - - let icon = icon.map(|i| i.inner.as_raw_handle()).unwrap_or_default(); - - unsafe { - taskbar - .SetOverlayIcon(self.window.0, icon, None) - .unwrap_or(()); - } - } - - #[inline] - pub fn set_undecorated_shadow(&self, shadow: bool) { - let window = self.window; - let window_state = Arc::clone(&self.window_state); - - self.thread_executor.execute_in_thread(move || { - let _ = &window; - WindowState::set_window_flags(window_state.lock(), window.0, |f| { - f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) - }); - }); - } - - #[inline] - pub fn has_undecorated_shadow(&self) -> bool { - self - .window_state - .lock() - .window_flags - .contains(WindowFlags::MARKER_UNDECORATED_SHADOW) - } - - pub fn set_content_protection(&self, enabled: bool) { - unsafe { - let _ = SetWindowDisplayAffinity( - self.hwnd(), - if enabled { - WDA_EXCLUDEFROMCAPTURE - } else { - WDA_NONE - }, - ); - } - } -} - -impl Drop for Window { - #[inline] - fn drop(&mut self) { - KEY_EVENT_BUILDERS.lock().remove(&self.id()); - unsafe { - // The window must be destroyed from the same thread that created it, so we send a - // custom message to be handled by our callback to do the actual work. - let _ = PostMessageW(Some(self.window.0), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); - } - } -} - -unsafe fn init( - attributes: WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes, - event_loop: &EventLoopWindowTarget, -) -> Result { - // registering the window class - let class_name = register_window_class(&pl_attribs.window_classname); - - let mut window_flags = WindowFlags::empty(); - window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); - window_flags.set( - WindowFlags::MARKER_UNDECORATED_SHADOW, - pl_attribs.decoration_shadow, - ); - window_flags.set(WindowFlags::ALWAYS_ON_BOTTOM, attributes.always_on_bottom); - window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top); - window_flags.set( - WindowFlags::NO_BACK_BUFFER, - pl_attribs.no_redirection_bitmap, - ); - window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent); - // WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured. - window_flags.set(WindowFlags::RESIZABLE, attributes.resizable); - window_flags.set(WindowFlags::MINIMIZABLE, attributes.minimizable); - window_flags.set(WindowFlags::MAXIMIZABLE, attributes.maximizable); - // will be changed later using `window.set_closable` - // but we need to have a default for the diffing to work - window_flags.set(WindowFlags::CLOSABLE, true); - - window_flags.set(WindowFlags::FOCUSABLE, attributes.focusable); - - window_flags.set(WindowFlags::MARKER_DONT_FOCUS, !attributes.focused); - - window_flags.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, pl_attribs.rtl); - - let parent = match pl_attribs.parent { - Parent::ChildOf(parent) => { - window_flags.set(WindowFlags::CHILD, true); - if pl_attribs.menu.is_some() { - warn!("Setting a menu on a child window is unsupported"); - } - Some(parent) - } - Parent::OwnedBy(parent) => { - window_flags.set(WindowFlags::POPUP, true); - Some(parent) - } - Parent::None => { - window_flags.set(WindowFlags::ON_TASKBAR, true); - None - } - }; - - // creating the real window this time, by using the functions in `extra_functions` - let real_window = { - let (style, ex_style) = window_flags.to_window_styles(); - let title = util::encode_wide(&attributes.title); - - let (target_monitor, position) = attributes - .position - .and_then(|p| { - monitor::available_monitors() - .into_iter() - .find_map(|monitor| { - let dpi = monitor.dpi(); - let scale_factor = dpi_to_scale_factor(dpi); - let position = p.to_physical::(scale_factor); - let (x, y): (i32, i32) = monitor.position().into(); - let (width, height): (i32, i32) = monitor.size().into(); - - let frame_thickness = if window_flags.contains_shadow() { - util::get_frame_thickness(dpi) - } else { - 0 - }; - - // Only the starting position x needs to be accounted - if x <= position.x + frame_thickness - && position.x <= x + width - && y <= position.y - && position.y <= y + height - { - Some((monitor, position.into())) - } else { - None - } - }) - }) - .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); - - let desired_size = attributes - .inner_size - .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - let clamped_size = attributes - .inner_size_constraints - .clamp(desired_size, target_monitor.scale_factor()); - - // Best effort: try to create the window with the requested inner size - let adjusted_size = { - let (mut w, mut h): (i32, i32) = clamped_size - .to_physical::(target_monitor.scale_factor()) - .into(); - - if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - let mut rect = RECT { - left: 0, - top: 0, - right: w, - bottom: h, - }; - - unsafe { - AdjustWindowRectEx( - &mut rect, - window_flags.to_adjusted_window_styles().0, - pl_attribs.menu.is_some(), - ex_style, - )?; - } - - w = rect.right - rect.left; - h = rect.bottom - rect.top; - } else if window_flags.undecorated_with_shadows() { - let dpi = target_monitor.dpi(); - let insets = calculate_insets_for_dpi(dpi); - w += insets.left + insets.right; - h += insets.top + insets.bottom; - } - - (w, h) - }; - - let handle = CreateWindowExW( - ex_style, - PCWSTR::from_raw(class_name.as_ptr()), - PCWSTR::from_raw(title.as_ptr()), - style, - position.0, - position.1, - adjusted_size.0, - adjusted_size.1, - parent, - pl_attribs.menu, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), - Some(Box::into_raw(Box::new(window_flags)) as _), - )?; - - if !IsWindow(Some(handle)).as_bool() { - return Err(os_error!(OsError::IoError(io::Error::last_os_error()))); - } - - super::dark_mode::allow_dark_mode_for_window(handle, true); - - WindowWrapper(handle) - }; - - // Register for touch events if applicable - { - let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; - if digitizer & NID_READY != 0 { - RegisterTouchWindow(real_window.0, TWF_WANTPALM)?; - } - } - - let dpi = hwnd_dpi(real_window.0); - let scale_factor = dpi_to_scale_factor(dpi); - - // making the window transparent - if attributes.transparent && !pl_attribs.no_redirection_bitmap { - // Empty region for the blur effect, so the window is fully transparent - let region = CreateRectRgn(0, 0, -1, -1); - - let bb = DWM_BLURBEHIND { - dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, - fEnable: true.into(), - hRgnBlur: region, - fTransitionOnMaximized: false.into(), - }; - - let _ = DwmEnableBlurBehindWindow(real_window.0, &bb); - let _ = DeleteObject(region.into()); - } - - // If the system theme is dark, we need to set the window theme now - // before we update the window flags (and possibly show the - // window for the first time). - let current_theme = try_window_theme( - real_window.0, - attributes - .preferred_theme - .or(*event_loop.preferred_theme.lock()), - false, - ); - - let window_state = { - let window_state = WindowState::new( - &attributes, - None, - scale_factor, - current_theme, - attributes.preferred_theme, - attributes.background_color, - ); - let window_state = Arc::new(Mutex::new(window_state)); - WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); - window_state - }; - - let win = Window { - window: real_window, - window_state, - thread_executor: event_loop.create_thread_executor(), - }; - - KEY_EVENT_BUILDERS - .lock() - .insert(win.id(), KeyEventBuilder::default()); - - let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); - win.set_window_icon(attributes.window_icon); - win.set_taskbar_icon(pl_attribs.taskbar_icon); - - if attributes.fullscreen.is_some() { - win.set_fullscreen(attributes.fullscreen); - force_window_active(win.window.0); - } else if attributes.maximized { - win.set_maximized(true); - } - - if attributes.content_protection { - win.set_content_protection(true); - } - - win.set_visible(attributes.visible); - win.set_closable(attributes.closable); - - Ok(win) -} - -unsafe fn register_window_class(window_classname: &str) -> Vec { - let class_name = util::encode_wide(window_classname); - - let class = WNDCLASSEXW { - cbSize: mem::size_of::() as u32, - style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC, - lpfnWndProc: Some(window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), - hIcon: HICON::default(), - hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly - hbrBackground: HBRUSH::default(), - lpszMenuName: PCWSTR::null(), - lpszClassName: PCWSTR::from_raw(class_name.as_ptr()), - hIconSm: HICON::default(), - }; - - // We ignore errors because registering the same window class twice would trigger - // an error, and because errors here are detected during CreateWindowEx anyway. - // Also since there is no weird element in the struct, there is no reason for this - // call to fail. - RegisterClassExW(&class); - - class_name -} - -unsafe extern "system" fn window_proc( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // This window procedure is only needed until the subclass procedure is attached. - // we need this because we need to respond to WM_NCCALCSIZE as soon as possible - // in order to make the window borderless if needed. - match msg { - win32wm::WM_NCCALCSIZE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata != 0 { - let window_flags = WindowFlags::from_bits_truncate(userdata as _); - - if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - return DefWindowProcW(window, msg, wparam, lparam); - } - - // adjust the maximized borderless window so it doesn't cover the taskbar - if util::is_maximized(window).unwrap_or(false) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - if let Ok(monitor_info) = - monitor::get_monitor_info(MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL)) - { - params.rgrc[0] = monitor_info.monitorInfo.rcWork; - } - } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - - let insets = util::calculate_window_insets(window); - - params.rgrc[0].left += insets.left; - params.rgrc[0].top += insets.top; - params.rgrc[0].right -= insets.right; - params.rgrc[0].bottom -= insets.bottom; - } - return LRESULT(0); // return 0 here to make the window borderless - } - - DefWindowProcW(window, msg, wparam, lparam) - } - win32wm::WM_NCCREATE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata == 0 { - let createstruct = &*(lparam.0 as *const CREATESTRUCTW); - let userdata = createstruct.lpCreateParams; - let window_flags = Box::from_raw(userdata as *mut WindowFlags); - util::SetWindowLongPtrW(window, GWL_USERDATA, window_flags.bits() as _); - } - DefWindowProcW(window, msg, wparam, lparam) - } - _ => DefWindowProcW(window, msg, wparam, lparam), - } -} - -struct ComInitialized(Option<()>); -impl Drop for ComInitialized { - fn drop(&mut self) { - if let Some(()) = self.0.take() { - unsafe { CoUninitialize() }; - } - } -} - -thread_local! { - static COM_INITIALIZED: ComInitialized = { - unsafe { - ComInitialized(match CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok() { - Ok(()) => Some(()), - Err(_) => None, - }) - } - }; - - static TASKBAR_LIST: RefCell> = const { RefCell::new(None) }; -} - -pub fn com_initialized() { - COM_INITIALIZED.with(|_| {}); -} - -// Reference Implementation: -// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc -// -// As per MSDN marking the window as fullscreen should ensure that the -// taskbar is moved to the bottom of the Z-order when the fullscreen window -// is activated. If the window is not fullscreen, the Shell falls back to -// heuristics to determine how the window should be treated, which means -// that it could still consider the window as fullscreen. :( -unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { - com_initialized(); - - TASKBAR_LIST.with(|task_bar_list_ptr| { - let mut task_bar_list = task_bar_list_ptr.borrow().clone(); - - if task_bar_list.is_none() { - let result: windows::core::Result = - CoCreateInstance(&TaskbarList, None, CLSCTX_ALL); - if let Ok(created) = result { - if let Ok(()) = created.HrInit() { - task_bar_list = Some(created); - } - } - - if task_bar_list.is_none() { - return; - } - - *task_bar_list_ptr.borrow_mut() = task_bar_list.clone(); - } - - let _ = task_bar_list - .unwrap() - .MarkFullscreenWindow(handle, fullscreen); - }) -} - -unsafe fn force_window_active(handle: HWND) { - // Try to focus the window without the hack first. - if SetForegroundWindow(handle).as_bool() { - return; - } - - // In some situations, calling SetForegroundWindow could not bring up the window, - // This is a little hack which can "steal" the foreground window permission. - // We only call this function in the window creation, so it should be fine. - // See: https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open - let alt_sc = MapVirtualKeyW(u32::from(VK_MENU.0), MAPVK_VK_TO_VSC); - - let mut inputs: [INPUT; 2] = mem::zeroed(); - inputs[0].r#type = INPUT_KEYBOARD; - inputs[0].Anonymous.ki.wVk = VK_LMENU as _; - inputs[0].Anonymous.ki.wScan = alt_sc as _; - inputs[0].Anonymous.ki.dwFlags = KEYEVENTF_EXTENDEDKEY; - - inputs[1].r#type = INPUT_KEYBOARD; - inputs[1].Anonymous.ki.wVk = VK_LMENU as _; - inputs[1].Anonymous.ki.wScan = alt_sc as _; - inputs[1].Anonymous.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP; - - // Simulate a key press and release - SendInput(&inputs, mem::size_of::() as _); - - let _ = SetForegroundWindow(handle); -} - -pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) -> Result<(), ExternalError> { - com_initialized(); - let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER)?; - if skip { - taskbar_list.DeleteTab(hwnd)?; - } else { - taskbar_list.AddTab(hwnd)?; - } - - Ok(()) -} - -impl ResizeDirection { - pub(crate) fn to_win32(&self) -> u32 { - match self { - ResizeDirection::East => HTRIGHT, - ResizeDirection::North => HTTOP, - ResizeDirection::NorthEast => HTTOPRIGHT, - ResizeDirection::NorthWest => HTTOPLEFT, - ResizeDirection::South => HTBOTTOM, - ResizeDirection::SouthEast => HTBOTTOMRIGHT, - ResizeDirection::SouthWest => HTBOTTOMLEFT, - ResizeDirection::West => HTLEFT, - } - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs deleted file mode 100644 index 6437c80df..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - dpi::PhysicalPosition, - icon::Icon, - keyboard::ModifiersState, - platform_impl::platform::{event_loop, minimal_ime::MinimalIme, util}, - window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints, RGBA}, -}; -use parking_lot::MutexGuard; -use std::io; -use windows::Win32::{ - Foundation::{HWND, LPARAM, RECT, WPARAM}, - Graphics::Gdi::InvalidateRgn, - UI::WindowsAndMessaging::*, -}; - -/// Contains information about states and the window that the callback is going to use. -pub struct WindowState { - pub mouse: MouseProperties, - - /// Used by `WM_GETMINMAXINFO`. - pub size_constraints: WindowSizeConstraints, - - pub window_icon: Option, - pub taskbar_icon: Option, - - pub saved_window: Option, - pub scale_factor: f64, - - pub dragging: bool, - - pub skip_taskbar: bool, - - pub modifiers_state: ModifiersState, - pub fullscreen: Option, - pub current_theme: Theme, - pub preferred_theme: Option, - - pub ime_handler: MinimalIme, - - pub window_flags: WindowFlags, - - // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS - pub is_active: bool, - pub is_focused: bool, - - pub background_color: Option, -} - -unsafe impl Send for WindowState {} -unsafe impl Sync for WindowState {} - -#[derive(Clone)] -pub struct SavedWindow { - pub placement: WINDOWPLACEMENT, -} - -#[derive(Clone)] -pub struct MouseProperties { - pub cursor: CursorIcon, - pub capture_count: u32, - cursor_flags: CursorFlags, - pub last_position: Option>, -} - -bitflags! { - #[derive(Clone, Copy)] - pub struct CursorFlags: u8 { - const GRABBED = 1 << 0; - const HIDDEN = 1 << 1; - const IN_WINDOW = 1 << 2; - } -} -bitflags! { - #[derive(Clone, Copy, PartialEq)] - pub struct WindowFlags: u32 { - const RESIZABLE = 1 << 0; - const VISIBLE = 1 << 1; - const ON_TASKBAR = 1 << 2; - const ALWAYS_ON_TOP = 1 << 3; - const NO_BACK_BUFFER = 1 << 4; - const TRANSPARENT = 1 << 5; - const CHILD = 1 << 6; - const MAXIMIZED = 1 << 7; - const POPUP = 1 << 8; - const ALWAYS_ON_BOTTOM = 1 << 9; - const MINIMIZABLE = 1 << 10; - const MAXIMIZABLE = 1 << 11; - const CLOSABLE = 1 << 12; - const MINIMIZED = 1 << 13; - - const IGNORE_CURSOR_EVENT = 1 << 14; - - /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is - /// included here to make masking easier. - const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 15; - const MARKER_BORDERLESS_FULLSCREEN = 1 << 16; - - /// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`. - /// In most cases, it's okay to let those parameters change the state. However, when we're - /// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to - /// effect our stored state, because the purpose of `apply_diff` is to update the actual - /// window's state to match our stored state. This controls whether to accept those changes. - const MARKER_RETAIN_STATE_ON_SIZE = 1 << 17; - - const MARKER_IN_SIZE_MOVE = 1 << 18; - - const MARKER_DONT_FOCUS = 1 << 19; - - /// Fully decorated window (incl. caption, border and drop shadow). - const MARKER_DECORATIONS = 1 << 20; - /// Drop shadow for undecorated windows. - const MARKER_UNDECORATED_SHADOW = 1 << 21; - - const RIGHT_TO_LEFT_LAYOUT = 1 << 22; - - const FOCUSABLE = 1 << 23; - - const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); - } -} - -impl WindowState { - pub fn new( - attributes: &WindowAttributes, - taskbar_icon: Option, - scale_factor: f64, - current_theme: Theme, - preferred_theme: Option, - background_color: Option, - ) -> WindowState { - WindowState { - mouse: MouseProperties { - cursor: CursorIcon::default(), - capture_count: 0, - cursor_flags: CursorFlags::empty(), - last_position: None, - }, - - size_constraints: attributes.inner_size_constraints, - - window_icon: attributes.window_icon.clone(), - taskbar_icon, - - saved_window: None, - scale_factor, - - dragging: false, - - skip_taskbar: false, - - modifiers_state: ModifiersState::default(), - fullscreen: None, - current_theme, - preferred_theme, - ime_handler: MinimalIme::default(), - window_flags: WindowFlags::empty(), - is_active: false, - is_focused: false, - - background_color, - } - } - - pub fn window_flags(&self) -> WindowFlags { - self.window_flags - } - - pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) - where - F: FnOnce(&mut WindowFlags), - { - let old_flags = this.window_flags; - f(&mut this.window_flags); - let new_flags = this.window_flags; - - drop(this); - old_flags.apply_diff(window, new_flags); - } - - pub fn set_window_flags_in_place(&mut self, f: F) - where - F: FnOnce(&mut WindowFlags), - { - f(&mut self.window_flags); - } - - pub fn has_active_focus(&self) -> bool { - self.is_active && self.is_focused - } - - // Updates is_active and returns whether active-focus state has changed - pub fn set_active(&mut self, is_active: bool) -> bool { - let old = self.has_active_focus(); - self.is_active = is_active; - old != self.has_active_focus() - } - - // Updates is_focused and returns whether active-focus state has changed - pub fn set_focused(&mut self, is_focused: bool) -> bool { - let old = self.has_active_focus(); - self.is_focused = is_focused; - old != self.has_active_focus() - } -} - -impl MouseProperties { - pub fn cursor_flags(&self) -> CursorFlags { - self.cursor_flags - } - - pub fn set_cursor_flags(&mut self, window: HWND, f: F) -> Result<(), io::Error> - where - F: FnOnce(&mut CursorFlags), - { - let old_flags = self.cursor_flags; - f(&mut self.cursor_flags); - match self.cursor_flags.refresh_os_cursor(window) { - Ok(()) => (), - Err(e) => { - self.cursor_flags = old_flags; - return Err(e); - } - } - - Ok(()) - } -} - -impl WindowFlags { - fn mask(mut self) -> WindowFlags { - if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { - self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; - } - - self - } - - pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { - let (mut style, mut style_ex) = (Default::default(), Default::default()); - style |= WS_CAPTION | WS_CLIPSIBLINGS | WS_SYSMENU; - style_ex |= WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; - if self.contains(WindowFlags::RESIZABLE) { - style |= WS_SIZEBOX; - } - if self.contains(WindowFlags::MAXIMIZABLE) { - style |= WS_MAXIMIZEBOX; - } - if self.contains(WindowFlags::MINIMIZABLE) { - style |= WS_MINIMIZEBOX; - } - if self.contains(WindowFlags::VISIBLE) { - style |= WS_VISIBLE; - } - if self.contains(WindowFlags::ON_TASKBAR) { - style_ex |= WS_EX_APPWINDOW; - } - if self.contains(WindowFlags::ALWAYS_ON_TOP) { - style_ex |= WS_EX_TOPMOST; - } - if self.contains(WindowFlags::NO_BACK_BUFFER) { - style_ex |= WS_EX_NOREDIRECTIONBITMAP; - } - if self.contains(WindowFlags::CHILD) { - style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. - - // Remove decorations window styles for child - if !self.contains(WindowFlags::MARKER_DECORATIONS) { - style &= !WS_CAPTION; - style_ex &= !WS_EX_WINDOWEDGE; - } - } - if self.contains(WindowFlags::POPUP) { - style |= WS_POPUP; - } - if self.contains(WindowFlags::MINIMIZED) { - style |= WS_MINIMIZE; - } - if self.contains(WindowFlags::MAXIMIZED) { - style |= WS_MAXIMIZE; - } - if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) { - style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; - } - if self.intersects( - WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, - ) { - style &= !WS_OVERLAPPEDWINDOW; - } - if self.contains(WindowFlags::RIGHT_TO_LEFT_LAYOUT) { - style_ex |= WS_EX_LAYOUTRTL | WS_EX_RTLREADING | WS_EX_RIGHT; - } - if !self.contains(WindowFlags::FOCUSABLE) { - style_ex |= WS_EX_NOACTIVATE; - } - - (style, style_ex) - } - - /// Returns the appropriate window styles for `AdjustWindowRectEx` - pub fn to_adjusted_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { - let (mut style, style_ex) = self.to_window_styles(); - - if !self.contains(WindowFlags::MARKER_DECORATIONS) { - style &= !(WS_CAPTION | WS_THICKFRAME) - } - - (style, style_ex) - } - - /// Adjust the window client rectangle to the return value, if present. - fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { - self = self.mask(); - new = new.mask(); - - let mut diff = self ^ new; - - if diff == WindowFlags::empty() { - return; - } - - if new.contains(WindowFlags::VISIBLE) { - unsafe { - let _ = ShowWindow( - window, - if self.contains(WindowFlags::MARKER_DONT_FOCUS) { - self.set(WindowFlags::MARKER_DONT_FOCUS, false); - SW_SHOWNOACTIVATE - } else { - SW_SHOW - }, - ); - } - } - - if diff.contains(WindowFlags::ALWAYS_ON_TOP) { - unsafe { - let _ = SetWindowPos( - window, - Some(if new.contains(WindowFlags::ALWAYS_ON_TOP) { - HWND_TOPMOST - } else { - HWND_NOTOPMOST - }), - 0, - 0, - 0, - 0, - SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, - ); - let _ = InvalidateRgn(window, None, false); - } - } - - if diff.contains(WindowFlags::ALWAYS_ON_BOTTOM) { - unsafe { - let _ = SetWindowPos( - window, - Some(if new.contains(WindowFlags::ALWAYS_ON_BOTTOM) { - HWND_BOTTOM - } else { - HWND_NOTOPMOST - }), - 0, - 0, - 0, - 0, - SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, - ); - let _ = InvalidateRgn(window, None, false); - } - } - - if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { - unsafe { - let _ = ShowWindow( - window, - match new.contains(WindowFlags::MAXIMIZED) { - true => SW_MAXIMIZE, - false => SW_RESTORE, - }, - ); - } - } - - // Minimize operations should execute after maximize for proper window animations - if diff.contains(WindowFlags::MINIMIZED) { - unsafe { - let _ = ShowWindow( - window, - match new.contains(WindowFlags::MINIMIZED) { - true => SW_MINIMIZE, - false => SW_RESTORE, - }, - ); - } - - diff.remove(WindowFlags::MINIMIZED); - } - - if diff.contains(WindowFlags::CLOSABLE) || new.contains(WindowFlags::CLOSABLE) { - unsafe { - let system_menu = GetSystemMenu(window, false); - let _ = EnableMenuItem( - system_menu, - SC_CLOSE, - MF_BYCOMMAND - | if new.contains(WindowFlags::CLOSABLE) { - MF_ENABLED - } else { - MF_GRAYED - }, - ); - } - } - - if !new.contains(WindowFlags::VISIBLE) { - unsafe { - let _ = ShowWindow(window, SW_HIDE); - } - } - - if diff != WindowFlags::empty() { - let (style, style_ex) = new.to_window_styles(); - - unsafe { - SendMessageW( - window, - *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, - Some(WPARAM(1)), - Some(LPARAM(0)), - ); - - // This condition is necessary to avoid having an unrestorable window - if !new.contains(WindowFlags::MINIMIZED) { - SetWindowLongW(window, GWL_STYLE, style.0 as i32); - SetWindowLongW(window, GWL_EXSTYLE, style_ex.0 as i32); - } - - let mut flags = SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED; - - // We generally don't want style changes here to affect window - // focus, but for fullscreen windows they must be activated - // (i.e. focused) so that they appear on top of the taskbar - if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) - && !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) - { - flags |= SWP_NOACTIVATE; - } - - // Refresh the window frame - let _ = SetWindowPos(window, None, 0, 0, 0, 0, flags); - SendMessageW( - window, - *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, - Some(WPARAM(0)), - Some(LPARAM(0)), - ); - } - } - } - - pub fn undecorated_with_shadows(&self) -> bool { - self.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) - && !self.contains(WindowFlags::MARKER_DECORATIONS) - } - - pub fn contains_shadow(&self) -> bool { - self.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) - || self.contains(WindowFlags::MARKER_DECORATIONS) - } -} - -impl CursorFlags { - fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { - let client_rect = util::get_client_rect(window)?; - - if util::is_focused(window) { - let cursor_clip = match self.contains(CursorFlags::GRABBED) { - true => Some(client_rect), - false => None, - }; - - let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom); - let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?); - let desktop_rect = rect_to_tuple(util::get_desktop_rect()); - - let active_cursor_clip = match desktop_rect == active_cursor_clip { - true => None, - false => Some(active_cursor_clip), - }; - - // We do this check because calling `set_cursor_clip` incessantly will flood the event - // loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags` - // which at times gets called once every iteration of the eventloop. - if active_cursor_clip != cursor_clip.map(rect_to_tuple) { - util::set_cursor_clip(cursor_clip)?; - } - } - - let cursor_in_client = self.contains(CursorFlags::IN_WINDOW); - if cursor_in_client { - util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN)); - } else { - util::set_cursor_hidden(false); - } - - Ok(()) - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs deleted file mode 100644 index 9ec5ccc49..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs +++ /dev/null @@ -1,1698 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -//! The `Window` struct and associated types. -use std::fmt; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Pixel, PixelUnit, Position, Size}, - error::{ExternalError, NotSupportedError, OsError}, - event_loop::EventLoopWindowTarget, - monitor::{MonitorHandle, VideoMode}, - platform_impl, -}; - -pub use crate::icon::{BadIcon, Icon}; - -/// Progress State -#[derive(Debug, Clone, Copy)] -pub enum ProgressState { - None, - Normal, - /// **Treated as Normal in linux and macOS** - Indeterminate, - /// **Treated as Normal in linux** - Paused, - /// **Treated as Normal in linux** - Error, -} - -pub struct ProgressBarState { - /// The progress bar state. - pub state: Option, - /// The progress bar progress. This can be a value ranging from `0` to `100` - pub progress: Option, - /// The `.desktop` filename with the Unity desktop window manager, for example `myapp.desktop` **Linux Only** - pub desktop_filename: Option, -} - -/// Represents a window. -/// -/// # Example -/// -/// ```no_run -/// use tao::{ -/// event::{Event, WindowEvent}, -/// event_loop::{ControlFlow, EventLoop}, -/// window::Window, -/// }; -/// -/// let mut event_loop = EventLoop::new(); -/// let window = Window::new(&event_loop).unwrap(); -/// -/// event_loop.run(move |event, _, control_flow| { -/// *control_flow = ControlFlow::Wait; -/// -/// match event { -/// Event::WindowEvent { -/// event: WindowEvent::CloseRequested, -/// .. -/// } => *control_flow = ControlFlow::Exit, -/// _ => (), -/// } -/// }); -/// ``` -pub struct Window { - pub(crate) window: platform_impl::Window, -} - -impl fmt::Debug for Window { - fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { - fmtr.pad("Window { .. }") - } -} - -impl Drop for Window { - fn drop(&mut self) { - // If the window is in exclusive fullscreen, we must restore the desktop - // video mode (generally this would be done on application exit, but - // closing the window doesn't necessarily always mean application exit, - // such as when there are multiple windows) - if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() { - self.set_fullscreen(None); - } - } -} - -/// Type alias for a color in the RGBA format. -/// -/// Each value can be 0..255 inclusive. -pub type RGBA = (u8, u8, u8, u8); - -/// Identifier of a window. Unique for each window. -/// -/// Can be obtained with `window.id()`. -/// -/// Whenever you receive an event specific to a window, this event contains a `WindowId` which you -/// can then compare to the ids of your windows. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(pub(crate) platform_impl::WindowId); - -impl WindowId { - /// # Safety - /// Returns a dummy `WindowId`, useful for unit testing. The only guarantee made about the return - /// value of this function is that it will always be equal to itself and to future values returned - /// by this function. No other guarantees are made. This may be equal to a real `WindowId`. - /// - /// **Passing this into a tao function will result in undefined behavior.** - pub unsafe fn dummy() -> Self { - WindowId(platform_impl::WindowId::dummy()) - } -} - -/// Object that allows you to build windows. -#[derive(Clone, Default)] -pub struct WindowBuilder { - /// The attributes to use to create the window. - pub window: WindowAttributes, - - // Platform-specific configuration. - pub(crate) platform_specific: platform_impl::PlatformSpecificWindowBuilderAttributes, -} - -impl fmt::Debug for WindowBuilder { - fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { - fmtr - .debug_struct("WindowBuilder") - .field("window", &self.window) - .finish() - } -} - -/// Attributes to use when creating a window. -#[derive(Debug, Clone)] -pub struct WindowAttributes { - /// The dimensions of the window. If this is `None`, some platform-specific dimensions will be - /// used. - /// - /// The default is `None`. - pub inner_size: Option, - - /// The window size constraints - pub inner_size_constraints: WindowSizeConstraints, - - /// The desired position of the window. If this is `None`, some platform-specific position - /// will be chosen. - /// - /// The default is `None`. - /// - /// ## Platform-specific - /// - /// - **macOS**: The top left corner position of the window content, the window's "inner" - /// position. The window title bar will be placed above it. - /// The window will be positioned such that it fits on screen, maintaining - /// set `inner_size` if any. - /// If you need to precisely position the top left corner of the whole window you have to - /// use [`Window::set_outer_position`] after creating the window. - /// - **Windows**: The top left corner position of the window title bar, the window's "outer" - /// position. - /// There may be a small gap between this position and the window due to the specifics of the - /// Window Manager. - /// - **Linux**: The top left corner of the window, the window's "outer" position. - /// - **Linux(Wayland)**: Unsupported. - /// - **Others**: Ignored. - /// - /// See [`Window::set_outer_position`]. - /// - /// [`Window::set_outer_position`]: crate::window::Window::set_outer_position - pub position: Option, - - /// Whether the window is resizable or not. - /// - /// The default is `true`. - pub resizable: bool, - - /// Whether the window is minimizable or not. - /// - /// The default is `true`. - /// - /// See [`Window::set_minimizable`] for details. - pub minimizable: bool, - - /// Whether the window is maximizable or not. - /// - /// The default is `true`. - /// - /// See [`Window::set_maximizable`] for details. - pub maximizable: bool, - - /// Whether the window is closable or not. - /// - /// The default is `true`. - /// - /// See [`Window::set_closable`] for details. - pub closable: bool, - - /// Whether the window should be set as fullscreen upon creation. - /// - /// The default is `None`. - pub fullscreen: Option, - - /// The title of the window in the title bar. - /// - /// The default is `"tao window"`. - pub title: String, - - /// Whether the window should be maximized upon creation. - /// - /// The default is `false`. - pub maximized: bool, - - /// Whether the window should be immediately visible upon creation. - /// - /// The default is `true`. - pub visible: bool, - - /// Whether the the window should be transparent. If this is true, writing colors - /// with alpha values different than `1.0` will produce a transparent window. - /// - /// The default is `false`. - pub transparent: bool, - - /// Whether the window should have borders and bars. - /// - /// The default is `true`. - pub decorations: bool, - - /// Whether the window should always be on top of other windows. - /// - /// The default is `false`. - /// - /// ## Platform-specific: - /// - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland):** Unsupported. - // TODO: Unsupported in gtk4 - pub always_on_top: bool, - - /// Whether the window should always be on bottom of other windows. - /// - /// The default is `false`. - /// - /// ## Platform-specific: - /// - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland):** Unsupported. - // TODO: Unsupported in gtk4 - pub always_on_bottom: bool, - - /// The window icon. - /// - /// The default is `None`. - pub window_icon: Option, - - pub preferred_theme: Option, - - /// Whether the window should be initially focused or not. - /// - /// ## Platform-specific: - /// - /// **Android / iOS:** Unsupported. - pub focused: bool, - - /// Whether the window should be focusable or not. - /// - /// ## Platform-specific: - /// - /// **Android / iOS:** Unsupported. - pub focusable: bool, - - /// Prevents the window contents from being captured by other apps. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Linux:** Unsupported. - pub content_protection: bool, - - /// Sets whether the window should be visible on all workspaces. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Windows:** Unsupported. - pub visible_on_all_workspaces: bool, - - /// Sets the window background color. - /// - /// ## Platform-specific: - /// - /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see - /// - **iOS / Android:** Unsupported. - pub background_color: Option, -} - -impl Default for WindowAttributes { - #[inline] - fn default() -> WindowAttributes { - WindowAttributes { - inner_size: None, - inner_size_constraints: Default::default(), - position: None, - resizable: true, - minimizable: true, - maximizable: true, - closable: true, - title: "tao window".to_owned(), - maximized: false, - fullscreen: None, - visible: true, - transparent: false, - decorations: true, - always_on_top: false, - always_on_bottom: false, - window_icon: None, - preferred_theme: None, - focused: true, - focusable: true, - content_protection: false, - visible_on_all_workspaces: false, - background_color: None, - } - } -} - -impl WindowBuilder { - /// Initializes a new `WindowBuilder` with default values. - #[inline] - pub fn new() -> Self { - Default::default() - } - - /// Requests the window to be of specific dimensions. - /// - /// See [`Window::set_inner_size`] for details. - /// - /// [`Window::set_inner_size`]: crate::window::Window::set_inner_size - #[inline] - pub fn with_inner_size>(mut self, size: S) -> Self { - self.window.inner_size = Some(size.into()); - self - } - - /// Sets a minimum dimension size for the window. - /// - /// See [`Window::set_min_inner_size`] for details. - /// - /// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size - #[inline] - pub fn with_min_inner_size>(mut self, min_size: S) -> Self { - let size: Size = min_size.into(); - let (width, height) = crate::extract_width_height(size); - self.window.inner_size_constraints.min_width = Some(width); - self.window.inner_size_constraints.min_height = Some(height); - self - } - - /// Sets a maximum dimension size for the window. - /// - /// See [`Window::set_max_inner_size`] for details. - /// - /// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size - #[inline] - pub fn with_max_inner_size>(mut self, max_size: S) -> Self { - let size: Size = max_size.into(); - let (width, height) = crate::extract_width_height(size); - self.window.inner_size_constraints.max_width = Some(width); - self.window.inner_size_constraints.max_height = Some(height); - self - } - - /// Sets inner size constraints for the window. - /// - /// See [`Window::set_inner_size_constraints`] for details. - /// - /// [`Window::set_inner_size_constraints`]: crate::window::Window::set_inner_size_constraints - #[inline] - pub fn with_inner_size_constraints(mut self, constraints: WindowSizeConstraints) -> Self { - self.window.inner_size_constraints = constraints; - self - } - - /// Sets a desired initial position for the window. - /// - /// See [`WindowAttributes::position`] for details. - /// - /// [`WindowAttributes::position`]: crate::window::WindowAttributes::position - #[inline] - pub fn with_position>(mut self, position: P) -> Self { - self.window.position = Some(position.into()); - self - } - - /// Sets whether the window is resizable or not. - /// - /// See [`Window::set_resizable`] for details. - /// - /// [`Window::set_resizable`]: crate::window::Window::set_resizable - #[inline] - pub fn with_resizable(mut self, resizable: bool) -> Self { - self.window.resizable = resizable; - self - } - - /// Sets whether the window is minimizable or not. - /// - /// See [`Window::set_minimizable`] for details. - /// - /// [`Window::set_minimizable`]: crate::window::Window::set_minimizable - #[inline] - pub fn with_minimizable(mut self, minimizable: bool) -> Self { - self.window.minimizable = minimizable; - self - } - - /// Sets whether the window is maximizable or not. - /// - /// See [`Window::set_maximizable`] for details. - /// - /// [`Window::set_maximizable`]: crate::window::Window::set_maximizable - #[inline] - pub fn with_maximizable(mut self, maximizable: bool) -> Self { - self.window.maximizable = maximizable; - self - } - - /// Sets whether the window is closable or not. - /// - /// See [`Window::set_closable`] for details. - /// - /// [`Window::set_closable`]: crate::window::Window::set_closable - #[inline] - pub fn with_closable(mut self, closable: bool) -> Self { - self.window.closable = closable; - self - } - - /// Requests a specific title for the window. - /// - /// See [`Window::set_title`] for details. - /// - /// [`Window::set_title`]: crate::window::Window::set_title - #[inline] - pub fn with_title>(mut self, title: T) -> Self { - self.window.title = title.into(); - self - } - - /// Sets the window fullscreen state. - /// - /// See [`Window::set_fullscreen`] for details. - /// - /// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen - #[inline] - pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { - self.window.fullscreen = fullscreen; - self - } - - /// Requests maximized mode. - /// - /// See [`Window::set_maximized`] for details. - /// - /// [`Window::set_maximized`]: crate::window::Window::set_maximized - #[inline] - pub fn with_maximized(mut self, maximized: bool) -> Self { - self.window.maximized = maximized; - self - } - - /// Sets whether the window will be initially hidden or visible. - /// - /// See [`Window::set_visible`] for details. - /// - /// [`Window::set_visible`]: crate::window::Window::set_visible - #[inline] - pub fn with_visible(mut self, visible: bool) -> Self { - self.window.visible = visible; - self - } - - /// Sets whether the background of the window should be transparent. - #[inline] - pub fn with_transparent(mut self, transparent: bool) -> Self { - self.window.transparent = transparent; - self - } - - /// Sets whether the window should have a border, a title bar, etc. - /// - /// See [`Window::set_decorations`] for details. - /// - /// [`Window::set_decorations`]: crate::window::Window::set_decorations - #[inline] - pub fn with_decorations(mut self, decorations: bool) -> Self { - self.window.decorations = decorations; - self - } - - /// Sets whether or not the window will always be below other windows. - /// - /// See [`Window::set_always_on_bottom`] for details. - /// - /// [`Window::set_always_on_bottom`]: crate::window::Window::set_always_on_bottom - #[inline] - pub fn with_always_on_bottom(mut self, always_on_bottom: bool) -> Self { - self.window.always_on_top = false; - self.window.always_on_bottom = always_on_bottom; - self - } - - /// Sets whether or not the window will always be on top of other windows. - /// - /// See [`Window::set_always_on_top`] for details. - /// - /// [`Window::set_always_on_top`]: crate::window::Window::set_always_on_top - #[inline] - pub fn with_always_on_top(mut self, always_on_top: bool) -> Self { - self.window.always_on_bottom = false; - self.window.always_on_top = always_on_top; - self - } - - /// Sets the window icon. - /// - /// See [`Window::set_window_icon`] for details. - /// - /// [`Window::set_window_icon`]: crate::window::Window::set_window_icon - #[inline] - pub fn with_window_icon(mut self, window_icon: Option) -> Self { - self.window.window_icon = window_icon; - self - } - - /// Forces a theme or uses the system settings if `None` was provided. - /// - /// ## Platform-specific: - /// - /// - **Windows**: It is recommended to always use the same theme used - /// in [`EventLoopBuilderExtWindows::with_theme`] for this method also - /// or use `None` so it automatically uses the theme used in [`EventLoopBuilderExtWindows::with_theme`] - /// or falls back to the system preference, because [`EventLoopBuilderExtWindows::with_theme`] changes - /// the theme for some controls like context menus which is app-wide and can't be changed by this method. - /// - /// [`EventLoopBuilderExtWindows::with_theme`]: crate::platform::windows::EventLoopBuilderExtWindows::with_theme - #[allow(rustdoc::broken_intra_doc_links)] - #[inline] - pub fn with_theme(mut self, theme: Option) -> WindowBuilder { - self.window.preferred_theme = theme; - self - } - - /// Whether the window will be initially focused or not. - /// - /// ## Platform-specific: - /// - /// **Android / iOS:** Unsupported. - #[inline] - pub fn with_focused(mut self, focused: bool) -> WindowBuilder { - self.window.focused = focused; - self - } - - /// Whether the window will be focusable or not. - /// - /// ## Platform-specific: - /// **Android / iOS:** Unsupported. - #[inline] - pub fn with_focusable(mut self, focusable: bool) -> WindowBuilder { - self.window.focusable = focusable; - self - } - - /// Prevents the window contents from being captured by other apps. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Linux:** Unsupported. - #[inline] - pub fn with_content_protection(mut self, protected: bool) -> WindowBuilder { - self.window.content_protection = protected; - self - } - - /// Sets whether the window should be visible on all workspaces. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Windows:** Unsupported. - #[inline] - pub fn with_visible_on_all_workspaces(mut self, visible: bool) -> WindowBuilder { - self.window.visible_on_all_workspaces = visible; - self - } - - /// Sets the window background color. - /// - /// ## Platform-specific: - /// - /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn with_background_color(mut self, color: RGBA) -> WindowBuilder { - self.window.background_color = Some(color); - self - } - - /// Builds the window. - /// - /// Possible causes of error include denied permission, incompatible system, and lack of memory. - #[inline] - pub fn build( - self, - window_target: &EventLoopWindowTarget, - ) -> Result { - platform_impl::Window::new(&window_target.p, self.window, self.platform_specific).map( - |window| { - window.request_redraw(); - Window { window } - }, - ) - } -} - -/// Base Window functions. -impl Window { - /// Creates a new Window for platforms where this is appropriate. - /// - /// This function is equivalent to [`WindowBuilder::new().build(event_loop)`]. - /// - /// Error should be very rare and only occur in case of permission denied, incompatible system, - /// out of memory, etc. - /// - /// [`WindowBuilder::new().build(event_loop)`]: crate::window::WindowBuilder::build - #[inline] - pub fn new(event_loop: &EventLoopWindowTarget) -> Result { - let builder = WindowBuilder::new(); - builder.build(event_loop) - } - - /// Returns an identifier unique to the window. - #[inline] - pub fn id(&self) -> WindowId { - WindowId(self.window.id()) - } - - /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. - /// - /// See the [`dpi`](crate::dpi) module for more information. - /// - /// Note that this value can change depending on user action (for example if the window is - /// moved to another screen); as such, tracking `WindowEvent::ScaleFactorChanged` events is - /// the most robust way to track the DPI you need to use to draw. - /// - /// ## Platform-specific - /// - /// - **Android:** Always returns 1.0. - /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s - /// [`contentScaleFactor`]. - /// - /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc - #[inline] - pub fn scale_factor(&self) -> f64 { - self.window.scale_factor() - } - - /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS - /// events have been processed by the event loop. - /// - /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with - /// OS-requested redraws (e.g. when a window gets resized). - /// - /// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared` - /// but before `Event::NewEvents` if called in the following circumstances: - /// * While processing `MainEventsCleared`. - /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any - /// directly subsequent `RedrawRequested` event. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. - /// - **Android:** Unsupported. - #[inline] - pub fn request_redraw(&self) { - self.window.request_redraw() - } -} - -/// Position and size functions. -impl Window { - /// Returns the position of the top-left hand corner of the window's client area relative to the - /// top-left hand corner of the desktop. - /// - /// The same conditions that apply to `outer_position` apply to this method. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the - /// window's [safe area] in the screen space coordinate system. - /// - **Android:** Always returns [`NotSupportedError`]. - /// - /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc - #[inline] - pub fn inner_position(&self) -> Result, NotSupportedError> { - self.window.inner_position() - } - - /// Returns the position of the top-left hand corner of the window relative to the - /// top-left hand corner of the desktop. - /// - /// Note that the top-left hand corner of the desktop is not necessarily the same as - /// the screen. If the user uses a desktop with multiple monitors, the top-left hand corner - /// of the desktop is the top-left hand corner of the monitor at the top-left of the desktop. - /// - /// The coordinates can be negative if the top-left hand corner of the window is outside - /// of the visible screen region. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the - /// window in the screen space coordinate system. - /// - **Android:** Always returns [`NotSupportedError`]. - /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global cordinate system - #[inline] - pub fn outer_position(&self) -> Result, NotSupportedError> { - self.window.outer_position() - } - - /// Modifies the position of the window. - /// - /// See `outer_position` for more information about the coordinates. This automatically un-maximizes the - /// window if it's maximized. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the - /// window in the screen space coordinate system. - /// - **Android / Linux(Wayland):** Unsupported. - #[inline] - pub fn set_outer_position>(&self, position: P) { - self.window.set_outer_position(position.into()) - } - - /// Returns the physical size of the window's client area. - /// - /// The client area is the content of the window, excluding the title bar and borders. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's - /// [safe area] in screen space coordinates. - /// - /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc - #[inline] - pub fn inner_size(&self) -> PhysicalSize { - self.window.inner_size() - } - - /// Modifies the inner size of the window. - /// - /// See `inner_size` for more information about the values. This automatically un-maximizes the - /// window if it's maximized. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_inner_size>(&self, size: S) { - self.window.set_inner_size(size.into()) - } - - /// Returns the physical size of the entire window. - /// - /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), - /// use `inner_size` instead. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in - /// screen space coordinates. - #[inline] - pub fn outer_size(&self) -> PhysicalSize { - self.window.outer_size() - } - - /// Sets a minimum dimension size for the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_min_inner_size>(&self, min_size: Option) { - self.window.set_min_inner_size(min_size.map(|s| s.into())) - } - - /// Sets a maximum dimension size for the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_max_inner_size>(&self, max_size: Option) { - self.window.set_max_inner_size(max_size.map(|s| s.into())) - } - - /// Sets inner size constraints for the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - self.window.set_inner_size_constraints(constraints) - } -} - -/// Misc. attribute functions. -impl Window { - /// Modifies the title of the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_title(&self, title: &str) { - self.window.set_title(title) - } - - /// Gets the current title of the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. Returns ane empty string. - #[inline] - pub fn title(&self) -> String { - self.window.title() - } - - /// Modifies the window's visibility. - /// - /// If `false`, this will hide the window. If `true`, this will show the window. - /// ## Platform-specific - /// - /// - **Android:** Unsupported. - /// - **iOS:** Can only be called on the main thread. - #[inline] - pub fn set_visible(&self, visible: bool) { - self.window.set_visible(visible) - } - - /// Bring the window to front and focus. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_focus(&self) { - self.window.set_focus() - } - - /// Sets whether the window is focusable or not. - /// - /// ## Platform-specific - /// - /// - **macOS**: If the window is already focused, it is not possible to unfocus it after calling `set_focusable(false)`. - /// In this case, you might consider calling [`Window::set_focus`] but it will move the window to the back i.e. at the bottom in terms of z-order. - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_focusable(&self, focusable: bool) { - self.window.set_focusable(focusable) - } - - /// Is window active and focused? - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_focused(&self) -> bool { - self.window.is_focused() - } - - /// Indicates whether the window is always on top of other windows. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_always_on_top(&self) -> bool { - self.window.is_always_on_top() - } - - /// Sets whether the window is resizable or not. - /// - /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be - /// triggered by DPI scaling, entering fullscreen mode, etc. - /// - /// ## Platform-specific - /// - /// This only has an effect on desktop platforms. - /// - /// Due to a bug in XFCE, this has no effect on Xfwm. - /// - /// ## Platform-specific - /// - /// - **Linux:** Most size methods like maximized are async and do not work well with calling - /// sequentailly. For setting inner or outer size, you don't need to set resizable to true before - /// it. It can resize no matter what. But if you insist to do so, it has a `100, 100` minimum - /// limitation somehow. For maximizing, it requires resizable is true. If you really want to set - /// resizable to false after it. You might need a mechanism to check the window is really - /// maximized. - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_resizable(&self, resizable: bool) { - self.window.set_resizable(resizable) - } - - /// Sets whether the window is minimizable or not. - /// - /// ## Platform-specific - /// - /// - **Linux / iOS / Android:** Unsupported. - #[inline] - pub fn set_minimizable(&self, minimizable: bool) { - self.window.set_minimizable(minimizable) - } - - /// Sets whether the window is maximizable or not. - /// - /// ## Platform-specific - /// - /// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode. - /// - **Linux / iOS / Android:** Unsupported. - #[inline] - pub fn set_maximizable(&self, maximizable: bool) { - self.window.set_maximizable(maximizable) - } - - /// Sets whether the window is closable or not. - /// - /// ## Platform-specific - /// - /// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button. - /// Depending on the system, this function may not have any effect when called on a window that is already visible" - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_closable(&self, closable: bool) { - self.window.set_closable(closable) - } - - /// Sets the window to minimized or back - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_minimized(&self, minimized: bool) { - self.window.set_minimized(minimized); - } - - /// Sets the window to maximized or back. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_maximized(&self, maximized: bool) { - self.window.set_maximized(maximized) - } - - /// Gets the window's current maximized state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_maximized(&self) -> bool { - self.window.is_maximized() - } - - /// Gets the window's current minimized state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_minimized(&self) -> bool { - self.window.is_minimized() - } - - /// Gets the window's current visibility state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_visible(&self) -> bool { - self.window.is_visible() - } - - /// Gets the window's current resizable state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_resizable(&self) -> bool { - self.window.is_resizable() - } - - /// Gets the window's current minimizable state. - /// - /// ## Platform-specific - /// - /// - **Linux / iOS / Android:** Unsupported. - #[inline] - pub fn is_minimizable(&self) -> bool { - self.window.is_minimizable() - } - - /// Gets the window's current maximizable state. - /// - /// ## Platform-specific - /// - /// - **Linux / iOS / Android:** Unsupported. - #[inline] - pub fn is_maximizable(&self) -> bool { - self.window.is_maximizable() - } - - /// Gets the window's current closable state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn is_closable(&self) -> bool { - self.window.is_closable() - } - - /// Gets the window's current decoration state. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - pub fn is_decorated(&self) -> bool { - self.window.is_decorated() - } - - /// Sets the window to fullscreen or back. - /// - /// ## Platform-specific - /// - /// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a - /// video mode change. *Caveat!* macOS doesn't provide task switching (or - /// spaces!) while in exclusive fullscreen mode. This mode should be used - /// when a video mode change is desired, but for a better user experience, - /// borderless fullscreen might be preferred. - /// - /// `Fullscreen::Borderless` provides a borderless fullscreen window on a - /// separate space. This is the idiomatic way for fullscreen games to work - /// on macOS. See `WindowExtMacOs::set_simple_fullscreen` if - /// separate spaces are not preferred. - /// - /// The dock and the menu bar are always disabled in fullscreen mode. - /// - **iOS:** Can only be called on the main thread. - /// - **Windows:** Screen saver is disabled in fullscreen mode. - /// - **Linux:** The window will only fullscreen to current monitor no matter which enum variant. - /// - **Android:** Unsupported. - #[inline] - pub fn set_fullscreen(&self, fullscreen: Option) { - self.window.set_fullscreen(fullscreen) - } - - /// Gets the window's current fullscreen state. - /// - /// ## Platform-specific - /// - /// - **iOS:** Can only be called on the main thread. - /// - **Android:** Will always return `None`. - #[inline] - pub fn fullscreen(&self) -> Option { - self.window.fullscreen() - } - - /// Turn window decorations on or off. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - /// - /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc - #[inline] - pub fn set_decorations(&self, decorations: bool) { - self.window.set_decorations(decorations) - } - - /// Change whether or not the window will always be below other windows. - /// - /// ## Platform-specific - /// - /// - **Windows**: There is no guarantee that the window will be the bottom most but it will try to be. - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland) / iOS / Android:** Unsupported. - // TODO: Unsupported in gtk4 - #[inline] - pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - self.window.set_always_on_bottom(always_on_bottom) - } - - /// Change whether or not the window will always be on top of other windows. - /// - /// ## Platform-specific - /// - /// - **Linux(x11):** Result depends on the system's window manager. Consider this setting a suggestion. - /// - **Linux(Wayland) / iOS / Android:** Unsupported. - // TODO: Unsupported in gtk4 - #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { - self.window.set_always_on_top(always_on_top) - } - - /// Sets the window icon. On Windows and Linux, this is typically the small icon in the top-left - /// corner of the title bar. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / macOS:** Unsupported. - /// - /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's - /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. - #[inline] - pub fn set_window_icon(&self, window_icon: Option) { - self.window.set_window_icon(window_icon) - } - - /// Sets location of IME candidate box in client area coordinates relative to the top left. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_ime_position>(&self, position: P) { - self.window.set_ime_position(position.into()) - } - - /// Sets the taskbar progress state. - /// - /// ## Platform-specific - /// - /// - **Linux / macOS**: Unlike windows, progress bar is app-wide and not specific to this window. Only supported desktop environments with `libunity` (e.g. GNOME). - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_progress_bar(&self, _progress: ProgressBarState) { - #[cfg(any( - windows, - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", - ))] - self.window.set_progress_bar(_progress) - } - - /// Requests user attention to the window, this has no effect if the application - /// is already focused. How requesting for user attention manifests is platform dependent, - /// see `UserAttentionType` for details. - /// - /// Providing `None` will unset the request for user attention. Unsetting the request for - /// user attention might not be done automatically by the WM when the window receives input. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - /// - **macOS:** `None` has no effect. - /// - **Linux:** Urgency levels have the same effect. - #[inline] - pub fn request_user_attention(&self, request_type: Option) { - self.window.request_user_attention(request_type) - } - - /// Returns the current window theme. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn theme(&self) -> Theme { - self.window.theme() - } - - /// Sets the theme for this window. - /// - /// ## Platform-specific - /// - /// - **Linux / macOS**: Theme is app-wide and not specific to this window. - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_theme(&self, #[allow(unused)] theme: Option) { - #[cfg(any( - windows, - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", - ))] - self.window.set_theme(theme) - } - - /// Prevents the window contents from being captured by other apps. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Linux:** Unsupported. - pub fn set_content_protection(&self, #[allow(unused)] enabled: bool) { - #[cfg(any(target_os = "macos", target_os = "windows"))] - self.window.set_content_protection(enabled); - } - - /// Sets whether the window should be visible on all workspaces. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Windows:** Unsupported. - pub fn set_visible_on_all_workspaces(&self, #[allow(unused)] visible: bool) { - #[cfg(any(target_os = "macos", target_os = "linux"))] - self.window.set_visible_on_all_workspaces(visible) - } - - /// Sets the window background color. - /// - /// ## Platform-specific: - /// - /// - **Windows:** alpha channel is ignored. Instead manually draw the window, for example using `softbuffer` crate, see - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_background_color(&self, color: Option) { - self.window.set_background_color(color) - } -} - -/// Cursor functions. -impl Window { - /// Modifies the cursor icon of the window. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - self.window.set_cursor_icon(cursor); - } - - /// Changes the position of the cursor in window coordinates. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. - #[inline] - pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { - self.window.set_cursor_position(position.into()) - } - - /// Grabs the cursor, preventing it from leaving the window. - /// - /// There's no guarantee that the cursor will be hidden. You should - /// hide it by yourself if you want so. - /// - /// ## Platform-specific - /// - /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - self.window.set_cursor_grab(grab) - } - - /// Modifies the cursor's visibility. - /// - /// If `false`, this will hide the cursor. If `true`, this will show the cursor. - /// - /// ## Platform-specific - /// - /// - **Windows:** The cursor is only hidden within the confines of the window. - /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is - /// outside of the window. - /// - **iOS / Android:** Unsupported. - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - self.window.set_cursor_visible(visible) - } - - /// Moves the window with the left mouse button until the button is released. - /// - /// There's no guarantee that this will work unless the left mouse button was pressed - /// immediately before this function is called. - /// - /// ## Platform-specific - /// - /// - **macOS:** May prevent the button release event to be triggered. - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. - #[inline] - pub fn drag_window(&self) -> Result<(), ExternalError> { - self.window.drag_window() - } - - /// Resizes the window with the left mouse button until the button is released. - /// - /// There's no guarantee that this will work unless the left mouse button was pressed - /// immediately before this function is called. - /// - /// ## Platform-specific - /// - /// - **macOS / iOS / Android:** Always returns an [`ExternalError::NotSupported`]. - #[inline] - pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { - self.window.drag_resize_window(direction) - } - - /// Modifies whether the window catches cursor events. - /// - /// If `true`, the events are passed through the window such that any other window behind it receives them. - /// If `false` the window will catch the cursor events. By default cursor events are not ignored. - /// - /// ## Platform-specific - /// - /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`] - #[inline] - pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - self.window.set_ignore_cursor_events(ignore) - } - - /// Returns the current cursor position - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Linux(Wayland)**: Unsupported, returns `0,0`. - #[inline] - pub fn cursor_position(&self) -> Result, ExternalError> { - self.window.cursor_position() - } -} - -/// Monitor info functions. -impl Window { - /// Returns the monitor on which the window currently resides. - /// - /// Returns `None` if current monitor can't be detected. - /// - /// ## Platform-specific - /// - /// **iOS:** Can only be called on the main thread. - #[inline] - pub fn current_monitor(&self) -> Option { - self.window.current_monitor() - } - - #[inline] - /// Returns the monitor that contains the given point. - /// - /// ## Platform-specific: - /// - /// - **Android / iOS:** Unsupported. - pub fn monitor_from_point(&self, x: f64, y: f64) -> Option { - self.window.monitor_from_point(x, y) - } - - /// Returns the list of all the monitors available on the system. - /// - /// This is the same as `EventLoopWindowTarget::available_monitors`, and is provided for convenience. - /// - /// ## Platform-specific - /// - /// **iOS:** Can only be called on the main thread. - #[inline] - pub fn available_monitors(&self) -> impl Iterator { - self - .window - .available_monitors() - .into_iter() - .map(|inner| MonitorHandle { inner }) - } - - /// Returns the primary monitor of the system. - /// - /// Returns `None` if it can't identify any monitor as a primary one. - /// - /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. - /// - /// ## Platform-specific - /// - /// **iOS:** Can only be called on the main thread. - #[inline] - pub fn primary_monitor(&self) -> Option { - self.window.primary_monitor() - } -} - -#[cfg(feature = "rwh_04")] -unsafe impl rwh_04::HasRawWindowHandle for Window { - fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { - self.window.raw_window_handle_rwh_04() - } -} - -#[cfg(feature = "rwh_05")] -unsafe impl rwh_05::HasRawWindowHandle for Window { - fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { - self.window.raw_window_handle_rwh_05() - } -} - -#[cfg(feature = "rwh_05")] -unsafe impl rwh_05::HasRawDisplayHandle for Window { - fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { - self.window.raw_display_handle_rwh_05() - } -} - -#[cfg(feature = "rwh_06")] -impl rwh_06::HasWindowHandle for Window { - fn window_handle(&self) -> Result, rwh_06::HandleError> { - let raw = self.window.raw_window_handle_rwh_06()?; - // SAFETY: The window handle will never be deallocated while the window is alive. - Ok(unsafe { rwh_06::WindowHandle::borrow_raw(raw) }) - } -} - -#[cfg(feature = "rwh_06")] -impl rwh_06::HasDisplayHandle for Window { - fn display_handle(&self) -> Result, rwh_06::HandleError> { - let raw = self.window.raw_display_handle_rwh_06()?; - // SAFETY: The window handle will never be deallocated while the window is alive. - Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) - } -} - -/// Describes the appearance of the mouse cursor. -#[non_exhaustive] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Default)] -pub enum CursorIcon { - /// The platform-dependent default cursor. - #[default] - Default, - /// A simple crosshair. - Crosshair, - /// A hand (often used to indicate links in web browsers). - Hand, - /// Self explanatory. - Arrow, - /// Indicates something is to be moved. - Move, - /// Indicates text that may be selected or edited. - Text, - /// Program busy indicator. - Wait, - /// Help indicator (often rendered as a "?") - Help, - /// Progress indicator. Shows that processing is being done. But in contrast - /// with "Wait" the user may still interact with the program. Often rendered - /// as a spinning beach ball, or an arrow with a watch or hourglass. - Progress, - - /// Cursor showing that something cannot be done. - NotAllowed, - ContextMenu, - Cell, - VerticalText, - Alias, - Copy, - NoDrop, - /// Indicates something can be grabbed. - Grab, - /// Indicates something is grabbed. - Grabbing, - AllScroll, - ZoomIn, - ZoomOut, - - /// Indicate that some edge is to be moved. For example, the 'SeResize' cursor - /// is used when the movement starts from the south-east corner of the box. - EResize, - NResize, - NeResize, - NwResize, - SResize, - SeResize, - SwResize, - WResize, - EwResize, - NsResize, - NeswResize, - NwseResize, - ColResize, - RowResize, -} - -/// Fullscreen modes. -#[non_exhaustive] -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, PartialEq)] -pub enum Fullscreen { - Exclusive(VideoMode), - - /// Providing `None` to `Borderless` will fullscreen on the current monitor. - Borderless(Option), -} - -#[non_exhaustive] -#[derive(Default, Clone, Copy, Debug, PartialEq)] -pub enum Theme { - #[default] - Light, - Dark, -} - -#[non_exhaustive] -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub enum UserAttentionType { - /// ## Platform-specific - /// - **macOS:** Bounces the dock icon until the application is in focus. - /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. - Critical, - /// ## Platform-specific - /// - **macOS:** Bounces the dock icon once. - /// - **Windows:** Flashes the taskbar button until the application is in focus. - #[default] - Informational, -} - -/// Window size constraints -#[derive(Clone, Copy, PartialEq, Debug, Default)] -pub struct WindowSizeConstraints { - /// The minimum width a window can be, If this is `None`, the window will have no minimum width (aside from reserved). - /// - /// The default is `None`. - pub min_width: Option, - /// The minimum height a window can be, If this is `None`, the window will have no minimum height (aside from reserved). - /// - /// The default is `None`. - pub min_height: Option, - /// The maximum width a window can be, If this is `None`, the window will have no maximum width (aside from reserved). - /// - /// The default is `None`. - pub max_width: Option, - /// The maximum height a window can be, If this is `None`, the window will have no maximum height (aside from reserved). - /// - /// The default is `None`. - pub max_height: Option, -} - -impl WindowSizeConstraints { - pub fn new( - min_width: Option, - min_height: Option, - max_width: Option, - max_height: Option, - ) -> Self { - Self { - min_width, - min_height, - max_width, - max_height, - } - } - - /// Returns true if `min_width` or `min_height` is set. - pub fn has_min(&self) -> bool { - self.min_width.is_some() || self.min_height.is_some() - } - /// Returns true if `max_width` or `max_height` is set. - pub fn has_max(&self) -> bool { - self.max_width.is_some() || self.max_height.is_some() - } - - /// Returns a physical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values - pub fn min_size_physical(&self, scale_factor: f64) -> PhysicalSize { - PhysicalSize::new( - self - .min_width - .unwrap_or(PixelUnit::MIN) - .to_physical(scale_factor) - .0, - self - .min_height - .unwrap_or(PixelUnit::MIN) - .to_physical(scale_factor) - .0, - ) - } - - /// Returns a logical size that represents the minimum constraints set and fallbacks to [`PixelUnit::MIN`] for unset values - pub fn min_size_logical(&self, scale_factor: f64) -> LogicalSize { - LogicalSize::new( - self - .min_width - .unwrap_or(PixelUnit::MIN) - .to_logical(scale_factor) - .0, - self - .min_height - .unwrap_or(PixelUnit::MIN) - .to_logical(scale_factor) - .0, - ) - } - - /// Returns a physical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values - pub fn max_size_physical(&self, scale_factor: f64) -> PhysicalSize { - PhysicalSize::new( - self - .max_width - .unwrap_or(PixelUnit::MAX) - .to_physical(scale_factor) - .0, - self - .max_height - .unwrap_or(PixelUnit::MAX) - .to_physical(scale_factor) - .0, - ) - } - - /// Returns a logical size that represents the maximum constraints set and fallbacks to [`PixelUnit::MAX`] for unset values - pub fn max_size_logical(&self, scale_factor: f64) -> LogicalSize { - LogicalSize::new( - self - .max_width - .unwrap_or(PixelUnit::MAX) - .to_logical(scale_factor) - .0, - self - .max_height - .unwrap_or(PixelUnit::MAX) - .to_logical(scale_factor) - .0, - ) - } - - /// Clamps the desired size based on the constraints set - pub fn clamp(&self, desired_size: Size, scale_factor: f64) -> Size { - let min_size: PhysicalSize = self.min_size_physical(scale_factor); - let max_size: PhysicalSize = self.max_size_physical(scale_factor); - Size::clamp(desired_size, min_size.into(), max_size.into(), scale_factor) - } -} - -/// Defines the orientation that a window resize will be performed. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum ResizeDirection { - East, - North, - NorthEast, - NorthWest, - South, - SouthEast, - SouthWest, - West, -} - -pub(crate) fn hit_test( - (left, top, right, bottom): (i32, i32, i32, i32), - cx: i32, - cy: i32, - border_x: i32, - border_y: i32, -) -> Option { - const LEFT: isize = 0b0001; - const RIGHT: isize = 0b0010; - const TOP: isize = 0b0100; - const BOTTOM: isize = 0b1000; - const TOPLEFT: isize = TOP | LEFT; - const TOPRIGHT: isize = TOP | RIGHT; - const BOTTOMLEFT: isize = BOTTOM | LEFT; - const BOTTOMRIGHT: isize = BOTTOM | RIGHT; - - #[rustfmt::skip] - let result = (LEFT * (cx < left + border_x) as isize) - | (RIGHT * (cx >= right - border_x) as isize) - | (TOP * (cy < top + border_y) as isize) - | (BOTTOM * (cy >= bottom - border_y) as isize); - - match result { - LEFT => Some(ResizeDirection::West), - RIGHT => Some(ResizeDirection::East), - TOP => Some(ResizeDirection::North), - BOTTOM => Some(ResizeDirection::South), - TOPLEFT => Some(ResizeDirection::NorthWest), - TOPRIGHT => Some(ResizeDirection::NorthEast), - BOTTOMLEFT => Some(ResizeDirection::SouthWest), - BOTTOMRIGHT => Some(ResizeDirection::SouthEast), - _ => None, - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore new file mode 100644 index 000000000..5922fdaa5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift new file mode 100644 index 000000000..43417f18c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version:5.3 +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "Tauri", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Tauri", + type: .static, + targets: ["Tauri"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "SwiftRs", url: "https://github.com/Brendonovich/swift-rs", from: "1.0.9") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "Tauri", + dependencies: [ + .byName(name: "SwiftRs") + ], + path: "Sources" + ), + .testTarget( + name: "TauriTests", + dependencies: ["Tauri"] + ), + ] +) diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md new file mode 100644 index 000000000..52c3f1c7f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md @@ -0,0 +1,3 @@ +# Tauri + +Tauri iOS API. diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift new file mode 100644 index 000000000..add065c74 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift @@ -0,0 +1,65 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +let CHANNEL_PREFIX = "__CHANNEL__:" +let channelDataKey = CodingUserInfoKey(rawValue: "sendChannelData")! + +public class Channel: Decodable { + public let id: UInt64 + let handler: (UInt64, String) -> Void + + public required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let channelDef = try container.decode(String.self) + + let components = channelDef.components(separatedBy: CHANNEL_PREFIX) + if components.count < 2 { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel definition from \(channelDef)" + ) + + } + guard let channelId = UInt64(components[1]) else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel ID from \(channelDef)" + ) + } + + guard let handler = decoder.userInfo[channelDataKey] as? (UInt64, String) -> Void else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "missing userInfo for Channel handler. This is a Tauri issue" + ) + } + + self.id = channelId + self.handler = handler + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func send(_ data: JsonObject) { + send(.dictionary(data)) + } + + public func send(_ data: JsonValue) { + handler(id, serialize(data)) + } + + public func send(_ data: T) throws { + let json = try JSONEncoder().encode(data) + handler(id, String(decoding: json, as: UTF8.self)) + } + +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift new file mode 100644 index 000000000..6f810868f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift @@ -0,0 +1,118 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import UIKit + +@objc public class Invoke: NSObject { + public let command: String + let callback: UInt64 + let error: UInt64 + let data: String + let sendResponse: (UInt64, String?) -> Void + let sendChannelData: (UInt64, String) -> Void + + public init( + command: String, callback: UInt64, error: UInt64, + sendResponse: @escaping (UInt64, String?) -> Void, + sendChannelData: @escaping (UInt64, String) -> Void, data: String + ) { + self.command = command + self.callback = callback + self.error = error + self.data = data + self.sendResponse = sendResponse + self.sendChannelData = sendChannelData + } + + public func getRawArgs() -> String { + return self.data + } + + public func getArgs() throws -> JSObject { + let jsonData = self.data.data(using: .utf8)! + let data = try JSONSerialization.jsonObject(with: jsonData, options: []) + return JSTypes.coerceDictionaryToJSObject( + (data as! NSDictionary), formattingDatesAsStrings: true)! + } + + public func parseArgs(_ type: T.Type) throws -> T { + let jsonData = self.data.data(using: .utf8)! + let decoder = JSONDecoder() + decoder.userInfo[channelDataKey] = sendChannelData + return try decoder.decode(type, from: jsonData) + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func resolve() { + sendResponse(callback, nil) + } + + public func resolve(_ data: JsonObject) { + resolve(.dictionary(data)) + } + + public func resolve(_ data: JsonValue) { + sendResponse(callback, serialize(data)) + } + + public func resolve(_ data: T) { + do { + let json = try JSONEncoder().encode(data) + sendResponse(callback, String(decoding: json, as: UTF8.self)) + } catch { + sendResponse(self.error, "\"\(error)\"") + } + } + + public func reject( + _ message: String, code: String? = nil, error: Error? = nil, data: JsonValue? = nil + ) { + let payload: NSMutableDictionary = [ + "message": message + ] + + if let code = code { + payload["code"] = code + } + + if let error = error { + payload["error"] = error + } + + if let data = data { + switch data { + case .dictionary(let dict): + for entry in dict { + payload[entry.key] = entry.value + } + } + } + + sendResponse(self.error, serialize(.dictionary(payload as! JsonObject))) + } + + public func unimplemented() { + unimplemented("not implemented") + } + + public func unimplemented(_ message: String) { + reject(message) + } + + public func unavailable() { + unavailable("not available") + } + + public func unavailable(_ message: String) { + reject(message) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift new file mode 100644 index 000000000..0ac9f2c79 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift @@ -0,0 +1,123 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +// declare our empty protocol, and conformance, for typing +public protocol JSValue {} +extension String: JSValue {} +extension Bool: JSValue {} +extension Int: JSValue {} +extension Float: JSValue {} +extension Double: JSValue {} +extension NSNumber: JSValue {} +extension NSNull: JSValue {} +extension Array: JSValue {} +extension Date: JSValue {} +extension Dictionary: JSValue where Key == String, Value == JSValue {} + +// convenience aliases +public typealias JSObject = [String: JSValue] +public typealias JSArray = [JSValue] + +extension Dictionary where Key == String, Value == JSValue { + public func getValue(_ key: String) -> JSValue? { + return self[key] + } + + public func getString(_ key: String) -> String? { + return self[key] as? String + } + + public func getBool(_ key: String) -> Bool? { + return self[key] as? Bool + } + + public func getInt(_ key: String) -> Int? { + return self[key] as? Int + } + + public func getFloat(_ key: String) -> Float? { + if let floatValue = self[key] as? Float { + return floatValue + } else if let doubleValue = self[key] as? Double { + return Float(doubleValue) + } + return nil + } + + public func getDouble(_ key: String) -> Double? { + return self[key] as? Double + } + + public func getArray(_ key: String) -> JSArray? { + return self[key] as? JSArray + } + + public func getObject(_ key: String) -> JSObject? { + return self[key] as? JSObject + } +} + +/* + Simply casting objects from foundation class clusters (such as __NSArrayM) + doesn't work with the JSValue protocol and will always fail. So we need to + recursively and explicitly convert each value in the dictionary. + */ +public enum JSTypes {} +extension JSTypes { + public static func coerceDictionaryToJSObject( + _ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceDictionaryToJSObject( + _ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } +} + +private let dateStringFormatter = ISO8601DateFormatter() + +// We need a large switch statement because we have a lot of types. +// swiftlint:disable:next cyclomatic_complexity +private func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let numberValue as NSNumber: + return numberValue + case let boolValue as Bool: + return boolValue + case let intValue as Int: + return intValue + case let floatValue as Float: + return floatValue + case let doubleValue as Double: + return doubleValue + case let dateValue as Date: + if formattingDates { + return dateStringFormatter.string(from: dateValue) + } + return dateValue + case let nullValue as NSNull: + return nullValue + case let arrayValue as NSArray: + return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) } + case let dictionaryValue as NSDictionary: + let keys = dictionaryValue.allKeys.compactMap { $0 as? String } + var result: JSObject = [:] + for key in keys { + result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates) + } + return result + default: + return nil + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift new file mode 100644 index 000000000..ae6d96f77 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +public typealias JsonObject = [String: Any?] + +public enum JsonValue { + case dictionary(JsonObject) + + enum SerializationError: Error { + case invalidObject + } + + public func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? { + switch self { + case .dictionary(var dictionary): + if let fields = includingFields { + dictionary.merge(fields) { (current, _) in current } + } + dictionary = prepare(dictionary: dictionary) + guard JSONSerialization.isValidJSONObject(dictionary) else { + throw SerializationError.invalidObject + } + let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + return String(data: data, encoding: .utf8) + } + } + + private static let formatter = ISO8601DateFormatter() + + private func prepare(dictionary: JsonObject) -> JsonObject { + return dictionary.mapValues { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } + + private func prepare(array: [Any]) -> [Any] { + return array.map { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift new file mode 100644 index 000000000..d055090dc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift @@ -0,0 +1,139 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import os.log +import UIKit +import Foundation + +class StdoutRedirector { + private var originalStdout: Int32 = -1 + private var originalStderr: Int32 = -1 + private var stdoutPipe: [Int32] = [-1, -1] + private var stderrPipe: [Int32] = [-1, -1] + private var stdoutReadSource: DispatchSourceRead? + private var stderrReadSource: DispatchSourceRead? + + func start() { + originalStdout = dup(STDOUT_FILENO) + originalStderr = dup(STDERR_FILENO) + + guard Darwin.pipe(&stdoutPipe) == 0, + Darwin.pipe(&stderrPipe) == 0 else { + Logger.error("Failed to create stdout/stderr pipes") + return + } + + dup2(stdoutPipe[1], STDOUT_FILENO) + dup2(stderrPipe[1], STDERR_FILENO) + close(stdoutPipe[1]) + close(stderrPipe[1]) + + stdoutReadSource = createReader( + readPipe: stdoutPipe[0], + writeToOriginal: originalStdout, + label: "stdout" + ) + + stderrReadSource = createReader( + readPipe: stderrPipe[0], + writeToOriginal: originalStderr, + label: "stderr" + ) + } + + private func createReader( + readPipe: Int32, + writeToOriginal: Int32, + label: String + ) -> DispatchSourceRead { + let source = DispatchSource.makeReadSource( + fileDescriptor: readPipe, + queue: .global(qos: .utility) + ) + + source.setEventHandler { + let bufferSize = 4096 + var buffer = [UInt8](repeating: 0, count: bufferSize) + let bytesRead = read(readPipe, &buffer, bufferSize) + + if bytesRead > 0 { + let output = String( + bytes: buffer[0..(_ type: T.Type) throws -> T { + let jsonData = self.config.data(using: .utf8)! + let decoder = JSONDecoder() + return try decoder.decode(type, from: jsonData) + } + + @objc open func load(webview: WKWebView) {} + + @objc open func checkPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + @objc open func requestPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + public func trigger(_ event: String, data: JSObject) { + if let eventListeners = listeners[event] { + for channel in eventListeners { + channel.send(data) + } + } + } + + public func trigger(_ event: String, data: T) throws { + if let eventListeners = listeners[event] { + for channel in eventListeners { + try channel.send(data) + } + } + } + + @objc func registerListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RegisterListenerArgs.self) + + if var eventListeners = listeners[args.event] { + eventListeners.append(args.handler) + listeners[args.event] = eventListeners + } else { + listeners[args.event] = [args.handler] + } + + invoke.resolve() + } + + @objc func removeListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RemoveListenerArgs.self) + + if let eventListeners = listeners[args.event] { + + listeners[args.event] = eventListeners.filter { $0.id != args.channelId } + } + + invoke.resolve() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift new file mode 100644 index 000000000..3404dacfa --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift @@ -0,0 +1,157 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import SwiftRs +import UIKit +import WebKit +import os.log + +class PluginHandle { + var instance: Plugin + var loaded = false + + init(plugin: Plugin) { + instance = plugin + } +} + +public class PluginManager { + static let shared: PluginManager = PluginManager() + public var viewController: UIViewController? + var plugins: [String: PluginHandle] = [:] + var ipcDispatchQueue = DispatchQueue(label: "ipc") + public var isSimEnvironment: Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } + + public func assetUrl(fromLocalURL url: URL?) -> URL? { + guard let inputURL = url else { + return nil + } + + return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path) + } + + func onWebviewCreated(_ webview: WKWebView) { + for (_, handle) in plugins { + if !handle.loaded { + handle.instance.load(webview: webview) + } + } + } + + func load(name: String, plugin: P, config: String, webview: WKWebView?) { + plugin.setConfig(config) + let handle = PluginHandle(plugin: plugin) + if let webview = webview { + handle.instance.load(webview: webview) + handle.loaded = true + } + plugins[name] = handle + } + + func invoke(name: String, invoke: Invoke) { + if let plugin = plugins[name] { + ipcDispatchQueue.async { + let selectorWithCompletionHandler = Selector(("\(invoke.command):completionHandler:")) + let selectorWithThrows = Selector(("\(invoke.command):error:")) + + if plugin.instance.responds(to: selectorWithCompletionHandler) { + let completion: @convention(block) (NSError?) -> Void = { error in + if let error = error { + invoke.reject("\(error)") + } + } + + let blockObj: AnyObject = unsafeBitCast(completion, to: AnyObject.self) + let imp = plugin.instance.method(for: selectorWithCompletionHandler) + + typealias Fn = @convention(c) (AnyObject, Selector, Invoke, AnyObject) -> Void + let fn = unsafeBitCast(imp, to: Fn.self) + fn(plugin.instance, selectorWithCompletionHandler, invoke, blockObj) + } else if plugin.instance.responds(to: selectorWithThrows) { + var error: NSError? = nil + withUnsafeMutablePointer(to: &error) { + let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows) + unsafeBitCast( + methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)( + plugin.instance, selectorWithThrows, invoke, OpaquePointer($0)) + } + if let error = error { + invoke.reject("\(error)") + // TODO: app crashes without this leak + let _ = Unmanaged.passRetained(error) + } + } else { + let selector = Selector(("\(invoke.command):")) + if plugin.instance.responds(to: selector) { + plugin.instance.perform(selector, with: invoke) + } else { + invoke.reject("No command \(invoke.command) found for plugin \(name)") + } + } + } + } else { + invoke.reject("Plugin \(name) not initialized") + } + } +} + +extension PluginManager: NSCopying { + public func copy(with zone: NSZone? = nil) -> Any { + return self + } +} + +private var stdoutRedirector: StdoutRedirector? + +@_cdecl("log_stdout") +public func logStdout() { + stdoutRedirector = StdoutRedirector() + stdoutRedirector!.start() +} + +@_cdecl("register_plugin") +public func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) { + PluginManager.shared.load( + name: name.toString(), + plugin: plugin as! Plugin, + config: config.toString(), + webview: webview + ) +} + +@_cdecl("on_webview_created") +public func onWebviewCreated(webview: WKWebView, viewController: UIViewController) { + PluginManager.shared.viewController = viewController + PluginManager.shared.onWebviewCreated(webview) +} + +@_cdecl("run_plugin_command") +public func runCommand( + id: Int, + name: SRString, + command: SRString, + data: SRString, + callback: @escaping @convention(c) (Int, Bool, UnsafePointer) -> Void, + sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer) -> Void +) { + let callbackId: UInt64 = 0 + let errorId: UInt64 = 1 + let invoke = Invoke( + command: command.toString(), callback: callbackId, error: errorId, + sendResponse: { (fn: UInt64, payload: String?) -> Void in + let success = fn == callbackId + callback(id, success, payload ?? "null") + }, + sendChannelData: { (id: UInt64, payload: String) -> Void in + sendChannelData(id, payload) + }, data: data.toString()) + PluginManager.shared.invoke(name: name.toString(), invoke: invoke) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift new file mode 100644 index 000000000..4097596e2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift @@ -0,0 +1,15 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit + +public class UIUtils { + public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) { + if let viewController = rootViewController { + popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0) + popoverController.popoverPresentationController?.sourceView = viewController.view + popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved new file mode 100644 index 000000000..7b82ebddb --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "207e4bfe5d36fdd0cc08f28d1849ff94ac7e24b8", + "version": "1.0.9" + } + } + ] + }, + "version": 1 +} From ddae0f83b66371e4cc645511dd8fd5a54e5b9514 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:21:12 +0000 Subject: [PATCH 03/14] research: carry the Android intent hardening onto tao 0.37.1 tauri-apps/tao#1217 was closed by #1224, which only fixed the getType() null case. tao 0.37 still unwraps getIntent/getAction and the ClipData and EXTRA_STREAM conversions, so a null launch intent aborts the process across JNI before tauri-plugin-deep-link sees OAuth or payment redirects. Re-apply Maple's 0.35.2 patch minus the hunk upstream now covers; checked with cargo check --target aarch64-linux-android. Co-Authored-By: Claude Opus 5.5 --- .../src/platform_impl/android/ndk_glue.rs | 100 ++++++++++++------ 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs index 765457e68..2ad139400 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs @@ -394,7 +394,7 @@ pub unsafe fn onCreate( setup: unsafe fn(&str, JNIEnv, &ThreadLooper, GlobalRef), ) { let intent = - jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).unwrap(); + jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).ok(); let vm = env.get_java_vm().unwrap(); @@ -469,7 +469,9 @@ pub unsafe fn onCreate( let _ = tx.send(()); } - handle_intent(env, intent); + if let Some(intent) = intent { + handle_intent(env, intent); + } } #[allow(non_snake_case)] @@ -518,14 +520,27 @@ pub unsafe fn onNewIntent(env: JNIEnv, _: JClass, intent: JObject) { } pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { - let action = jni_call_method!(env, &intent, "getAction", "()Ljava/lang/String;", l) - .unwrap() - .into(); - let action = env - .get_string(&action) - .map(|action| action.to_string_lossy().to_string()); + // Maple local patch for https://github.com/tauri-apps/tao/issues/1217. + // + // Android can provide null intent/action/type objects during cold launch, + // task restoration, and OEM deep-link delivery. Upstream tao (still in 0.37) + // unwraps these JNI calls, which aborts the process across the Rust + // JNI boundary. Treat malformed intent pieces as absent and return/continue. + if intent.is_null() { + return; + } - let Ok(action) = action else { + let Ok(action) = jni_call_method!(env, &intent, "getAction", "()Ljava/lang/String;", l) else { + return; + }; + if action.is_null() { + return; + } + let action: JString = action.into(); + let Ok(action) = env + .get_string(&action) + .map(|action| action.to_string_lossy().to_string()) + else { return; }; @@ -604,30 +619,39 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { // getItemCount may return null if the intent has only a single uri in which case the uri can be received by getData|String instead. if let Ok(item_count) = jni_call_method!(env, &clip_data, "getItemCount", "()I", i) { for i in 0..item_count { - let clip_item = jni_call_method!( + let Ok(clip_item) = jni_call_method!( env, &clip_data, "getItemAt", "(I)Landroid/content/ClipData$Item;", &[i.into()], l - ) - .unwrap(); + ) else { + continue; + }; + if clip_item.is_null() { + continue; + } let uri = jni_call_method!(env, &clip_item, "getUri", "()Landroid/net/Uri;", l) .ok() .filter(|uri| !uri.is_null()); if let Some(uri) = uri { - let uri_string: JString = - jni_call_method!(env, &uri, "toString", "()Ljava/lang/String;", l) - .unwrap() - .into(); - let uri_str = env + let Ok(uri_string) = jni_call_method!(env, &uri, "toString", "()Ljava/lang/String;", l) + else { + continue; + }; + if uri_string.is_null() { + continue; + } + let uri_string: JString = uri_string.into(); + let Ok(uri_str) = env .get_string(&uri_string) - .unwrap() - .to_string_lossy() - .to_string(); + .map(|uri_str| uri_str.to_string_lossy().to_string()) + else { + continue; + }; if let Ok(url) = url::Url::parse(&uri_str) { urls.insert(url); } else { @@ -657,19 +681,29 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { .filter(|extra_stream| !extra_stream.is_null()); if let Some(stream_uri) = extra_stream { - let uri_string: JString = - jni_call_method!(env, &stream_uri, "toString", "()Ljava/lang/String;", l) - .unwrap() - .into(); - let uri_str = env - .get_string(&uri_string) - .unwrap() - .to_string_lossy() - .to_string(); - if let Ok(url) = url::Url::parse(&uri_str) { - urls.insert(url); - } else { - log::error!("failed to parse URI: {}", uri_str); + let uri_str = jni_call_method!( + env, + &stream_uri, + "toString", + "()Ljava/lang/String;", + l + ) + .ok() + .filter(|uri_string| !uri_string.is_null()) + .and_then(|uri_string| { + let uri_string: JString = uri_string.into(); + env + .get_string(&uri_string) + .ok() + .map(|uri_str| uri_str.to_string_lossy().to_string()) + }); + + if let Some(uri_str) = uri_str { + if let Ok(url) = url::Url::parse(&uri_str) { + urls.insert(url); + } else { + log::error!("failed to parse URI: {}", uri_str); + } } } } From 95921de4ff1ace582ef94864e43afb62d448c976 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:37:01 +0000 Subject: [PATCH 04/14] ci: keep Linux AppImage bundling on the pinned linuxdeploy tauri-cli 2.12 looks for target/.tauri/linuxdeploy-07333c6-.AppImage instead of linuxdeploy-.AppImage. It no longer found the pinned wrapper, so it downloaded an unpinned linuxdeploy and ran it with its own embedded gtk plugin. That plugin sources /etc/os-release, which the Nix /usr bind leaves dangling, and the AppImage step failed. Install the wrapper under the new name. tauri-cli now rewrites its embedded plugin scripts in the cache on every build, so stop writing Maple's copies there; the wrapper never runs them. Co-Authored-By: Claude Opus 5.5 --- scripts/ci/_common.sh | 23 ++++++++++++++++------- scripts/ci/desktop-pr.sh | 2 +- scripts/ci/desktop-release.sh | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/scripts/ci/_common.sh b/scripts/ci/_common.sh index f9886112a..35adba06e 100755 --- a/scripts/ci/_common.sh +++ b/scripts/ci/_common.sh @@ -1257,6 +1257,16 @@ linuxdeploy_tools_arch() { esac } +# tauri-cli runs target/.tauri/linuxdeploy--.AppImage and downloads +# an unpinned linuxdeploy when that file is missing, so the pinned wrapper is +# installed under that name. The hash is LINUXDEPLOY_COMMIT_HASH in the +# tauri-bundler that @tauri-apps/cli ships; update it with the CLI. +tauri_linuxdeploy_wrapper() { + local arch + arch="$(linuxdeploy_tools_arch)" || return 1 + printf '%s\n' "${TAURI_DIR}/target/.tauri/linuxdeploy-07333c6-${arch}.AppImage" +} + write_chmod_patchelf_wrapper() { local wrapper="$1" local real_patchelf="$2" @@ -1441,7 +1451,7 @@ prepare_tauri_linuxdeploy_tools_cache() { tools="${MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS}" cache="${TAURI_DIR}/target/.tauri" internal="${cache}/maple-linuxdeploy-tools" - linuxdeploy_wrapper="${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" + linuxdeploy_wrapper="$(tauri_linuxdeploy_wrapper)" appimage_wrapper="${cache}/linuxdeploy-plugin-appimage.AppImage" linuxdeploy_appdir_bin="${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/usr/bin" plugin_bin="${internal}/plugins" @@ -1470,8 +1480,9 @@ prepare_tauri_linuxdeploy_tools_cache() { wrap_linuxdeploy_appdir_patchelf "${linuxdeploy_appdir_bin}" rm -f "${linuxdeploy_appdir_bin}"/linuxdeploy-plugin-* - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gtk.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gtk.real.sh" - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gstreamer.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gstreamer.real.sh" + # tauri-cli also writes its own embedded linuxdeploy-plugin-*.sh into the + # cache. The wrapper replaces PATH and unsets APPIMAGE, so linuxdeploy only + # finds the pinned plugins below. write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gtk" "../linuxdeploy-plugin-gtk.real.sh" write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gstreamer" "../linuxdeploy-plugin-gstreamer.real.sh" @@ -1809,7 +1820,7 @@ EOF print_file_hashes \ "${cache}/AppRun-${arch}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" \ - "${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" \ + "${linuxdeploy_wrapper}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/AppRun" \ "${plugin_bin}/linuxdeploy-plugin-appimage" \ "${plugin_bin}/linuxdeploy-plugin-gtk" \ @@ -1817,9 +1828,7 @@ EOF "${appimage_real_appimage}" \ "${cache}/linuxdeploy-plugin-appimage.AppImage" \ "${appimage_appdir}/AppRun" \ - "${runtime_file}" \ - "${cache}/linuxdeploy-plugin-gtk.sh" \ - "${cache}/linuxdeploy-plugin-gstreamer.sh" + "${runtime_file}" } verify_linuxdeploy_plugin_metadata() { diff --git a/scripts/ci/desktop-pr.sh b/scripts/ci/desktop-pr.sh index 9b10f1da1..b37959349 100755 --- a/scripts/ci/desktop-pr.sh +++ b/scripts/ci/desktop-pr.sh @@ -23,7 +23,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins run_with_nix_usr_bin pkg-config --modversion glib-2.0 remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" diff --git a/scripts/ci/desktop-release.sh b/scripts/ci/desktop-release.sh index b20a0d97c..fa82c404a 100755 --- a/scripts/ci/desktop-release.sh +++ b/scripts/ci/desktop-release.sh @@ -41,7 +41,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" From eac93ff23eb955917da5979fd8dcfb91e0b62f6e Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:48:30 +0000 Subject: [PATCH 05/14] research(ios): adopt the UIScene life cycle Apps built with the iOS 27 SDK must adopt the scene-based life cycle or UIKit aborts at launch (_UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption). tauri-cli merges src-tauri/Info.ios.plist into the generated iOS plist, so the manifest stays out of the shared Info.plist that macOS also uses. One scene only: tao 0.37 creates a single WebView window, and any extra scene iPad or iPhone Duo opened would have no WebView and render black. The delegate is tao's TaoSceneDelegate. iOS 26 SDK builds behave the same with the manifest, so this lands before the Xcode 27.1 bump. Co-Authored-By: Claude Opus 5.5 --- .../frontend/src-tauri/Info.ios.plist | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 apps/maple-research/frontend/src-tauri/Info.ios.plist diff --git a/apps/maple-research/frontend/src-tauri/Info.ios.plist b/apps/maple-research/frontend/src-tauri/Info.ios.plist new file mode 100644 index 000000000..da3db42d6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/Info.ios.plist @@ -0,0 +1,25 @@ + + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + TaoSceneDelegate + + + + + + From b373057556403a0405625f889e765e58d28abbc4 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:49:52 +0000 Subject: [PATCH 06/14] research: vendor tauri-plugin-sign-in-with-apple 1.0.2 unmodified Patch it from an in-tree copy so the next commit can fix its Swift entry point for Xcode 27. The copy omits the crate's pnpm lockfile, the Gradle cache files it ships and its build-generated permission files, which are ignored the same way as in plugins/tauri-plugin-iap. Co-Authored-By: Claude Opus 5.5 --- .../frontend/src-tauri/Cargo.lock | 2 - .../frontend/src-tauri/Cargo.toml | 6 + .../.cargo-ok | 1 + .../.gitignore | 8 + .../Cargo.toml | 49 +++ .../Cargo.toml.orig | 18 + .../LICENSE | 7 + .../README.md | 13 + .../android/.gitignore | 2 + .../android/build.gradle.kts | 44 +++ .../android/proguard-rules.pro | 21 ++ .../android/settings.gradle | 31 ++ .../java/ExampleInstrumentedTest.kt | 24 ++ .../android/src/main/AndroidManifest.xml | 3 + .../android/src/main/java/Example.kt | 10 + .../android/src/main/java/ExamplePlugin.kt | 28 ++ .../android/src/test/java/ExampleUnitTest.kt | 17 + .../build.rs | 8 + .../guest-js/index.ts | 31 ++ .../ios/.gitignore | 10 + .../ios/Package.swift | 32 ++ .../ios/README.md | 3 + ...gnInWithAppleAuthorizationController.swift | 82 +++++ .../ios/Sources/SignInWithApplePlugin.swift | 58 ++++ .../ios/Tests/PluginTests/PluginTests.swift | 8 + .../package.json | 34 ++ .../permissions/default.toml | 3 + .../permissions/schemas/schema.json | 318 ++++++++++++++++++ .../rollup.config.js | 31 ++ .../src/commands.rs | 13 + .../src/desktop.rs | 20 ++ .../src/error.rs | 23 ++ .../src/lib.rs | 48 +++ .../src/mobile.rs | 34 ++ .../src/models.rs | 36 ++ .../tsconfig.json | 14 + 36 files changed, 1088 insertions(+), 2 deletions(-) create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs create mode 100644 apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json diff --git a/apps/maple-research/frontend/src-tauri/Cargo.lock b/apps/maple-research/frontend/src-tauri/Cargo.lock index faa29bc76..bd183ab4f 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.lock +++ b/apps/maple-research/frontend/src-tauri/Cargo.lock @@ -8525,8 +8525,6 @@ dependencies = [ [[package]] name = "tauri-plugin-sign-in-with-apple" version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9402eb8a70fdff5127fccc1f1666ebb06b5efc10e0eb656d8a70c798e26142e" dependencies = [ "serde", "tauri", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index 7da870c2a..d9042e412 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -129,3 +129,9 @@ maple-sdk = { path = "../../../../sdk/rust" } # The vendored library uses this application's Cargo.lock; its upstream # standalone lockfile is intentionally omitted. tao = { path = "patches/tao-0.37.1" } +# tauri-plugin-sign-in-with-apple 1.0.2 (last release 2024-11) declares its Swift +# entry point `@_cdecl("init_plugin_sign_in_with_apple") func initPlugin()` with +# internal visibility. Xcode 27 release builds need these entry points `public` +# to link (Tauri 2.12.3 made its own public: tauri-apps/tauri#16130); the +# vendored copy only adds that keyword. Remove once upstream publishes the fix. +tauri-plugin-sign-in-with-apple = { path = "patches/tauri-plugin-sign-in-with-apple-1.0.2" } diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok new file mode 100644 index 000000000..5f8b79583 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore new file mode 100644 index 000000000..d6789ca54 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore @@ -0,0 +1,8 @@ +/.tauri/ +/ios/.build/ +/ios/Package.resolved +/target/ +/Cargo.lock +/permissions/schemas/ +/permissions/autogenerated/ +/android/.gradle/ diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml new file mode 100644 index 000000000..df1bb1bed --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml @@ -0,0 +1,49 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.77.2" +name = "tauri-plugin-sign-in-with-apple" +version = "1.0.2" +authors = ["Manaf941"] +build = "build.rs" +links = "tauri-plugin-sign-in-with-apple" +exclude = [ + "/examples", + "/webview-dist", + "/webview-src", + "/node_modules", +] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Add Sign In With Apple capabilities to your Tauri iOS App." +readme = "README.md" +license = "MIT" + +[lib] +name = "tauri_plugin_sign_in_with_apple" +path = "src/lib.rs" + +[dependencies.serde] +version = "1.0" + +[dependencies.tauri] +version = "2.1.0" + +[dependencies.thiserror] +version = "2" + +[build-dependencies.tauri-plugin] +version = "2.0.2" +features = ["build"] diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig new file mode 100644 index 000000000..991605794 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig @@ -0,0 +1,18 @@ +[package] +name = "tauri-plugin-sign-in-with-apple" +version = "1.0.2" +authors = [ "Manaf941" ] +description = "Add Sign In With Apple capabilities to your Tauri iOS App." +license = "MIT" +edition = "2021" +rust-version = "1.77.2" +exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"] +links = "tauri-plugin-sign-in-with-apple" + +[dependencies] +tauri = { version = "2.1.0" } +serde = "1.0" +thiserror = "2" + +[build-dependencies] +tauri-plugin = { version = "2.0.2", features = ["build"] } diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE new file mode 100644 index 000000000..8cb345be2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE @@ -0,0 +1,7 @@ +Copyright 2024 Manaf Mhamdi Alaoui + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md new file mode 100644 index 000000000..792e168d1 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md @@ -0,0 +1,13 @@ +# Tauri Plugin sign_in_with_apple + +probably unstable, use with caution ! + +For now, only iOS works. + +For other platform, you should probably use the Sign In with Apple Web SDK. + +https://developer.apple.com/help/account/configure-app-capabilities/configure-sign-in-with-apple-for-the-web/ + +in case of any question, contact me: +Discord: @manaf941 +Twitter: @manaaaaaaaf \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore new file mode 100644 index 000000000..c0f21ec2f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts new file mode 100644 index 000000000..0ed421e5a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "ch.manaf.tauri_plugins.sign_in_with_apple" + compileSdk = 34 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle new file mode 100644 index 000000000..d7782a40d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle @@ -0,0 +1,31 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + google() + } + resolutionStrategy { + eachPlugin { + switch (requested.id.id) { + case "com.android.library": + useVersion("8.0.2") + break + case "org.jetbrains.kotlin.android": + useVersion("1.8.20") + break + } + } + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + + } +} + +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..7f1e75be0 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("ch.manaf.tauri_plugins.sign_in_with_apple", appContext.packageName) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9a40236b9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt new file mode 100644 index 000000000..28c6203ea --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt @@ -0,0 +1,10 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import android.util.Log + +class Example { + fun pong(value: String): String { + Log.i("Pong", value) + return value + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt new file mode 100644 index 000000000..045f0c6ff --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt @@ -0,0 +1,28 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import android.app.Activity +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import app.tauri.plugin.Invoke + +@InvokeArg +class PingArgs { + var value: String? = null +} + +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + private val implementation = Example() + + @Command + fun ping(invoke: Invoke) { + val args = invoke.parseArgs(PingArgs::class.java) + + val ret = JSObject() + ret.put("value", implementation.pong(args.value ?: "default value :(")) + invoke.resolve(ret) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 000000000..d1c3ad2b5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs new file mode 100644 index 000000000..0ca719650 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs @@ -0,0 +1,8 @@ +const COMMANDS: &[&str] = &["get_apple_id_credential"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .android_path("android") + .ios_path("ios") + .build(); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts new file mode 100644 index 000000000..99c1234f9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts @@ -0,0 +1,31 @@ +import { invoke } from '@tauri-apps/api/core' + +export type Scope = "fullName" | "email" +export interface AppleIDAuthorizationRequest { + scope: Scope[], + nonce?: string, + state?: string +} + +export interface AppleIDAuthorizationResponse { + // usually not null + userIdentifier: string | null, + + givenName: string | null, + familyName: string | null, + email: string | null, + + authorizationCode: string, + identityToken: string | null, + state: string | null +} + +export async function get_apple_id_credential(request: AppleIDAuthorizationRequest): Promise { + const result = await invoke('plugin:sign-in-with-apple|get_apple_id_credential', { + payload: request, + }) + + return result +} + + diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore new file mode 100644 index 000000000..5922fdaa5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift new file mode 100644 index 000000000..c95f5c0b6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "tauri-plugin-sign-in-with-apple", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-sign-in-with-apple", + type: .static, + targets: ["tauri-plugin-sign-in-with-apple"]), + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-sign-in-with-apple", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md new file mode 100644 index 000000000..9d6980b0f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin sign_in_with_apple + +A description of this package. diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift new file mode 100644 index 000000000..9ed7f9c36 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift @@ -0,0 +1,82 @@ +// +// SignInWithAppleAuthorizationController.swift +// tauri-plugin-sign-in-with-apple +// +// Created by Manaf Mhamdi Alaoui on 10.11.2024. +// + +import AuthenticationServices +import Tauri + +class SignInWithAppleAuthorizationController: NSObject, ASAuthorizationControllerDelegate { + var invoke: Invoke + init(_ invoke: Invoke) { + self.invoke = invoke + } + + public func authorize(req: AppleIDAuthorizationRequest) { + let provider = ASAuthorizationAppleIDProvider() + let request = provider.createRequest() + + if let nonce = req.nonce { + request.nonce = nonce + } + if let state = req.state { + request.state = state + } + + request.requestedScopes = [] + for scope in req.scope { + switch scope { + case "email": + request.requestedScopes?.append(.email) + case "fullName": + request.requestedScopes?.append(.fullName) + default: + NSLog("[tauri-plugin-sign-in-with-apple] Unsupported scope: \(scope)") + continue; + } + } + + let controller = ASAuthorizationController( + authorizationRequests: [ + request + ] + ) + + controller.delegate = self + controller.performRequests() + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + invoke.resolve( + AppleIDAuthorizationResponse( + userIdentifier: !appleIDCredential.user.isEmpty ? + appleIDCredential.user : + nil, + givenName: appleIDCredential.fullName?.givenName, + familyName: appleIDCredential.fullName?.familyName, + email: appleIDCredential.email, + authorizationCode: String(decoding: appleIDCredential.authorizationCode!, as: UTF8.self), + identityToken: appleIDCredential.identityToken != nil ? + String(decoding: appleIDCredential.identityToken!, as: UTF8.self) : + nil, + state: appleIDCredential.state + ) + ) + } else { + invoke.reject("Returned credential is not of type ASAuthorizationAppleIDCredential") + } + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + invoke.reject(error.localizedDescription) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift new file mode 100644 index 000000000..a7824326b --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift @@ -0,0 +1,58 @@ +// +// SignInWithApplePlugin.swift +// tauri-plugin-sign-in-with-apple +// +// Created by Manaf Mhamdi Alaoui on 10.11.2024. +// + +import SwiftRs +import Tauri +import UIKit +import WebKit + +class AppleIDAuthorizationRequest: Decodable { + let scope: [String] + let nonce: String? + let state: String? +} + +class AppleIDAuthorizationResponse: Encodable { + let userIdentifier: String? + + let givenName: String? + let familyName: String? + let email: String? + + let authorizationCode: String + let identityToken: String? + let state: String? + + init(userIdentifier: String?, givenName: String?, familyName: String?, email: String?, authorizationCode: String, identityToken: String?, state: String?) { + self.userIdentifier = userIdentifier + self.givenName = givenName + self.familyName = familyName + self.email = email + self.authorizationCode = authorizationCode + self.identityToken = identityToken + self.state = state + } +} + +class SignInWithApplePlugin: Plugin { + var lastController: SignInWithAppleAuthorizationController? + + @objc public func get_apple_id_credential(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(AppleIDAuthorizationRequest.self) + + // store to keep alive + let controller = SignInWithAppleAuthorizationController(invoke) + lastController = controller + + controller.authorize(req: args) + } +} + +@_cdecl("init_plugin_sign_in_with_apple") +func initPlugin() -> Plugin { + return SignInWithApplePlugin() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 000000000..2f50e3087 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,8 @@ +import XCTest +@testable import SignInWithApplePlugin + +final class SignInWithApplePluginTests: XCTestCase { + func testExample() throws { + let plugin = SignInWithApplePlugin() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json new file mode 100644 index 000000000..7ad4261c5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json @@ -0,0 +1,34 @@ +{ + "name": "tauri-plugin-sign-in-with-apple-api", + "version": "1.0.2", + "author": "Manaf941", + "description": "", + "type": "module", + "license": "MIT", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "files": [ + "dist-js", + "README.md" + ], + "scripts": { + "build": "rollup -c", + "prepublishOnly": "pnpm build", + "pretest": "pnpm build" + }, + "dependencies": { + "@tauri-apps/api": ">=2.0.0-beta.6" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^11.1.6", + "rollup": "^4.9.6", + "typescript": "^5.3.3", + "tslib": "^2.6.2" + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml new file mode 100644 index 000000000..02f8eb766 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-get-apple-id-credential"] diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json new file mode 100644 index 000000000..149d8c00d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json @@ -0,0 +1,318 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the get_apple_id_credential command without any pre-configured scope.", + "type": "string", + "const": "allow-get-apple-id-credential", + "markdownDescription": "Enables the get_apple_id_credential command without any pre-configured scope." + }, + { + "description": "Denies the get_apple_id_credential command without any pre-configured scope.", + "type": "string", + "const": "deny-get-apple-id-credential", + "markdownDescription": "Denies the get_apple_id_credential command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-apple-id-credential`", + "type": "string", + "const": "default", + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-apple-id-credential`" + } + ] + } + } +} \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js new file mode 100644 index 000000000..d83436352 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js @@ -0,0 +1,31 @@ +import { readFileSync } from 'fs' +import { join } from 'path' +import { cwd } from 'process' +import typescript from '@rollup/plugin-typescript' + +const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8')) + +export default { + input: 'guest-js/index.ts', + output: [ + { + file: pkg.exports.import, + format: 'esm' + }, + { + file: pkg.exports.require, + format: 'cjs' + } + ], + plugins: [ + typescript({ + declaration: true, + declarationDir: `./${pkg.exports.import.split('/')[0]}` + }) + ], + external: [ + /^@tauri-apps\/api/, + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ] +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs new file mode 100644 index 000000000..5f1e9b3d9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs @@ -0,0 +1,13 @@ +use tauri::{AppHandle, command, Runtime}; + +use crate::models::*; +use crate::Result; +use crate::SignInWithAppleExt; + +#[command] +pub(crate) async fn get_apple_id_credential( + app: AppHandle, + payload: AppleIDAuthorizationRequest, +) -> Result { + app.sign_in_with_apple().get_apple_id_credential(payload) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs new file mode 100644 index 000000000..833307bf6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs @@ -0,0 +1,20 @@ +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(SignInWithApple(app.clone())) +} + +/// Access to the sign-in-with-apple APIs. +pub struct SignInWithApple(AppHandle); + +impl SignInWithApple { + pub fn get_apple_id_credential(&self, _payload: AppleIDAuthorizationRequest) -> crate::Result { + Err(crate::Error::UnsupportedPlatformError) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs new file mode 100644 index 000000000..0e76603ed --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs @@ -0,0 +1,23 @@ +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Unsupported platform for sign_in_with_apple")] + UnsupportedPlatformError, + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs new file mode 100644 index 000000000..4f4dec6a9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs @@ -0,0 +1,48 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +use desktop::SignInWithApple; +#[cfg(mobile)] +use mobile::SignInWithApple; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sign-in-with-apple APIs. +pub trait SignInWithAppleExt { + fn sign_in_with_apple(&self) -> &SignInWithApple; +} + +impl> crate::SignInWithAppleExt for T { + fn sign_in_with_apple(&self) -> &SignInWithApple { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("sign-in-with-apple") + .invoke_handler(tauri::generate_handler![commands::get_apple_id_credential]) + .setup(|app, api| { + #[cfg(mobile)] + let sign_in_with_apple = mobile::init(app, api)?; + #[cfg(desktop)] + let sign_in_with_apple = desktop::init(app, api)?; + app.manage(sign_in_with_apple); + Ok(()) + }) + .build() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs new file mode 100644 index 000000000..c28ac9039 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs @@ -0,0 +1,34 @@ +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_sign_in_with_apple); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin("ch.manaf.tauri_plugins.sign_in_with_apple", "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_sign_in_with_apple)?; + Ok(SignInWithApple(handle)) +} + +/// Access to the sign-in-with-apple APIs. +pub struct SignInWithApple(PluginHandle); + +impl SignInWithApple { + pub fn get_apple_id_credential(&self, payload: AppleIDAuthorizationRequest) -> crate::Result { + self + .0 + .run_mobile_plugin("get_apple_id_credential", payload) + .map_err(Into::into) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs new file mode 100644 index 000000000..980c7ec4c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppleIDAuthorizationRequest { + pub scope: Vec, + pub nonce: Option, + pub state: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppleIDAuthorizationResponse { + pub user_identifier: Option, + + pub given_name: Option, + pub family_name: Option, + pub email: Option, + + pub authorization_code: String, + pub identity_token: Option, + pub state: Option, +} + + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingRequest { + pub value: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingResponse { + pub value: Option, +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json new file mode 100644 index 000000000..059112270 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "noEmit": true + }, + "include": ["guest-js/*.ts"], + "exclude": ["dist-js", "node_modules"] +} From 6b57d28dfbe174eee51faa3ae4ce3fe5b8f15ca4 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:49:52 +0000 Subject: [PATCH 07/14] research(ios): make Swift plugin entry points public Xcode 27 release builds need @_cdecl plugin entry points to be public for the Rust bindings to link; Tauri 2.12.3 did the same for its own Swift package (tauri-apps/tauri#16130). Covers the in-tree IAP plugin and the vendored Sign in with Apple plugin. Co-Authored-By: Claude Opus 5.5 --- .../ios/Sources/SignInWithApplePlugin.swift | 2 +- .../plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift index a7824326b..df9acc495 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift @@ -53,6 +53,6 @@ class SignInWithApplePlugin: Plugin { } @_cdecl("init_plugin_sign_in_with_apple") -func initPlugin() -> Plugin { +public func initPlugin() -> Plugin { return SignInWithApplePlugin() } diff --git a/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift b/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift index 6841fabf1..2fb10e151 100644 --- a/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift +++ b/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift @@ -311,7 +311,7 @@ class IapPlugin: Plugin { } @_cdecl("init_plugin_iap") -func initPlugin() -> Plugin { +public func initPlugin() -> Plugin { if #available(iOS 16.0, *) { return IapPlugin() } return Plugin() } From 7df88b2e49e7e83d004dc65c1520ec83fd1bb13c Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:50:07 +0000 Subject: [PATCH 08/14] research(ios): deliver deep links that cold-launch the app With the scene life cycle, UIKit hands a launch URL to the connecting scene's connection options and never calls scene:openURLContexts: for it. tao 0.37 ignores those options, so a deep link that starts Maple (for example an OAuth callback after iOS killed the app) never reached tauri-plugin-deep-link. Backports tauri-apps/tao#1376 onto the vendored tao 0.37.1 (only the import context differs from upstream dev). Co-Authored-By: Claude Opus 5.5 --- .../frontend/src-tauri/Cargo.toml | 4 ++ .../src/platform_impl/ios/app_state.rs | 29 ++++++++- .../tao-0.37.1/src/platform_impl/ios/scene.rs | 62 +++++++++++++------ 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index d9042e412..6731c76ee 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -126,6 +126,10 @@ maple-sdk = { path = "../../../../sdk/rust" } # receive null objects from real Android launch/deep-link flows, aborting the # process before tauri-plugin-deep-link sees OAuth/payment redirects. Remove # this once Tauri resolves to a Tao release containing the upstream fix. +# +# It also carries tauri-apps/tao#1376 for iOS: in the scene life cycle a URL that +# cold-launches the app arrives in the scene connection options, which tao 0.37 +# ignores, so OAuth callbacks that relaunch Maple were dropped. # The vendored library uses this application's Cargo.lock; its upstream # standalone lockfile is intentionally omitted. tao = { path = "patches/tao-0.37.1" } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs index d46e076f4..a0d08996d 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs @@ -14,7 +14,10 @@ use std::{ }; use objc2::{rc::Retained, runtime::AnyObject, MainThreadMarker, Message}; -use objc2_ui_kit::{UIApplication, UIScene, UISceneConnectionOptions, UIWindowScene}; +use objc2_foundation::{NSSet, NSUserActivity}; +use objc2_ui_kit::{ + UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UIWindowScene, +}; use crate::{ dpi::LogicalSize, @@ -28,7 +31,7 @@ use crate::{ CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSUInteger, }, - scene::scene_lifecycle_enabled, + scene::{self, scene_lifecycle_enabled}, }, window::WindowId as RootWindowId, }; @@ -561,6 +564,28 @@ pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions) let () = msg_send![window, release]; } } + + // a URL that launches the app (or opens a new scene) is handed over here, in the connection + // options: scene:openURLContexts: is only called for URLs received by a scene that is already + // connected, so without this they would be lost + // + // UIKit returns nil for both sets when the app is launched without a link, although the objc2 + // bindings declare them non-null: read them as optional, the bindings would panic on nil + let url_contexts: Option>> = msg_send![options, URLContexts]; + let user_activities: Option>> = msg_send![options, userActivities]; + let source = "the scene connection options"; + let mut urls = url_contexts + .map(|contexts| scene::urls_from_contexts(&contexts, source)) + .unwrap_or_default(); + urls.extend( + user_activities + .iter() + .flat_map(|activities| activities.iter()) + .filter_map(|activity| scene::universal_link_url(&activity, source)), + ); + if !urls.is_empty() { + handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); + } } pub unsafe fn register_window_for_scene(window: id) { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs index 0077367b3..7495517cc 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs @@ -4,6 +4,7 @@ use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; use objc2_foundation::{ NSBundle, NSError, NSObject, NSObjectProtocol, NSSet, NSString, NSUserActivity, + NSUserActivityTypeBrowsingWeb, }; use objc2_ui_kit::{ UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, @@ -59,6 +60,45 @@ unsafe fn handle_scene_window_events(scene: &UIScene, event: impl Fn() -> Window } } +// parses the URLs carried by the given contexts, skipping (and logging) the ones that are not +// valid; `source` names the delivery path in the log +pub unsafe fn urls_from_contexts( + url_contexts: &NSSet, + source: &str, +) -> Vec { + url_contexts + .iter() + .filter_map(|ctx| { + ctx + .URL() + .absoluteString() + .and_then(|url| parse_url(&url.to_string(), source)) + }) + .collect() +} + +// the URL of a universal link activity, if `activity` is one: other activities, such as +// Handoff, can carry a web page URL too and are not links that opened the app +pub unsafe fn universal_link_url(activity: &NSUserActivity, source: &str) -> Option { + if !activity + .activityType() + .isEqualToString(NSUserActivityTypeBrowsingWeb) + { + return None; + } + activity + .webpageURL() + .and_then(|url| url.absoluteString()) + .and_then(|url| parse_url(&url.to_string(), source)) +} + +fn parse_url(url: &str, source: &str) -> Option { + url + .parse() + .map_err(|e| log::error!("failed to parse URL {url} from {source}: {e}")) + .ok() +} + define_class!( #[unsafe(super(NSObject))] #[name = "TaoSceneDelegate"] @@ -125,21 +165,7 @@ define_class!( #[unsafe(method(scene:openURLContexts:))] fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet) { unsafe { - let urls: Vec = url_contexts - .iter() - .filter_map(|ctx| { - ctx.URL().absoluteString().and_then(|url| { - let url = url.to_string(); - url - .parse() - .map_err(|e| { - log::error!("failed to parse URL {url} from scene:openURLContexts: {e}"); - e - }) - .ok() - }) - }) - .collect(); + let urls = urls_from_contexts(url_contexts, "scene:openURLContexts:"); if !urls.is_empty() { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); } @@ -174,11 +200,7 @@ define_class!( fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) { unsafe { // universal app links - if let Some(url) = user_activity - .webpageURL() - .and_then(|url| url.absoluteString()) - { - let url = url.to_string().parse::().unwrap(); + if let Some(url) = universal_link_url(user_activity, "scene:continueUserActivity:") { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls: vec![url], })); From acaee3d9fbb5cca17bcaf1c192686241109c450b Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:26:00 +0000 Subject: [PATCH 09/14] ci: accept suffixed Xcode installs with the pinned build GitHub's xcode-27 image keeps Xcode 27.1 (27A9275) at /Applications/Xcode_27.1_Release_Candidate.app, and its default is 27.0. Also consider Xcode__*.app after the exact names; selection still requires the pinned build and still skips beta installs. Co-Authored-By: Claude Opus 5.5 --- scripts/ci/select-xcode.py | 5 ++++- scripts/ci/test_select_xcode.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/ci/select-xcode.py b/scripts/ci/select-xcode.py index 2e29aadb5..8a85f59cb 100644 --- a/scripts/ci/select-xcode.py +++ b/scripts/ci/select-xcode.py @@ -29,7 +29,10 @@ def load_pin(path=PIN): def select_xcode(pin, applications=Path("/Applications")): version = pin["version"] - for name in (f"Xcode_{version}.app", f"Xcode_{version}.0.app", "Xcode.app"): + # Hosted images may keep a release under a suffixed name (for example + # Xcode_27.1_Release_Candidate.app); the exact build check below decides. + suffixed = sorted(path.name for path in applications.glob(f"Xcode_{version}_*.app")) + for name in (f"Xcode_{version}.app", f"Xcode_{version}.0.app", *suffixed, "Xcode.app"): developer = applications / name / "Contents/Developer" if not (developer / "usr/bin/xcodebuild").is_file(): continue diff --git a/scripts/ci/test_select_xcode.py b/scripts/ci/test_select_xcode.py index fd6a0b0c4..04b3fd705 100644 --- a/scripts/ci/test_select_xcode.py +++ b/scripts/ci/test_select_xcode.py @@ -97,6 +97,19 @@ def test_beta_install_and_disguised_beta_symlink_are_never_probed(self): selector.select_xcode(self.pin, self.directory) run.assert_not_called() + def test_suffixed_release_install_is_selected_by_exact_build(self): + self.install("Xcode_26.5_beta_2.app") + developer = self.install("Xcode_26.5_Release_Candidate.app") + with patch.object(selector.subprocess, "run", return_value=self.result()) as run: + self.assertEqual(selector.select_xcode(self.pin, self.directory), developer) + self.assertEqual(run.call_count, 1) + + def test_suffixed_install_with_another_build_is_rejected(self): + self.install("Xcode_26.5_Release_Candidate.app") + with patch.object(selector.subprocess, "run", return_value=self.result(build="17F40")): + with self.assertRaisesRegex(selector.ToolchainError, "26.5 build 17F42"): + selector.select_xcode(self.pin, self.directory) + def test_rolling_default_is_skipped_when_pinned_release_is_available(self): self.install("Xcode_26.5.app") developer = self.install("Xcode_26.5.0.app") From dd2e0be42ca0550184d42409e7e8a322b1b4ff46 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:31:09 +0000 Subject: [PATCH 10/14] ci: build Apple lanes with Xcode 27.1 Xcode 27.1 (27A9275) brings the iOS 27.1 SDK and iPhone Duo support, and App Store Connect requires the iOS 27 SDK from April 2027. - apple-toolchain.json pins 27.1 / 27A9275 for the selector, the Nix shell and the cache keys. - Builders move to blacksmith-6vcpu-macos-27 and GitHub-hosted jobs to xcode-27; the macos-26 images have no Xcode 27. Signing stays on GitHub-hosted runners. - Agent CI's macOS jobs select the pinned Xcode instead of the runner default (27.0 on the xcode-27 image); the workflow lint now rejects macos-14 and macos-26 labels. - The iOS ONNX Runtime build targets iOS 16.0 like the app: Xcode 27 only builds for deployment targets 15.0 and later. Its hash pins move to 27A9275 from a local Xcode 27.1 build, and the dead 17F42 defaults go. - Docs: runner labels and the pin; the StoreKit harness commands no longer select the removed Xcode 26.6. Drops the stale project.pbxproj.backup, which still targeted iOS 13. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/agent-ci.yml | 14 +- .github/workflows/agent-desktop-build.yml | 8 +- .github/workflows/agent-store-performance.yml | 7 +- .github/workflows/desktop-build.yml | 4 +- .github/workflows/desktop-pr-build.yml | 4 +- .github/workflows/ios-dev-testflight.yml | 4 +- .github/workflows/mobile-build.yml | 6 +- .github/workflows/mobile-pr-build.yml | 2 +- .github/workflows/proxy-rust.yml | 2 +- .github/workflows/release.yml | 10 +- apps/maple-agent/crates/store-bench/README.md | 2 +- .../maple.xcodeproj/project.pbxproj.backup | 626 ------------------ .../scripts/build-ios-onnxruntime-all.sh | 2 +- .../scripts/build-ios-onnxruntime.sh | 2 +- .../src-tauri/scripts/onnxruntime-pins.sh | 18 +- .../src-tauri/tests/storekit/README.md | 23 +- docs/repository-workflows.md | 12 +- scripts/ci/_common.sh | 2 +- scripts/ci/apple-toolchain.json | 4 +- scripts/ci/test_agent_workflows.py | 2 +- scripts/ci/test_select_xcode.py | 6 +- scripts/testing/storekit/run.sh | 7 +- 22 files changed, 79 insertions(+), 688 deletions(-) delete mode 100644 apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup diff --git a/.github/workflows/agent-ci.yml b/.github/workflows/agent-ci.yml index 753036e1f..1592f5453 100644 --- a/.github/workflows/agent-ci.yml +++ b/.github/workflows/agent-ci.yml @@ -83,7 +83,7 @@ jobs: strategy: fail-fast: false matrix: - os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] + os: [blacksmith-8vcpu-ubuntu-2404, xcode-27, windows-latest] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -101,6 +101,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Install pinned Windows Rust if: runner.os == 'Windows' uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below @@ -264,7 +269,7 @@ jobs: strategy: fail-fast: false matrix: - os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] + os: [blacksmith-8vcpu-ubuntu-2404, xcode-27, windows-latest] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -282,6 +287,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Install pinned Windows Rust if: runner.os == 'Windows' uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below diff --git a/.github/workflows/agent-desktop-build.yml b/.github/workflows/agent-desktop-build.yml index dc5300974..811ccd401 100644 --- a/.github/workflows/agent-desktop-build.yml +++ b/.github/workflows/agent-desktop-build.yml @@ -80,7 +80,7 @@ jobs: name: Build macOS binary (${{ matrix.variant }}) needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') && (github.event_name == 'pull_request' || (github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch'))) }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 timeout-minutes: 120 strategy: fail-fast: false @@ -172,7 +172,7 @@ jobs: contents: read id-token: write attestations: write - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false @@ -235,7 +235,7 @@ jobs: name: Unsigned macOS packaging (${{ matrix.variant }}) needs: [changes, macos-build] if: ${{ always() && !cancelled() && needs.macos-build.result == 'success' && github.event_name == 'pull_request' && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') }} - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false @@ -428,7 +428,7 @@ jobs: permissions: contents: read attestations: read - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 30 strategy: fail-fast: false diff --git a/.github/workflows/agent-store-performance.yml b/.github/workflows/agent-store-performance.yml index 20658021c..ba2291395 100644 --- a/.github/workflows/agent-store-performance.yml +++ b/.github/workflows/agent-store-performance.yml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJSON((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"},{"os":"macos-26","profile":"macos-full","scale":"--full"}]' || '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"}]') }} + include: ${{ fromJSON((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"},{"os":"xcode-27","profile":"macos-full","scale":"--full"}]' || '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"}]') }} if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'push' runs-on: ${{ matrix.os }} timeout-minutes: 240 @@ -60,6 +60,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Cache benchmark dependencies uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 223dfbc88..ff65aa0e8 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -18,7 +18,7 @@ jobs: environment: desktop-signing needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.frontend != 'false' || needs.changes.outputs.macos != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: @@ -204,7 +204,7 @@ jobs: verify-macos-desktop-artifacts: needs: - build-macos - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml index fb65222b4..1abd21f64 100644 --- a/.github/workflows/desktop-pr-build.yml +++ b/.github/workflows/desktop-pr-build.yml @@ -24,7 +24,7 @@ jobs: name: build-macos${{ github.event.action == 'labeled' && format(' (label {0})', github.event.label.name) || '' }} needs: changes if: ${{ always() && !cancelled() && github.event.action != 'labeled' && (needs.changes.result != 'success' || needs.changes.outputs.macos != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: @@ -84,7 +84,7 @@ jobs: concurrency: group: research-dev-macos-pr-${{ github.event.pull_request.number }} cancel-in-progress: true - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: diff --git a/.github/workflows/ios-dev-testflight.yml b/.github/workflows/ios-dev-testflight.yml index 56432b4d8..5381fa2c4 100644 --- a/.github/workflows/ios-dev-testflight.yml +++ b/.github/workflows/ios-dev-testflight.yml @@ -22,7 +22,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') environment: apple-signing - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 timeout-minutes: 240 permissions: contents: read @@ -130,7 +130,7 @@ jobs: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') needs: build-ios-dev environment: apple-signing - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 45 permissions: contents: read diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index de914c085..fef19d248 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -34,7 +34,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (needs.changes.result != 'success' || needs.changes.outputs.frontend != 'false' || needs.changes.outputs.ios != 'false') - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 permissions: contents: read id-token: write @@ -118,7 +118,7 @@ jobs: verify-ios-artifacts: needs: build-ios - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: @@ -153,7 +153,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') needs: verify-ios-artifacts - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/.github/workflows/mobile-pr-build.yml b/.github/workflows/mobile-pr-build.yml index d76b0c2f6..27767260b 100644 --- a/.github/workflows/mobile-pr-build.yml +++ b/.github/workflows/mobile-pr-build.yml @@ -18,7 +18,7 @@ jobs: build-ios: needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.ios != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: diff --git a/.github/workflows/proxy-rust.yml b/.github/workflows/proxy-rust.yml index e55b6dfc5..80de5e47e 100644 --- a/.github/workflows/proxy-rust.yml +++ b/.github/workflows/proxy-rust.yml @@ -95,7 +95,7 @@ jobs: archive: maple-proxy-linux-x86_64.tar.gz - runner: ubuntu-24.04-arm archive: maple-proxy-linux-aarch64.tar.gz - - runner: blacksmith-6vcpu-macos-26 + - runner: blacksmith-6vcpu-macos-27 archive: maple-proxy-macos-aarch64.tar.gz - runner: windows-2025 archive: maple-proxy-windows-x86_64.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 812c8b2a6..5884fe303 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: fail-fast: false matrix: include: - - platform: blacksmith-6vcpu-macos-26 + - platform: blacksmith-6vcpu-macos-27 nix_shell: ci artifact_prefix: macos - platform: blacksmith-8vcpu-ubuntu-2404 @@ -484,7 +484,7 @@ jobs: build-ios: environment: apple-signing needs: classify-app-release - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 permissions: contents: write id-token: write @@ -651,7 +651,7 @@ jobs: archive: maple-proxy-linux-x86_64.tar.gz - runner: ubuntu-24.04-arm archive: maple-proxy-linux-aarch64.tar.gz - - runner: blacksmith-6vcpu-macos-26 + - runner: blacksmith-6vcpu-macos-27 archive: maple-proxy-macos-aarch64.tar.gz - runner: windows-2025 archive: maple-proxy-windows-x86_64.zip @@ -848,7 +848,7 @@ jobs: needs: - classify-app-release - build-tauri - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: @@ -996,7 +996,7 @@ jobs: - update-latest-json - verify-android-release-artifacts - verify-proxy-release-artifacts - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/apps/maple-agent/crates/store-bench/README.md b/apps/maple-agent/crates/store-bench/README.md index 9a045e579..c9daf8786 100644 --- a/apps/maple-agent/crates/store-bench/README.md +++ b/apps/maple-agent/crates/store-bench/README.md @@ -61,7 +61,7 @@ dataset identity and settings. No synthetic database or key is uploaded. scenarios and their sample counts, effective durability settings, and indexed plans with and without statistics. Missing scenarios cannot silently remove a slow operation. Linux CI compares medians at 10x its actual Linux reference; -the scheduled `macos-26` full run uses 2x its actual macOS reference. Platform +the scheduled `xcode-27` full run uses 2x its actual macOS reference. Platform mismatches are rejected. Native Mac small runs can use `--profile linux-ci --diagnostic` to validate workload evidence without comparing or recording a Linux runner gate. diff --git a/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup b/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup deleted file mode 100644 index 41a99a896..000000000 --- a/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup +++ /dev/null @@ -1,626 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 56; - objects = { - -/* Begin PBXBuildFile section */ - 0F4AA8C5111094B8D7B033A4 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E996CAD20B045EF299247BEF /* WebKit.framework */; }; - 23D760C25DBAAA96DF52F98C /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */; }; - 27222ACBBD95CA356BDB87AB /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */; }; - 46728F6CD07C626A781543FF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 938F41BA03AF7FAB80585A60 /* Security.framework */; }; - 4E14944735CA89389849E430 /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F27A6FC4AB3E06D481B1E137 /* libapp.a */; }; - 621A96F6B965E600E714FB6C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */; }; - 8F9EDB5679AB5E12D6F1E071 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */; }; - 90DEA2E2EC5F95784C9D8B00 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */; }; - 9F3ED7EA97AFC22E0B4A6EAF /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = CE20E8A9C4CF149CA9CA485A /* main.mm */; }; - A120CF155DE2B343CEFFB77C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */; }; - B79EC8A2A1A869B9F38906ED /* assets in Resources */ = {isa = PBXBuildFile; fileRef = AF5DA3546C7B12BA141E9508 /* assets */; }; - FB98D58EAB479A32F7CA66CA /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C072EB8E1CCC1094712EA7F7 /* Metal.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 1094B8CB671809878F533CEC /* lib.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = lib.rs; sourceTree = ""; }; - 19C98BDFCAE8C0B2FE05F13B /* maple_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = maple_iOS.entitlements; sourceTree = ""; }; - 1DB07799A17353B2E32B65D3 /* Maple.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Maple.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 7BBAB8D5C4E3D05A4F48B6EA /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; - 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 938F41BA03AF7FAB80585A60 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; - AF5DA3546C7B12BA141E9508 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; - B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - C072EB8E1CCC1094712EA7F7 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - C0A332F21EB65CD4B83229DF /* main.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.rs; sourceTree = ""; }; - CE20E8A9C4CF149CA9CA485A /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; - D6F6085362AF0C062EDB5810 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - E996CAD20B045EF299247BEF /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - F27A6FC4AB3E06D481B1E137 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 04F3BEBB04BD927FDC86B255 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4E14944735CA89389849E430 /* libapp.a in Frameworks */, - 90DEA2E2EC5F95784C9D8B00 /* CoreGraphics.framework in Frameworks */, - FB98D58EAB479A32F7CA66CA /* Metal.framework in Frameworks */, - 23D760C25DBAAA96DF52F98C /* MetalKit.framework in Frameworks */, - 27222ACBBD95CA356BDB87AB /* QuartzCore.framework in Frameworks */, - 46728F6CD07C626A781543FF /* Security.framework in Frameworks */, - 8F9EDB5679AB5E12D6F1E071 /* UIKit.framework in Frameworks */, - 0F4AA8C5111094B8D7B033A4 /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 31943D18956B6AFD5A82AB02 /* maple_iOS */ = { - isa = PBXGroup; - children = ( - D6F6085362AF0C062EDB5810 /* Info.plist */, - 19C98BDFCAE8C0B2FE05F13B /* maple_iOS.entitlements */, - ); - path = maple_iOS; - sourceTree = ""; - }; - 6AE8986BFC081ADF4EF98889 /* Externals */ = { - isa = PBXGroup; - children = ( - ); - path = Externals; - sourceTree = ""; - }; - A278899D6905367F60E00F01 /* Sources */ = { - isa = PBXGroup; - children = ( - E5992A2C52DE443515252068 /* maple */, - ); - path = Sources; - sourceTree = ""; - }; - B58B2AB62FA5D8F41C3A031C /* Frameworks */ = { - isa = PBXGroup; - children = ( - 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */, - F27A6FC4AB3E06D481B1E137 /* libapp.a */, - C072EB8E1CCC1094712EA7F7 /* Metal.framework */, - AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */, - BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */, - 938F41BA03AF7FAB80585A60 /* Security.framework */, - DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */, - E996CAD20B045EF299247BEF /* WebKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - C312B5C9A1506139452E194B /* Products */ = { - isa = PBXGroup; - children = ( - 1DB07799A17353B2E32B65D3 /* Maple.app */, - ); - name = Products; - sourceTree = ""; - }; - D3140E092D76240F6A157CCE = { - isa = PBXGroup; - children = ( - AF5DA3546C7B12BA141E9508 /* assets */, - 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */, - B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */, - 6AE8986BFC081ADF4EF98889 /* Externals */, - 31943D18956B6AFD5A82AB02 /* maple_iOS */, - A278899D6905367F60E00F01 /* Sources */, - FF31B258F4FE6CC5B57B4FB8 /* src */, - B58B2AB62FA5D8F41C3A031C /* Frameworks */, - C312B5C9A1506139452E194B /* Products */, - ); - sourceTree = ""; - }; - E5992A2C52DE443515252068 /* maple */ = { - isa = PBXGroup; - children = ( - CE20E8A9C4CF149CA9CA485A /* main.mm */, - F9E1B0C7AF484C8F6312F2C8 /* bindings */, - ); - path = maple; - sourceTree = ""; - }; - F9E1B0C7AF484C8F6312F2C8 /* bindings */ = { - isa = PBXGroup; - children = ( - 7BBAB8D5C4E3D05A4F48B6EA /* bindings.h */, - ); - path = bindings; - sourceTree = ""; - }; - FF31B258F4FE6CC5B57B4FB8 /* src */ = { - isa = PBXGroup; - children = ( - 1094B8CB671809878F533CEC /* lib.rs */, - C0A332F21EB65CD4B83229DF /* main.rs */, - ); - name = src; - path = ../../src; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 745812EBA5246F9E4CEE1574 /* maple_iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 27939507295816FC37580B40 /* Build configuration list for PBXNativeTarget "maple_iOS" */; - buildPhases = ( - 88FF8D973FB9516A2A98CD39 /* Build Rust Code */, - 6D9F08407F22BE108C77276C /* Sources */, - 25104604B2C2A81B04543E68 /* Resources */, - 04F3BEBB04BD927FDC86B255 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = maple_iOS; - productName = maple_iOS; - productReference = 1DB07799A17353B2E32B65D3 /* Maple.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EAA0B11A781B9CD753D1399F /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - }; - buildConfigurationList = 2FA6FD4DEFE659C96AAB5E5D /* Build configuration list for PBXProject "maple" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = D3140E092D76240F6A157CCE; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 745812EBA5246F9E4CEE1574 /* maple_iOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 25104604B2C2A81B04543E68 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A120CF155DE2B343CEFFB77C /* Assets.xcassets in Resources */, - 621A96F6B965E600E714FB6C /* LaunchScreen.storyboard in Resources */, - B79EC8A2A1A869B9F38906ED /* assets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 88FF8D973FB9516A2A98CD39 /* Build Rust Code */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Build Rust Code"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "bun tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 6D9F08407F22BE108C77276C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9F3ED7EA97AFC22E0B4A6EAF /* main.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 1DE1BEC252AAEF735A07CA8B /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = "X773Y823TN"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = debug; - }; - 1E8FE8E2E3063C3FDCD3B5D1 /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = debug; - }; - 94ECC2E044AA76E166E0866E /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - }; - name = release; - }; - B90C9BEF4885680BFA986CD2 /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = "X773Y823TN"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = release; - }; - EA2669D02DADAA11005A7F4B /* local */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = local; - }; - EA2669D12DADAA11005A7F4B /* local */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = "X773Y823TN"; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = X773Y823TN; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = "86059ea7-ae8e-44af-8a58-b2ab7c78d299"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "86059ea7-ae8e-44af-8a58-b2ab7c78d299"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = local; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 27939507295816FC37580B40 /* Build configuration list for PBXNativeTarget "maple_iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DE1BEC252AAEF735A07CA8B /* debug */, - EA2669D12DADAA11005A7F4B /* local */, - B90C9BEF4885680BFA986CD2 /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; - 2FA6FD4DEFE659C96AAB5E5D /* Build configuration list for PBXProject "maple" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1E8FE8E2E3063C3FDCD3B5D1 /* debug */, - EA2669D02DADAA11005A7F4B /* local */, - 94ECC2E044AA76E166E0866E /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; -/* End XCConfigurationList section */ - }; - rootObject = EAA0B11A781B9CD753D1399F /* Project object */; -} diff --git a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh index 21b6e43c8..0baf7fc9d 100755 --- a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh @@ -23,7 +23,7 @@ BUILD_DIR="${TAURI_DIR}/onnxruntime-build" OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework" -IOS_DEPLOYMENT_TARGET="13.0" +IOS_DEPLOYMENT_TARGET="16.0" echo "========================================" echo "Building ONNX Runtime ${ORT_VERSION} for iOS" diff --git a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh index de3e38d0e..649e1df11 100755 --- a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh @@ -24,7 +24,7 @@ OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework" # Minimum iOS version to support -IOS_DEPLOYMENT_TARGET="13.0" +IOS_DEPLOYMENT_TARGET="16.0" echo "========================================" echo "Building ONNX Runtime ${ORT_VERSION} for iOS" diff --git a/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh b/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh index 51cb644df..ce6b55e44 100644 --- a/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh @@ -165,11 +165,11 @@ onnxruntime_ios_commit_for_version() { } onnxruntime_ios_device_lib_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "917cf5f30aa65f435371e323e347e094c99804fd6a268d3ae946cd8e5e58945f" + 1.23.2:27A9275) + printf '%s\n' "e4d288188f0b520d6676e87ad4a694326a8844618f471b5c8cfcdb69daf66657" ;; *) echo "No pinned ONNX Runtime iOS device library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -179,11 +179,11 @@ onnxruntime_ios_device_lib_sha256_for_version() { } onnxruntime_ios_simulator_lib_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "87c87225026db5ee2b5a1293468b9cc3410ad22eac182899056a05f2f2862071" + 1.23.2:27A9275) + printf '%s\n' "0b670a0fce021844551d7bd9fc0c3c4275208375155e0c17254866215ae28736" ;; *) echo "No pinned ONNX Runtime iOS simulator library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -193,11 +193,11 @@ onnxruntime_ios_simulator_lib_sha256_for_version() { } onnxruntime_ios_xcframework_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "86871fe82fb5b043540d5bbc67de5ba73f2a52105ec05c66e835b7add8972af5" + 1.23.2:27A9275) + printf '%s\n' "93cbb699abebba06963ccf1b5c283ea0bbf7b8e9e4664f9b2df726d2808bf18a" ;; *) echo "No pinned ONNX Runtime iOS xcframework SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 diff --git a/apps/maple-research/frontend/src-tauri/tests/storekit/README.md b/apps/maple-research/frontend/src-tauri/tests/storekit/README.md index 96943c890..e570b3064 100644 --- a/apps/maple-research/frontend/src-tauri/tests/storekit/README.md +++ b/apps/maple-research/frontend/src-tauri/tests/storekit/README.md @@ -26,8 +26,9 @@ re-signs/verifies them, and generates the local experiment scheme. Source plist and entitlements are restored. Preserve the existing frozen artifact when comparing toolchains; rebuilding Maple is a separate action. -These test commands explicitly select installed Xcode 26.6 **after entering** the -pinned Apple shell, without changing repository pins or global Xcode selection. +These test commands use the Xcode selected by the pinned Apple shell +(`scripts/ci/apple-toolchain.json`). The dated notes below record earlier runs on +Xcode 26.5 and 26.6. Use `run.sh`, which prepares native linker variables; do not invoke `run.py` directly from an unprepared Nix shell. Substitute the exact frozen app path and intended simulator UDID when different from these examples. @@ -35,9 +36,7 @@ intended simulator UDID when different from these examples. First build the small runner and run the catalog canary, which needs no fixture: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ --udid "" \ @@ -47,9 +46,7 @@ nix develop --no-update-lock-file .#apple -c env \ Then run the certificate canary with a fresh runner-owned bootstrap fixture: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --skip-build --fixture-bootstrap \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ --udid "" \ @@ -69,9 +66,7 @@ Pass that certificate to the remaining cases. They reuse the runner build but each gets a separate XCTest invocation, fixture process and journal: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --skip-build \ --fixture-certificate /absolute/path/to/MapleStoreKitSigningCertificate.cer \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ @@ -175,9 +170,7 @@ To export metadata from an already running purchased diagnostic screen without reinstalling or restarting Maple, pass the exact GUI-built DerivedData app: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --app /absolute/path/to/Maple.app \ --udid "" \ --only MapleStoreKitCaptureUITests/testExportPublicSigningCertificate @@ -202,7 +195,7 @@ The exact Foundation-only parser can also be checked on the host, without building an app or operating a simulator: ```sh -DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +DEVELOPER_DIR=/Applications/Xcode_27.1.app/Contents/Developer \ python3 scripts/testing/storekit/check-snapshot-parser.py ``` diff --git a/docs/repository-workflows.md b/docs/repository-workflows.md index 338230424..3ee3f61dd 100644 --- a/docs/repository-workflows.md +++ b/docs/repository-workflows.md @@ -165,21 +165,25 @@ for the baseline, tradeoffs and cold/warm validation procedure. ## macOS runners and Apple toolchain Research desktop/iOS builds, Agent release-binary jobs -and native proxy macOS archives use ARM64 `blacksmith-6vcpu-macos-26` runners. +and native proxy macOS archives use ARM64 `blacksmith-6vcpu-macos-27` runners. Artifact verifiers, TestFlight uploads, and Agent preview packaging/protected -signing use standard GitHub `macos-26` runners. Research signed builders retain +signing use GitHub's `xcode-27` image runners. Research signed builders retain their existing `desktop-signing` or `apple-signing` environment, so their provider receives that environment's secret context. Existing master pushes can build and upload independent Dev/Prod TestFlight artifacts after verification. The Agent's final signed-package provenance identifies its GitHub packaging/signing job; Blacksmith is trusted for prebuilt binary integrity. -Research desktop and iOS builds select stable Xcode 26.5, build `17F42`, from +Research desktop and iOS builds and the Agent's macOS jobs select stable Xcode 27.1, +build `27A9275`, from [`scripts/ci/apple-toolchain.json`](../scripts/ci/apple-toolchain.json) through the shared selector, which aligns the runner's active `xcode-select` directory and `DEVELOPER_DIR`; the root Nix shell exports the same version and build for its Apple recipes. Tauri mobile tooling discovers the active Xcode directly. Do not rely on the runner's default Xcode or beta app -names. The independent Agent shell selects a full installed Xcode, and the +names; hosted images can keep a release under a suffixed name such as +`Xcode_27.1_Release_Candidate.app`, which the selector accepts only when its +build matches the pin. Xcode 27 builds iOS code for deployment targets 15.0 and +later, so the iOS ONNX Runtime build targets 16.0 like the app. The independent Agent shell selects a full installed Xcode, and the SDK/proxy flakes own their Nix compiler and SDK inputs. The [iOS ONNX pins](../apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh) diff --git a/scripts/ci/_common.sh b/scripts/ci/_common.sh index 35adba06e..d8a00955a 100755 --- a/scripts/ci/_common.sh +++ b/scripts/ci/_common.sh @@ -2812,7 +2812,7 @@ verify_ios_onnxruntime_pinned_hashes() { local version xcode_build xcframework manifest file expected actual status version="$(ios_onnxruntime_version)" - xcode_build="${MAPLE_XCODE_BUILD_VERSION:-17F42}" + xcode_build="${MAPLE_XCODE_BUILD_VERSION:-27A9275}" xcframework="$(ios_onnxruntime_xcframework_dir)" manifest="$(ios_onnxruntime_manifest_file)" status=0 diff --git a/scripts/ci/apple-toolchain.json b/scripts/ci/apple-toolchain.json index 8f2cd86e1..9aa97b351 100644 --- a/scripts/ci/apple-toolchain.json +++ b/scripts/ci/apple-toolchain.json @@ -1,4 +1,4 @@ { - "version": "26.5", - "build": "17F42" + "version": "27.1", + "build": "27A9275" } diff --git a/scripts/ci/test_agent_workflows.py b/scripts/ci/test_agent_workflows.py index 1101a7f21..94aadde76 100644 --- a/scripts/ci/test_agent_workflows.py +++ b/scripts/ci/test_agent_workflows.py @@ -404,7 +404,7 @@ def test_only_trusted_master_can_receive_apple_signing_credentials(self): jobs = config["jobs"] signed = jobs["macos"] self.assertEqual(signed["environment"], "desktop-signing") - self.assertEqual(signed["runs-on"], "macos-26") + self.assertEqual(signed["runs-on"], "xcode-27") condition = " ".join(signed["if"].split()) self.assertIn(self.MASTER_GUARD, condition) self.assertIn("always() && !cancelled()", condition) diff --git a/scripts/ci/test_select_xcode.py b/scripts/ci/test_select_xcode.py index 04b3fd705..5f4dbaffe 100644 --- a/scripts/ci/test_select_xcode.py +++ b/scripts/ci/test_select_xcode.py @@ -155,7 +155,8 @@ def test_no_workflow_uses_retired_mac_os_and_every_native_selector_has_checkout( selectors = [] for path in sorted((ROOT / ".github/workflows").glob("*.yml")): workflow = json.loads(subprocess.check_output(["yq", "-o=json", ".", str(path)], text=True)) - self.assertNotRegex(json.dumps(workflow), r"macos-14(?:-large|-xlarge)?") + # Apple lanes run on the Xcode 27 images; older macOS labels lack the pinned Xcode. + self.assertNotRegex(json.dumps(workflow), r"\bmacos-(?:14|26)(?:-large|-xlarge)?\b|vcpu-macos-26\b") for job_id, job in workflow["jobs"].items(): steps = job.get("steps", []) for index, step in enumerate(steps): @@ -169,6 +170,9 @@ def test_no_workflow_uses_retired_mac_os_and_every_native_selector_has_checkout( self.assertEqual(len(checkout), 1, (path.name, job_id)) self.assertIs(checkout[0]["with"]["persist-credentials"], False) self.assertCountEqual(selectors, [ + ("agent-ci.yml", "desktop"), + ("agent-ci.yml", "provider"), + ("agent-store-performance.yml", "performance"), ("agent-desktop-build.yml", "macos-build"), ("agent-desktop-build.yml", "macos"), ("agent-desktop-build.yml", "macos-unsigned"), diff --git a/scripts/testing/storekit/run.sh b/scripts/testing/storekit/run.sh index 812b01f14..e0ac66d1a 100644 --- a/scripts/testing/storekit/run.sh +++ b/scripts/testing/storekit/run.sh @@ -2,10 +2,11 @@ set -euo pipefail # Invoke through .#apple so native linker variables are prepared here. -# A deliberate local comparison can select Xcode 26.6 without changing pins: +# A deliberate local comparison can select another installed Xcode without +# changing pins: # nix develop --no-update-lock-file .#apple -c env \ -# MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ -# DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +# MAPLE_NIX_XCODE_VERSION= MAPLE_NIX_XCODE_BUILD_VERSION= \ +# DEVELOPER_DIR=/Applications/Xcode_.app/Contents/Developer \ # bash scripts/testing/storekit/run.sh ... source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../../ci" && pwd)/_common.sh" use_xcode_toolchain From 273055cd078f1611a30e49ada701afbe4ea11093 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:35:08 +0000 Subject: [PATCH 11/14] ci: adapt workflow lint, Mach-O test and Agent linking to Xcode 27 - actionlint: allow the blacksmith-6vcpu-macos-27 and xcode-27 labels, which the pinned actionlint predates. - test-canonical-macho: Xcode 27's ld sorts local absolute symbols, so both fixtures came out in the same order. Swap the entries to recreate the input-order variant older linkers emitted. - Agent shell: link Rust with Xcode's clang as well. nixpkgs' ld64 cannot read the macOS 27 SDK's .tbd stubs (they list arm64e.x1), so build scripts failed to link (_strerror_r undefined). Co-Authored-By: Claude Opus 5.5 --- .github/actionlint.yaml | 4 +++- apps/maple-agent/flake.nix | 5 +++++ scripts/ci/test-canonical-macho.py | 33 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index af31490dd..e73872d40 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -3,5 +3,7 @@ self-hosted-runner: labels: - blacksmith-8vcpu-ubuntu-2404-arm - blacksmith-8vcpu-windows-2025 - - blacksmith-6vcpu-macos-26 + - blacksmith-6vcpu-macos-27 + # GitHub-hosted Xcode 27 image; newer than the pinned actionlint label list. + - xcode-27 - blacksmith-8vcpu-ubuntu-2404 diff --git a/apps/maple-agent/flake.nix b/apps/maple-agent/flake.nix index aa921e936..772e8d836 100644 --- a/apps/maple-agent/flake.nix +++ b/apps/maple-agent/flake.nix @@ -329,6 +329,11 @@ # nixpkgs' generic clang wrapper cannot infer that SDK boundary. export CC="$(/usr/bin/xcrun --find clang)" export CXX="$(/usr/bin/xcrun --find clang++)" + # Rust links through `cc`, which is nixpkgs' clang wrapper and its + # older ld64. That ld64 cannot read the macOS 27 SDK's .tbd stubs + # (they list the arm64e.x1 architecture), so link with Xcode too. + export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="$CC" + export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="$CC" export PATH="${xcrun}/bin:$PATH" unset maple_nix_developer_dir maple_nix_selected_developer_dir diff --git a/scripts/ci/test-canonical-macho.py b/scripts/ci/test-canonical-macho.py index 99e344fe0..94790a3af 100644 --- a/scripts/ci/test-canonical-macho.py +++ b/scripts/ci/test-canonical-macho.py @@ -71,6 +71,35 @@ def local_absolute_symbols(path): return symbols +def swap_local_absolute_symbols(path): + """Swap the first two local absolute nlist entries in place.""" + data = bytearray(Path(path).read_bytes()) + _, _, _, _, ncmds, _, _, _ = struct.unpack_from(" Date: Sun, 25 Oct 2026 01:18:45 +0000 Subject: [PATCH 12/14] ci: fix macOS 27 release builds and the Agent's Metal shaders - Research and Agent: stop stripping build scripts and proc-macros in release builds. rustc strips Apple outputs with rust-objcopy, which doesn't pad __LINKEDIT entries, so chained-fixup images (deployment target 12+) can end up with a 4-byte-aligned string table. macOS 27's dyld refuses such dylibs once they're linked against the macOS 27 SDK, so rustc could not load zerofrom-derive ("can't find crate"). Executables aren't checked, so shipped binaries keep their current strip settings. - Agent desktop build: build the macOS binary on GitHub's xcode-27 image. gpui compiles Metal shaders, and Blacksmith's macOS 27 image has no Metal Toolchain for Xcode 27.1 and cannot download it (the MobileAsset catalog fetch fails on every attempt). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/agent-desktop-build.yml | 5 ++++- apps/maple-agent/Cargo.toml | 8 ++++++++ apps/maple-research/frontend/src-tauri/Cargo.toml | 8 ++++++++ docs/repository-workflows.md | 12 +++++++----- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/agent-desktop-build.yml b/.github/workflows/agent-desktop-build.yml index 811ccd401..830f734c9 100644 --- a/.github/workflows/agent-desktop-build.yml +++ b/.github/workflows/agent-desktop-build.yml @@ -80,7 +80,10 @@ jobs: name: Build macOS binary (${{ matrix.variant }}) needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') && (github.event_name == 'pull_request' || (github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch'))) }} - runs-on: blacksmith-6vcpu-macos-27 + # gpui compiles Metal shaders, and Blacksmith's macOS 27 image has no Metal + # Toolchain for Xcode 27.1 and cannot download it (the MobileAsset catalog + # fetch fails). GitHub's xcode-27 image ships it, so build there meanwhile. + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false diff --git a/apps/maple-agent/Cargo.toml b/apps/maple-agent/Cargo.toml index 17e22bd43..81f1a1476 100644 --- a/apps/maple-agent/Cargo.toml +++ b/apps/maple-agent/Cargo.toml @@ -35,6 +35,14 @@ lto = "fat" codegen-units = 1 strip = true +# rustc strips with rust-objcopy, which can leave a dylib's LINKEDIT string table +# 4-byte aligned. macOS 27 refuses to load such dylibs when they are linked against +# its SDK, and the build loads its proc-macros as dylibs. Build scripts and +# proc-macros never ship, so leave them unstripped. Remove once the pinned Rust's +# rust-objcopy includes llvm/llvm-project#203680. +[profile.release.build-override] +strip = false + # The embedded proxy requires `maple-sdk` from the registry; resolve it to the same # in-tree source as the workspace dependency above (docs/sdk-publishing.md). [patch.crates-io] diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index 6731c76ee..04c7022f6 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -139,3 +139,11 @@ tao = { path = "patches/tao-0.37.1" } # to link (Tauri 2.12.3 made its own public: tauri-apps/tauri#16130); the # vendored copy only adds that keyword. Remove once upstream publishes the fix. tauri-plugin-sign-in-with-apple = { path = "patches/tauri-plugin-sign-in-with-apple-1.0.2" } + +# Release builds strip with rustc's rust-objcopy, which can leave a dylib's LINKEDIT +# string table 4-byte aligned. macOS 27 refuses to load such dylibs when they are +# linked against its SDK, and the build loads its proc-macros as dylibs. Build +# scripts and proc-macros never ship, so leave them unstripped. Remove once the +# pinned Rust's rust-objcopy includes llvm/llvm-project#203680. +[profile.release.build-override] +strip = false diff --git a/docs/repository-workflows.md b/docs/repository-workflows.md index 3ee3f61dd..09305f270 100644 --- a/docs/repository-workflows.md +++ b/docs/repository-workflows.md @@ -164,15 +164,17 @@ for the baseline, tradeoffs and cold/warm validation procedure. ## macOS runners and Apple toolchain -Research desktop/iOS builds, Agent release-binary jobs -and native proxy macOS archives use ARM64 `blacksmith-6vcpu-macos-27` runners. -Artifact verifiers, TestFlight uploads, and Agent preview packaging/protected -signing use GitHub's `xcode-27` image runners. Research signed builders retain +Research desktop/iOS builds and native proxy macOS archives use ARM64 +`blacksmith-6vcpu-macos-27` runners. Artifact verifiers, TestFlight uploads, +and the Agent's release binaries, preview packaging and protected signing use +GitHub's `xcode-27` image runners. The Agent binary build moves back to +Blacksmith once that image can install the Metal Toolchain gpui's shaders need. +Research signed builders retain their existing `desktop-signing` or `apple-signing` environment, so their provider receives that environment's secret context. Existing master pushes can build and upload independent Dev/Prod TestFlight artifacts after verification. The Agent's final signed-package provenance identifies its GitHub -packaging/signing job; Blacksmith is trusted for prebuilt binary integrity. +packaging/signing job. Research desktop and iOS builds and the Agent's macOS jobs select stable Xcode 27.1, build `27A9275`, from [`scripts/ci/apple-toolchain.json`](../scripts/ci/apple-toolchain.json) through From 10897a1f90ca95f2a2525b3fce5a4f63b1fffca6 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:50:35 +0000 Subject: [PATCH 13/14] ci(experiment): Agent macOS 27 builds on Blacksmith Experiment only, not for merge. Checks whether the Agent's macOS 27 work can stay on Blacksmith instead of GitHub's xcode-27 image: - Export Xcode 27.1's Metal Toolchain on GitHub xcode-27, import it on blacksmith-6vcpu-macos-27, compile a shader, then build the Agent workspace cold with compiled Metal shaders. - Without any Metal Toolchain, run the Agent app clippy with gpui's runtime_shaders feature. Co-Authored-By: Claude Opus 5.5 --- .../macos27-blacksmith-experiment.yml | 179 ++++++++++++++++++ scripts/ci/test_nix_workflows.py | 2 +- scripts/ci/test_select_xcode.py | 3 + 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/macos27-blacksmith-experiment.yml diff --git a/.github/workflows/macos27-blacksmith-experiment.yml b/.github/workflows/macos27-blacksmith-experiment.yml new file mode 100644 index 000000000..75a6cc7d4 --- /dev/null +++ b/.github/workflows/macos27-blacksmith-experiment.yml @@ -0,0 +1,179 @@ +# Experiment only, never merge. Can Agent macOS work stay on Blacksmith's macOS 27 +# image with Xcode 27.1? Two arms: +# 1. Export Xcode 27.1's Metal Toolchain on GitHub's xcode-27 image, import it on +# Blacksmith (whose own catalog download fails), and build the Agent with +# compiled Metal shaders. +# 2. Without any Metal Toolchain, run the Agent clippy with gpui's +# runtime_shaders feature (shaders compile at run time, not build time). +name: macOS 27 Blacksmith experiment + +on: + pull_request: + paths: + - .github/workflows/macos27-blacksmith-experiment.yml + +permissions: + contents: read + +concurrency: + group: macos27-blacksmith-experiment-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + export-metal: + name: Export Metal Toolchain (GitHub xcode-27) + runs-on: xcode-27 + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Download and export the Metal Toolchain + run: | + set -euxo pipefail + xcodebuild -version + xcodebuild -showComponent metalToolchain || true + export_dir="$RUNNER_TEMP/metal-export" + mkdir -p "$export_dir" + start=$SECONDS + xcodebuild -downloadComponent metalToolchain -exportPath "$export_dir" + echo "::notice::Metal Toolchain export took $((SECONDS - start))s" + find "$export_dir" -maxdepth 2 -print + du -sh "$export_dir" + find "$export_dir" -name ExportMetadata.plist -exec plutil -p {} \; || true + # Keep symlinks and modes intact; artifacts flatten them. + tar -C "$export_dir" -cf "$RUNNER_TEMP/metal-export.tar" . + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: metal-toolchain-xcode-27.1 + path: ${{ runner.temp }}/metal-export.tar + retention-days: 1 + compression-level: 0 + + blacksmith-imported-metal: + name: Blacksmith macos-27, imported Metal, Agent build + needs: export-metal + runs-on: blacksmith-6vcpu-macos-27 + timeout-minutes: 90 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Metal before import + run: | + xcodebuild -version + xcodebuild -showComponent metalToolchain || true + if xcrun -sdk macosx metal --version; then + echo "::notice::Metal Toolchain is already present on this Blacksmith image" + else + echo "::notice::Metal Toolchain missing before import, as expected" + fi + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: metal-toolchain-xcode-27.1 + path: ${{ runner.temp }}/metal + + - name: Import the Metal Toolchain + run: | + set -euxo pipefail + export_dir="$RUNNER_TEMP/metal-export" + mkdir -p "$export_dir" + tar -C "$export_dir" -xf "$RUNNER_TEMP/metal/metal-export.tar" + ls -la "$export_dir" + bundle="$(find "$export_dir" -maxdepth 1 \( -name '*.exportedBundle' -o -name '*.dmg' \) -print -quit)" + test -n "$bundle" + start=$SECONDS + if ! xcodebuild -importComponent metalToolchain -importPath "$bundle"; then + echo "::warning::unprivileged import failed; retrying with sudo" + sudo xcodebuild -importComponent metalToolchain -importPath "$bundle" + fi + echo "::notice::Metal Toolchain import took $((SECONDS - start))s" + xcodebuild -showComponent metalToolchain || true + + - name: Compile a Metal shader + run: | + set -euxo pipefail + cat > "$RUNNER_TEMP/probe.metal" <<'EOF' + #include + using namespace metal; + kernel void probe(device float *out [[buffer(0)]]) { out[0] = 1.0; } + EOF + xcrun -sdk macosx metal --version + xcrun -sdk macosx metal -c "$RUNNER_TEMP/probe.metal" -o "$RUNNER_TEMP/probe.air" + xcrun -sdk macosx metallib "$RUNNER_TEMP/probe.air" -o "$RUNNER_TEMP/probe.metallib" + echo "::notice::Metal shader compiled on Blacksmith macos-27" + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Build the Agent workspace (cold, compiled Metal shaders) + working-directory: apps/maple-agent + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo build --workspace --all-targets --locked + build=$((SECONDS - start)) + echo "::notice::Agent dev shell ${shell}s, cold cargo build --workspace --all-targets ${build}s" + printf '| Step | Seconds |\n| --- | ---: |\n| Agent dev shell | %s |\n| Cold workspace build (compiled shaders) | %s |\n' "$shell" "$build" >> "$GITHUB_STEP_SUMMARY" + + blacksmith-runtime-shaders: + name: Blacksmith macos-27, no Metal, runtime_shaders clippy + runs-on: blacksmith-6vcpu-macos-27 + timeout-minutes: 90 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Confirm no Metal Toolchain + run: | + if xcrun -sdk macosx metal --version; then + echo "::warning::Metal Toolchain present; this arm no longer proves the no-Metal path" + else + echo "::notice::No Metal Toolchain on this runner" + fi + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Clippy the Agent app with runtime shaders (cold) + working-directory: apps/maple-agent + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo clippy -p maple-agent-app --all-targets --locked \ + -F gpui-platform/runtime_shaders -- -D warnings + clippy=$((SECONDS - start)) + echo "::notice::Agent dev shell ${shell}s, cold clippy (runtime_shaders) ${clippy}s" + printf '| Step | Seconds |\n| --- | ---: |\n| Agent dev shell | %s |\n| Cold app clippy, runtime_shaders | %s |\n' "$shell" "$clippy" >> "$GITHUB_STEP_SUMMARY" diff --git a/scripts/ci/test_nix_workflows.py b/scripts/ci/test_nix_workflows.py index 691126aa0..674ad2864 100644 --- a/scripts/ci/test_nix_workflows.py +++ b/scripts/ci/test_nix_workflows.py @@ -19,7 +19,7 @@ "run": "bash scripts/ci/verify-nix-runtime.sh", } # Independent Agent provider, Linux release and attestation jobs retain pinned setup. -EXPECTED_INSTALLS = 79 +EXPECTED_INSTALLS = 81 @functools.cache diff --git a/scripts/ci/test_select_xcode.py b/scripts/ci/test_select_xcode.py index 5f4dbaffe..8cb1e9f47 100644 --- a/scripts/ci/test_select_xcode.py +++ b/scripts/ci/test_select_xcode.py @@ -173,6 +173,9 @@ def test_no_workflow_uses_retired_mac_os_and_every_native_selector_has_checkout( ("agent-ci.yml", "desktop"), ("agent-ci.yml", "provider"), ("agent-store-performance.yml", "performance"), + ("macos27-blacksmith-experiment.yml", "export-metal"), + ("macos27-blacksmith-experiment.yml", "blacksmith-imported-metal"), + ("macos27-blacksmith-experiment.yml", "blacksmith-runtime-shaders"), ("agent-desktop-build.yml", "macos-build"), ("agent-desktop-build.yml", "macos"), ("agent-desktop-build.yml", "macos-unsigned"), From 832adb213e1854045a63b6e985580ae2a4ca1a64 Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:31:59 +0000 Subject: [PATCH 14/14] ci(experiment): probe Blacksmith 8/16/32 vCPU Linux for the Agent Experiment only. Checks that blacksmith-16vcpu-ubuntu-2404 and blacksmith-32vcpu-ubuntu-2404 provision without a pre-created scale set, and times a cold Agent clippy and test build on each size. Co-Authored-By: Claude Opus 5.5 --- .github/actionlint.yaml | 2 + .github/workflows/linux-runner-size-probe.yml | 61 +++++++++++++++++++ scripts/ci/test_nix_workflows.py | 2 +- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/linux-runner-size-probe.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index e73872d40..7c9063c86 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -7,3 +7,5 @@ self-hosted-runner: # GitHub-hosted Xcode 27 image; newer than the pinned actionlint label list. - xcode-27 - blacksmith-8vcpu-ubuntu-2404 + - blacksmith-16vcpu-ubuntu-2404 + - blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/linux-runner-size-probe.yml b/.github/workflows/linux-runner-size-probe.yml new file mode 100644 index 000000000..411f21e59 --- /dev/null +++ b/.github/workflows/linux-runner-size-probe.yml @@ -0,0 +1,61 @@ +# Experiment only, never merge. Does blacksmith-16vcpu-ubuntu-2404 (and 32) +# provision without a pre-created scale set, and how much faster is a cold Agent +# clippy + test build on 16/32 vCPU than on 8? +name: Linux runner size probe + +on: + pull_request: + paths: + - .github/workflows/linux-runner-size-probe.yml + +permissions: + contents: read + +concurrency: + group: linux-runner-size-probe-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + probe: + name: Agent cold build on ${{ matrix.runner }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + runner: [blacksmith-8vcpu-ubuntu-2404, blacksmith-16vcpu-ubuntu-2404, blacksmith-32vcpu-ubuntu-2404] + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Machine + run: | + echo "::notice::$(nproc) CPUs, $(free -g | awk '/Mem:/{print $2}') GB RAM on ${{ matrix.runner }}" + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Cold Agent clippy and test build + working-directory: apps/maple-agent + env: + RUSTFLAGS: -D warnings + CARGO_INCREMENTAL: "0" + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo clippy --workspace --all-targets --locked -- -D warnings + clippy=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo test --workspace --locked --no-run + tests=$((SECONDS - start)) + echo "::notice::${{ matrix.runner }}: shell ${shell}s, cold clippy ${clippy}s, cold test build ${tests}s" diff --git a/scripts/ci/test_nix_workflows.py b/scripts/ci/test_nix_workflows.py index 674ad2864..4ab17d8ec 100644 --- a/scripts/ci/test_nix_workflows.py +++ b/scripts/ci/test_nix_workflows.py @@ -19,7 +19,7 @@ "run": "bash scripts/ci/verify-nix-runtime.sh", } # Independent Agent provider, Linux release and attestation jobs retain pinned setup. -EXPECTED_INSTALLS = 81 +EXPECTED_INSTALLS = 82 @functools.cache