winit-0.29.15/.cargo/config.toml000064400000000000000000000001071046102023000144450ustar 00000000000000[alias] run-wasm = ["run", "--release", "--package", "run-wasm", "--"] winit-0.29.15/.cargo_vcs_info.json0000644000000001360000000000100123440ustar { "git": { "sha1": "8f08fbae8990fb71caae4808e9518bdecb76f91b" }, "path_in_vcs": "" }winit-0.29.15/.gitattributes000064400000000000000000000007431046102023000140330ustar 00000000000000# Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain winit-0.29.15/.github/CODEOWNERS000064400000000000000000000017241046102023000140730ustar 00000000000000# Core maintainers: # - @msiglreith # - @kchibisov # - @madsmtm # - @maroider # Android /src/platform/android.rs @msiglreith /src/platform_impl/android @msiglreith # iOS /src/platform/ios.rs @madsmtm /src/platform_impl/ios @madsmtm # Unix /src/platform_impl/linux/mod.rs @kchibisov # Wayland /src/platform/wayland.rs @kchibisov /src/platform_impl/linux/wayland @kchibisov # X11 /src/platform/x11.rs @kchibisov /src/platform_impl/linux/x11 @kchibisov # macOS /src/platform/macos.rs @madsmtm /src/platform_impl/macos @madsmtm # Web (no maintainer) /src/platform/web.rs @daxpedda /src/platform_impl/web @daxpedda # Windows /src/platform/windows.rs @msiglreith /src/platform_impl/windows @msiglreith # Orbital (Redox OS) /src/platform/orbital.rs @jackpot51 /src/platform_impl/orbital @jackpot51 winit-0.29.15/.github/PULL_REQUEST_TEMPLATE.md000064400000000000000000000007361046102023000163030ustar 00000000000000- [ ] Tested on all platforms changed - [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior - [ ] Created or updated an example program if it would help users understand this functionality - [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented winit-0.29.15/.github/workflows/ci.yml000064400000000000000000000171411046102023000156530ustar 00000000000000name: CI on: pull_request: push: branches: [master] jobs: fmt: name: Check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check Formatting run: cargo fmt -- --check tests: name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }} runs-on: ${{ matrix.platform.os }} strategy: fail-fast: false matrix: toolchain: [stable, nightly, '1.65.0'] platform: # Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! - { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, } - { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } - { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' } - { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' } - { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } - { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, } - { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, } exclude: # Android is tested on stable-3 - toolchain: '1.65.0' platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } include: - toolchain: '1.69.0' platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' } env: # Set more verbose terminal output CARGO_TERM_VERBOSE: true RUST_BACKTRACE: 1 # Faster compilation and error on warnings RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings' RUSTDOCFLAGS: '--deny=warnings' OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }} CMD: ${{ matrix.platform.cmd }} steps: - uses: actions/checkout@v3 - name: Restore cache of cargo folder # We use `restore` and later `save`, so that we can create the key after # the cache has been downloaded. # # This could be avoided if we added Cargo.lock to the repository. uses: actions/cache/restore@v3 with: # https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }} - name: Generate lockfile # Also updates the crates.io index run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0 - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') run: sudo apt-get update && sudo apt-get install gcc-multilib - name: Cache cargo-apk if: contains(matrix.platform.target, 'android') id: cargo-apk-cache uses: actions/cache@v3 with: path: ~/.cargo/bin/cargo-apk # Change this key if we update the required cargo-apk version key: cargo-apk-v0-9-7 - uses: dtolnay/rust-toolchain@master if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') with: toolchain: stable - name: Install cargo-apk if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') run: cargo install cargo-apk --version=^0.9.7 --locked - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }} targets: ${{ matrix.platform.target }} components: clippy - name: Check documentation run: cargo doc --no-deps $OPTIONS --document-private-items - name: Build crate run: cargo $CMD build $OPTIONS - name: Build tests if: > !contains(matrix.platform.target, 'redox') && matrix.toolchain != '1.65.0' run: cargo $CMD test --no-run $OPTIONS - name: Run tests if: > !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'redox') && matrix.toolchain != '1.65.0' run: cargo $CMD test $OPTIONS - name: Lint with clippy if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features') run: cargo clippy --all-targets $OPTIONS -- -Dwarnings - name: Build tests with serde enabled if: > !contains(matrix.platform.target, 'redox') && matrix.toolchain != '1.65.0' run: cargo $CMD test --no-run $OPTIONS --features serde - name: Run tests with serde enabled if: > !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32') && !contains(matrix.platform.target, 'redox') && matrix.toolchain != '1.65.0' run: cargo $CMD test $OPTIONS --features serde # See restore step above - name: Save cache of cargo folder uses: actions/cache/save@v3 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }} cargo-deny: name: Run cargo-deny on ${{ matrix.platform.name }} runs-on: ubuntu-latest # TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved strategy: fail-fast: false matrix: platform: - { name: 'Android', target: aarch64-linux-android } - { name: 'iOS', target: aarch64-apple-ios } - { name: 'Linux', target: x86_64-unknown-linux-gnu } - { name: 'macOS', target: x86_64-apple-darwin } - { name: 'Redox OS', target: x86_64-unknown-redox } - { name: 'web', target: wasm32-unknown-unknown } - { name: 'Windows', target: x86_64-pc-windows-gnu } steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: command: check log-level: error arguments: --all-features --target ${{ matrix.platform.target }} winit-0.29.15/.gitignore000064400000000000000000000001031046102023000131160ustar 00000000000000Cargo.lock target/ rls/ .vscode/ *~ *.wasm *.ts *.js #*# .DS_Store winit-0.29.15/CHANGELOG.md000064400000000000000000002637261046102023000127650ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section). And please only add new entries to the top of this list, right below the `# Unreleased` header. # Unreleased # 0.29.15 - On X11, fix crash due to xsettings query on systems with incomplete xsettings. # 0.29.14 - On X11/Wayland, fix `text` and `text_with_all_modifiers` not being `None` during compose. - On Wayland, don't reapply cursor grab when unchanged. - On X11, fix a bug where some mouse events would be unexpectedly filtered out. # 0.29.13 - On Web, fix possible crash with `ControlFlow::Wait` and `ControlFlow::WaitUntil`. # 0.29.12 - On X11, fix use after free during xinput2 handling. - On X11, filter close to zero values in mouse device events # 0.29.11 - On Wayland, fix DeviceEvent::Motion not being sent - On X11, don't require XIM to run. - On X11, fix xkb state not being updated correctly sometimes leading to wrong input. - Fix compatibility with 32-bit platforms without 64-bit atomics. - On macOS, fix incorrect IME cursor rect origin. - On X11, fix swapped instance and general class names. - On Windows, fixed a race condition when sending an event through the loop proxy. - On Wayland, disable `Occluded` event handling. - On X11, reload dpi on `_XSETTINGS_SETTINGS` update. - On X11, fix deadlock when adjusting DPI and resizing at the same time. - On Wayland, fix `Focused(false)` being send when other seats still have window focused. - On Wayland, fix `Window::set_{min,max}_inner_size` not always applied. - On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop. - On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform. - On Orbital, fix `logical_key` and `text` not reported in `KeyEvent`. - On Orbital, implement `KeyEventExtModifierSupplement`. - On Orbital, map keys to `NamedKey` when possible. - On Orbital, implement `set_cursor_grab`. - On Orbital, implement `set_cursor_visible`. - On Orbital, implement `drag_window`. - On Orbital, implement `drag_resize_window`. - On Orbital, implement `set_transparent`. - On Orbital, implement `set_visible`. - On Orbital, implement `is_visible`. - On Orbital, implement `set_resizable`. - On Orbital, implement `is_resizable`. - On Orbital, implement `set_maximized`. - On Orbital, implement `is_maximized`. - On Orbital, implement `set_decorations`. - On Orbital, implement `is_decorated`. - On Orbital, implement `set_window_level`. - On Orbital, emit `DeviceEvent::MouseMotion`. - On Wayland, fix title in CSD not updated from `AboutToWait`. # 0.29.10 - On Web, account for canvas being focused already before event loop starts. - On Web, increase cursor position accuracy. # 0.29.9 - On X11, fix `NotSupported` error not propagated when creating event loop. - On Wayland, fix resize not issued when scale changes - On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`. - On macOS, report correct logical key when Ctrl or Cmd is pressed. # 0.29.8 - On X11, fix IME input lagging behind. - On X11, fix `ModifiersChanged` not sent from xdotool-like input - On X11, fix keymap not updated from xmodmap. - On X11, reduce the amount of time spent fetching screen resources. - On Wayland, fix `Window::request_inner_size` being overwritten by resize. - On Wayland, fix `Window::inner_size` not using the correct rounding. # 0.29.7 - On X11, fix `Xft.dpi` reload during runtime. - On X11, fix window minimize. # 0.29.6 - On Web, fix context menu not being disabled by `with_prevent_default(true)`. - On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window. - Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation # 0.29.5 - On macOS, remove spurious error logging when handling `Fn`. - On X11, fix an issue where floating point data from the server is misinterpreted during a drag and drop operation. - On X11, fix a bug where focusing the window would panic. - On macOS, fix `refresh_rate_millihertz`. - On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported. - On X11, fix `Xft.dpi` detection from Xresources. - On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`. - On Wayland, fix resize being sent on focus change. - On Windows, fix `set_ime_cursor_area`. # 0.29.4 - Fix crash when running iOS app on macOS. - On X11, check common alternative cursor names when loading cursor. - On X11, reload the DPI after a property change event. - On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread. - On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account. - On macOS, send a `Resized` event after each `ScaleFactorChanged` event. - On Wayland, fix `wl_surface` being destroyed before associated objects. - On macOS, fix assertion when pressing `Fn` key. # 0.29.3 - On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible. - On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature. - On Wayland, ignore resize requests when the window is fully tiled. - On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size. - On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`. - On Windows, add support for `Window::set_transparent`. - On macOS, fix deadlock when entering a nested event loop from an event handler. - On macOS, add support for `Window::set_blur`. # 0.29.2 - **Breaking:** Bump MSRV from `1.60` to `1.65`. - **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android. - **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`. - **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`. - **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`. - **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely. - **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`. - **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result` - **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables. - **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back. - **Breaking:** remove `DeviceEvent::Text`. - **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`. - **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately. - **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more. - **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size. - **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) - **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767)) - `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`. - On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()` - **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking:** Remove all deprecated `modifiers` fields. - **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants. - **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900)) - **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`. - **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone. - **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer. - **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web. - **Breaking:** On Web, `instant` is now replaced by `web_time`. - **Breaking:** On Web, dropped support for Safari versions below 13.1. - **Breaking:** On Web, the canvas output bitmap size is no longer adjusted. - **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`. - **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types. - **Breaking:** `CursorIcon::Arrow` was removed. - **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`. - **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate. - **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`. - **Breaking:** Overhaul keyboard input handling. - Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`. - Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`. - Change `Event::Key` to contain a `RawKeyEvent`. - Remove `Event::ReceivedCharacter`. In its place, you should use `KeyEvent.text` in combination with `WindowEvent::Ime`. - Replace `VirtualKeyCode` with the `Key` enum. - Replace `ScanCode` with the `KeyCode` enum. - Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`. - Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`. - Add `KeyCode` to refer to keys (roughly) by their physical location. - Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't understand. - Add `Key` to represent the keys after they've been interpreted by the active (software) keyboard layout. - Add `NamedKey` to represent the categorized keys. - Add `NativeKey` to represent raw `Key`s which Winit doesn't understand. - Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing, but can appear simultaneously in different spots on the same keyboard layout. - Add `Window::reset_dead_keys` to enable application-controlled cancellation of dead key sequences. - Add `KeyEventExtModifierSupplement` to expose additional (and less portable) interpretations of a given key-press. - Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and `PhysicalKey`. - `ModifiersChanged` now uses dedicated `Modifiers` struct. - Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead: - `platform::windows::HINSTANCE`. - `WindowExtWindows::hinstance`. - `WindowExtWindows::hwnd`. - `WindowExtIOS::ui_window`. - `WindowExtIOS::ui_view_controller`. - `WindowExtIOS::ui_view`. - `WindowExtMacOS::ns_window`. - `WindowExtMacOS::ns_view`. - `EventLoopWindowTargetExtWayland::wayland_display`. - `WindowExtWayland::wayland_surface`. - `WindowExtWayland::wayland_display`. - `WindowExtX11::xlib_window`. - `WindowExtX11::xlib_display`. - `WindowExtX11::xlib_screen_id`. - `WindowExtX11::xcb_connection`. - Reexport `raw-window-handle` in `window` module. - Add `ElementState::is_pressed`. - Add `Window::pre_present_notify` to notify winit before presenting to the windowing system. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. - Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now. - Implement `AsFd`/`AsRawFd` for `EventLoop` on X11 and Wayland. - Implement `PartialOrd` and `Ord` for `MouseButton`. - Implement `PartialOrd` and `Ord` on types in the `dpi` module. - Make `WindowBuilder` `Send + Sync`. - Make iOS `MonitorHandle` and `VideoMode` usable from other threads. - Make iOS windows usable from other threads. - On Android, add force data to touch events. - On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually. - On Android, fix `DeviceId` to contain device id's. - On Orbital, fix `ModifiersChanged` not being sent. - On Wayland, `Window::outer_size` now accounts for **client side** decorations. - On Wayland, add `Window::drag_resize_window` method. - On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable. - On Wayland, fix `TouchPhase::Canceled` being sent for moved events. - On Wayland, fix forward compatibility issues. - On Wayland, fix initial window size not restored for maximized/fullscreened on startup window. - On Wayland, fix maximized startup not taking full size on GNOME. - On Wayland, fix maximized window creation and window geometry handling. - On Wayland, fix window not checking that it actually got initial configure event. - On Wayland, make double clicking and moving the CSD frame more reliable. - On Wayland, support `Occluded` event with xdg-shell v6 - On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor. - On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback. - On Web, `EventLoopProxy` now implements `Send`. - On Web, `Window` now implements `Send` and `Sync`. - On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position. - On Web, add Fullscreen API compatibility for Safari. - On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support. - On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation. - On Web, allow event loops to be recreated with `spawn`. - On Web, enable event propagation. - On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time. - On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window. - On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation. - On Web, fix pen treated as mouse input. - On Web, fix pointer button events not being processed when a buttons is already pressed. - On Web, fix scale factor resize suggestion always overwriting the canvas size. - On Web, fix some `WindowBuilder` methods doing nothing. - On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties. - On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events. - On Web, fix touch input not gaining or loosing focus. - On Web, fix touch location to be as accurate as mouse position. - On Web, handle coalesced pointer events, which increases the resolution of pointer inputs. - On Web, implement `Window::focus_window()`. - On Web, implement `Window::set_(min|max)_inner_size()`. - On Web, implement `WindowEvent::Occluded`. - On Web, never return a `MonitorHandle`. - On Web, prevent clicks on the canvas to select text. - On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected. - On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`. - On Web, respect `EventLoopWindowTarget::listen_device_events()` settings. - On Web, scale factor and dark mode detection are now more robust. - On Web, send mouse position on button release as well. - On Web, take all transient activations on the canvas and window into account to queue a fullscreen request. - On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events. - On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size. - On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop. - On Web: fix position of touch events to be relative to the canvas. - On Windows, add `drag_resize_window` method support. - On Windows, add horizontal MouseWheel `DeviceEvent`. - On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name. - On Windows, fix IME APIs not working when from non event loop thread. - On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse. - On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`. - On Windows, port to `windows-sys` version 0.48.0. - On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window. - On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`. - On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses. - On X11, set `visual_id` in returned `raw-window-handle`. - On iOS, add ability to change the status bar style. - On iOS, add force data to touch events when using the Apple Pencil. - On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`. - On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground. - On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`. - On macOS, fix assertion when pressing `Globe` key. - On macOS, fix crash in `window.set_minimized(false)`. - On macOS, fix crash when dropping `Window`. # 0.28.7 - Fix window size sometimes being invalid when resizing on macOS 14 Sonoma. # 0.28.6 - On macOS, fixed memory leak when getting monitor handle. - On macOS, fix `Backspace` being emitted when clearing preedit with it. # 0.28.5 - On macOS, fix `key_up` being ignored when `Ime` is disabled. # 0.28.4 - On macOS, fix empty marked text blocking regular input. - On macOS, fix potential panic when getting refresh rate. - On macOS, fix crash when calling `Window::set_ime_position` from another thread. # 0.28.3 - Fix macOS memory leaks. # 0.28.2 - Implement `HasRawDisplayHandle` for `EventLoop`. - On macOS, set resize increments only for live resizes. - On Wayland, fix rare crash on DPI change - Web: Added support for `Window::theme`. - On Wayland, fix rounding issues when doing resize. - On macOS, fix wrong focused state on startup. - On Windows, fix crash on setting taskbar when using Visual Studio debugger. - On macOS, resize simple fullscreen windows on windowDidChangeScreen events. # 0.28.1 - On Wayland, fix crash when dropping a window in multi-window setup. # 0.28.0 - On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`. - On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`. - On Windows, fix window size for maximized, undecorated windows. - On Windows and macOS, add `WindowBuilder::with_active`. - Add `Window::is_minimized`. - On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit. - Add `Window::has_focus`. - On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey. - **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`. - **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`. - On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window. - On Windows, fix left mouse button release event not being sent after `Window::drag_window`. - On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more. - On macOS, fix panic when getting current monitor without any monitor attached. - On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc). - On Windows, macOS, X11 and Wayland, add `Window::set_theme`. - **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`. - On Windows, revert window background to an empty brush to avoid white flashes when changing scaling. - **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`. - On Windows, MacOS and X11, add always on bottom APIs. - On Windows, fix the value in `MouseButton::Other`. - On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs. - **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this. - On MacOS and Windows, add `Window::set_content_protected`. - On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`. - On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one. - On Windows and macOS, add `Window::title` to query the current window title. - On Windows, fix focusing menubar when pressing `Alt`. - On MacOS, made `accepts_first_mouse` configurable. - Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`. - Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS. - macOS/iOS: Use `objc2` instead of `objc` internally. - **Breaking:** Bump MSRV from `1.57` to `1.60`. - **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed. - **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`. - Removed `parking_lot` dependency. - **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`. - **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop. - On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations. - On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations. - On Wayland, if not otherwise specified use upstream automatic CSD theme selection. - On X11, added `WindowExtX11::with_parent` to create child windows. - Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland. - On macOS, added support for `WindowEvent::ThemeChanged`. - **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`. - **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`. - Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels. - **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444)) - **Breaking:** Removed support for `raw-window-handle` version `0.4` - On Wayland, `RedrawRequested` not emitted during resize. - Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`. - **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface. - Added Orbital support for Redox OS - On X11, added `drag_resize_window` method. - Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS. - On macOS, fix the mouse buttons other than left/right/middle being reported as middle. - On Wayland, support fractional scaling via the wp-fractional-scale protocol. - On web, fix removal of mouse event listeners from the global object upon window distruction. - Add WindowAttributes getter to WindowBuilder to allow introspection of default values. - Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only. # 0.27.5 - On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes. # 0.27.4 - On Windows, emit `ReceivedCharacter` events on system keybindings. - On Windows, fixed focus event emission on minimize. - On X11, fixed IME crashing during reload. # 0.27.3 - On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window. - On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled. - On Windows, fixed ALT+Space shortcut to open window menu. - On Wayland, fixed `Ime::Preedit` not being sent on IME reset. - Fixed unbound version specified for `raw-window-handle` leading to compilation failures. - Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit. - On X11, fixed IME context picking by querying for supported styles beforehand. # 0.27.2 (2022-8-12) - On macOS, fixed touch phase reporting when scrolling. - On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change). - On Windows, respect min/max inner sizes when creating the window. - For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait - On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`. - On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame. # 0.27.1 (2022-07-30) - The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested. - On X11, fix crash on start due to inability to create an IME context without any preedit. # 0.27.0 (2022-07-26) - On Windows, fix hiding a maximized window. - On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`. - On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception. - Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11. - On X11, fix events for caps lock key not being sent - Build docs on `docs.rs` for iOS and Android as well. - **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`. - Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute. - On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q. - On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning. - On Wayland, fix bug where the cursor wouldn't hide in GNOME. - On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events. - On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`. - On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`. - On Windows, remove internally unique DC per window. - On macOS, remove the need to call `set_ime_position` after moving the window. - Added `Window::is_visible`. - Added `Window::is_resizable`. - Added `Window::is_decorated`. - On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait` - On X11, fix scale factor calculation when the only monitor is reconnected - On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`. - On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends. - **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`. - Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable. - **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs. - Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it. - Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead. - **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods). - **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods). - **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods). - **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies. - The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings. - On Wayland, fix polling during consecutive `EventLoop::run_return` invocations. - On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen` - On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found. - Added helper methods on `ControlFlow` to set its value. - On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor sent a cancel or frame event. - On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms. - **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`. - On Wayland, fallback CSD was replaced with proper one: - `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder. - `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window. - `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting. - `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library. - `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering. - On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`. - On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages - **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms. - Added `Window::set_ime_allowed` supported on desktop platforms. - **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled. - On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`. - **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level. - Implemented `Default` on `EventLoop<()>`. - Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`. - **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior. - On Wayland, add support for `Window::set_cursor_position`. - Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value. - `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once. - Added `From` for `WindowId` and `From` for `u64`. - Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. - **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. - On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. - On Windows, fix focus events being sent to inactive windows. - **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`. - On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib. - On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`. - All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability. - **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down. - On MacOS, fix deadlock when calling `set_maximized` from event loop. # 0.26.1 (2022-01-05) - Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions. - On Web, implement cursor grabbing through the pointer lock API. - On X11, add mappings for numpad comma, numpad enter, numlock and pause. - On macOS, fix Pinyin IME input by reverting a change that intended to improve IME. - On Windows, fix a crash with transparent windows on Windows 11. # 0.26.0 (2021-12-01) - Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading. - On X11, bump `mio` to 0.8. - On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`. - On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0. - On X11, select an appropriate visual for transparency if is requested - On Wayland and X11, fix diagonal window resize cursor orientation. - On macOS, drop the event callback before exiting. - On Android, implement `Window::request_redraw` - **Breaking:** On Web, remove the `stdweb` backend. - Added `Window::focus_window`to bring the window to the front and set input focus. - On Wayland and X11, implement `is_maximized` method on `Window`. - On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`. - On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events. - On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`. - **Breaking:** On Wayland, Theme trait and its support types are dropped. - On Wayland, bump `smithay-client-toolkit` to 0.15.1. - On Wayland, implement `request_user_attention` with `xdg_activation_v1`. - On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected. - On X11, if RANDR based scale factor is higher than 20 reset it to 1 - On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries. - **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5. - On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`. - On macOS, fix native file dialogs hanging the event loop. - On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland` - On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode. - On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup. # 0.25.0 (2021-05-15) - **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy` - On macOS, wait with activating the application until the application has initialized. - On macOS, fix creating new windows when the application has a main menu. - On Windows, fix fractional deltas for mouse wheel device events. - On macOS, fix segmentation fault after dropping the main window. - On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode. - Added `is_maximized` method to `Window`. - On Windows, fix bug where clicking the decoration bar would make the cursor blink. - On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor. - On macOS, wake up the event loop immediately when a redraw is requested. - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. - On Android, unimplemented events are marked as unhandled on the native event loop. - On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time. - On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`. - On macOS, fix objects captured by the event loop closure not being dropped on panic. - On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme. - On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements. - Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11. - Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland. - On X11, bump `mio` to 0.7. - On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows. - On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows. - On macOS, emit `RedrawRequested` events immediately while the window is being resized. - Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`. - On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`) - On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click". - Implement mint (math interoperability standard types) conversions (under feature flag `mint`). # 0.24.0 (2020-12-09) - On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory. - On Windows, implement `Window::set_ime_position`. - **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. - **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`. - On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. - On Windows, fix bug causing message boxes to appear delayed. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - **Breaking:** On Windows, include prefix byte in scancodes. - On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`. - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. - On Windows, fix use-after-free crash during window destruction. - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. - On macOS, fix compilation when targeting aarch64. - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. - Added `request_user_attention` method to `Window`. - **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. - **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. - On Windows, fix bug causing message boxes to appear delayed. - On Android, support multi-touch. - On Wayland, extra mouse buttons are not dropped anymore. - **Breaking**: `MouseButton::Other` now uses `u16`. # 0.23.0 (2020-10-02) - On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode. - On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling - On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. - On Web, use mouse events if pointer events aren't supported. This affects Safari. - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. - On Web, implement mouse capturing for click-dragging out of the canvas. - On Web, fix `ControlFlow::Exit` not properly handled. - On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. - **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`. - On macOS, fix inverted horizontal scroll. - **Breaking:** `current_monitor` now returns `Option`. - **Breaking:** `primary_monitor` now returns `Option`. - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 - On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. - On iOS, fixed starting the app in landscape where the view still had portrait dimensions. - Deprecate the stdweb backend, to be removed in a future release - **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. - Added `Asterisk` and `Plus` virtual key codes. - On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page. - On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped. - On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed. - On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed. - **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. - **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. - On MacOS, fix `WindowEvent::Moved` ignoring the scale factor. - On Wayland, add missing virtual keycodes. - On Wayland, implement proper `set_cursor_grab`. - On Wayland, the cursor will use similar icons if the requested one isn't available. - On Wayland, right clicking on client side decorations will request application menu. - On Wayland, fix tracking of window size after state changes. - On Wayland, fix client side decorations not being hidden properly in fullscreen. - On Wayland, fix incorrect size event when entering fullscreen with client side decorations. - On Wayland, fix `resizable` attribute not being applied properly on startup. - On Wayland, fix disabled repeat rate not being handled. - On Wayland, fix decoration buttons not working after tty switch. - On Wayland, fix scaling not being applied on output re-enable. - On Wayland, fix crash when `XCURSOR_SIZE` is `0`. - On Wayland, fix pointer getting created in some cases without pointer capability. - On Wayland, on kwin, fix space between window and decorations on startup. - **Breaking:** On Wayland, `Theme` trait was reworked. - On Wayland, disable maximize button for non-resizable window. - On Wayland, added support for `set_ime_position`. - On Wayland, fix crash on startup since GNOME 3.37.90. - On X11, fix incorrect modifiers state on startup. # 0.22.2 (2020-05-16) - Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. - On macOS, updated core-* dependencies and cocoa # 0.22.1 (2020-04-16) - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling - On macOS, fix `EventLoopProxy` leaking memory for every instance. # 0.22.0 (2020-03-09) - On Windows, fix minor timing issue in wait_until_time_or_msg - On Windows, rework handling of request_redraw() to address panics. - On macOS, fix `set_simple_screen` to remember frame excluding title bar. - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend - On Wayland, Add HiDPI cursor support - On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. - **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. - On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource - Add `BadIcon::OsError` variant for when OS icon functionality fails - On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode - Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. # 0.21.0 (2020-02-04) - On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows. - On macOS, fix set_minimized(true) works only with decorations. - On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`. - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. - **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends - On Wayland, fix coordinates in mouse events when scale factor isn't 1 - On Web, add the ability to provide a custom canvas - **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration # 0.20.0 (2020-01-05) - On X11, fix `ModifiersChanged` emitting incorrect modifier change events - **Breaking**: Overhaul how Winit handles DPI: - Window functions and events now return `PhysicalSize` instead of `LogicalSize`. - Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types. - `hidpi_factor` has been renamed to `scale_factor`. - `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS resizes the window in response to the change. - On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`. - `Size` and `Position` types are now generic over their exact pixel type. # 0.20.0 Alpha 6 (2020-01-03) - On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not terminating on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. - On Windows, theme the title bar according to whether the system theme is "Light" or "Dark". - Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows. - **Breaking**: Changes to the `RedrawRequested` event (#1041): - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - `EventsCleared` has been renamed to `MainEventsCleared`. - `RedrawRequested` is now issued only after `MainEventsCleared`. - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. - **Breaking**: Change `ModifiersState` to a `bitflags` struct. - On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`. - On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted. - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. - Add `DeviceEvent::ModifiersChanged`. - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. - On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output. - On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`. # 0.20.0 Alpha 5 (2019-12-09) - On macOS, fix application termination on `ControlFlow::Exit` - On Windows, fix missing `ReceivedCharacter` events when Alt is held. - On macOS, stop emitting private corporate characters in `ReceivedCharacter` events. - On X11, fix misreporting DPI factor at startup. - On X11, fix events not being reported when using `run_return`. - On X11, fix key modifiers being incorrectly reported. - On X11, fix window creation hanging when another window is fullscreen. - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. - Change `EventLoopClosed` to contain the original event. - **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, indicating that the event is generated by winit. - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. # 0.20.0 Alpha 4 (2019-10-18) - Add web support via the 'stdweb' or 'web-sys' features - On Windows, implemented function to get HINSTANCE - On macOS, implement `run_return`. - On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. - On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. - On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. - On Windows, unset `maximized` when manually changing the window's position or size. - On Windows, add touch pressure information for touch events. - On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. - Officially remove the Emscripten backend. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On iOS, fix panic upon closing the app. - On X11, allow setting mulitple `XWindowType`s. - On iOS, fix null window on initial `HiDpiFactorChanged` event. - On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On macOS, fix events not being emitted during modal loops, such as when windows are being resized by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. - Always dispatch a `RedrawRequested` event after creating a new window. - On X11, return dummy monitor data to avoid panicking when no monitors exist. - On X11, prevent stealing input focus when creating a new window. Only steal input focus when entering fullscreen mode. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced - On Wayland, add support for set_cursor_visible and set_cursor_grab. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced. - Removed `derivative` crate dependency. - On Wayland, add support for set_cursor_icon. - Use `impl Iterator` instead of `AvailableMonitorsIter` consistently. - On macOS, fix fullscreen state being updated after entering fullscreen instead of before, resulting in `Window::fullscreen` returning the old state in `Resized` events instead of reflecting the new fullscreen state - On X11, fix use-after-free during window creation - On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen. - On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor. - Prevent `EventLoop::new` and `EventLoop::with_user_event` from getting called outside the main thread. - This is because some platforms cannot run the event loop outside the main thread. Preventing this reduces the potential for cross-platform compatibility gotchyas. - On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread. - On Wayland, drop resize events identical to the current window size. - On Windows, fix window rectangle not getting set correctly on high-DPI systems. # 0.20.0 Alpha 3 (2019-08-14) - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. - On Windows, screen saver won't start if the window is in fullscreen mode. - Change all occurrences of the `new_user_event` method to `with_user_event`. - On macOS, the dock and the menu bar are now hidden in fullscreen mode. - `Window::set_fullscreen` now takes `Option` where `Fullscreen` consists of `Fullscreen::Exclusive(VideoMode)` and `Fullscreen::Borderless(MonitorHandle)` variants. - Adds support for exclusive fullscreen mode. - On iOS, add support for hiding the home indicator. - On iOS, add support for deferring system gestures. - On iOS, fix a crash that occurred while acquiring a monitor's name. - On iOS, fix armv7-apple-ios compile target. - Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy`. - On iOS, disable overscan compensation for external displays (removes black bars surrounding the image). - On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait. - On iOS, add `set_prefers_status_bar_hidden` extension function instead of hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. - On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application. - On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number. - On iOS, RedrawRequested now works for gl/metal backed views. - On iOS, RedrawRequested is generally ordered after EventsCleared. # 0.20.0 Alpha 2 (2019-07-09) - On X11, non-resizable windows now have maximize explicitly disabled. - On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile` and `WindowEvent::HoveredFile`. - On Mac, implement `DeviceEvent::Button`. - Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`. - On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor. - Revert the use of invisible surfaces in Wayland, which introduced graphical glitches with OpenGL (#835) - On X11, implement `_NET_WM_PING` to allow desktop environment to kill unresponsive programs. - On Windows, when a window is initially invisible, it won't take focus from the existing visible windows. - On Windows, fix multiple calls to `request_redraw` during `EventsCleared` sending multiple `RedrawRequested events.` - On Windows, fix edge case where `RedrawRequested` could be dispatched before input events in event loop iteration. - On Windows, fix timing issue that could cause events to be improperly dispatched after `RedrawRequested` but before `EventsCleared`. - On macOS, drop unused Metal dependency. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. # 0.20.0 Alpha 1 (2019-06-21) - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. - Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. - `os` module changes: - Renamed to `platform`. - All traits now have platform-specific suffixes. - Exposes new `desktop` module on Windows, Mac, and Linux. - Changes to event loop types: - `EventLoopProxy::wakeup` has been removed in favor of `send_event`. - **Major:** New `run` method drives winit event loop. - Returns `!` to ensure API behaves identically across all supported platforms. - This allows `emscripten` implementation to work without lying about the API. - `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`. - Is read after `EventsCleared` is processed. - `Wait` waits until new events are available. - `WaitUntil` waits until either new events are available or the provided time has been reached. - `Poll` instantly resumes the event loop. - `Exit` aborts the event loop. - Takes a closure that implements `'static + FnMut(Event, &EventLoop, &mut ControlFlow)`. - `&EventLoop` is provided to allow new `Window`s to be created. - **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method. - Behaves identically to `run`, but returns control flow to the calling context and can take non-`'static` closures. - `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`. - Changes to events: - Remove `Event::Awakened` in favor of `Event::UserEvent(T)`. - Can be sent with `EventLoopProxy::send_event`. - Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`. - `RedrawRequested` can be sent by the user with the `Window::request_redraw` method. - `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`. - **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`. - `NewEvents` is emitted when new events are ready to be processed by event loop. - `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop). - `EventsCleared` is emitted when all available events have been processed. - Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop). - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. - Rename several functions to improve both internal consistency and compliance with Rust API guidelines. - Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now. - **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers. - Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor. - On Wayland, the window now exists even if nothing has been drawn. - On Windows, fix initial dimensions of a fullscreen window. - On Windows, Fix transparent borderless windows rendering wrong. # Version 0.19.1 (2019-04-08) - On Wayland, added a `get_wayland_display` function to `EventsLoopExt`. - On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus. - On macOS, fix command key event left and right reverse. - On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend. - On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes - On macOS, the numpad's subtract key has been added to the `Subtract` mapping - On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes - On Windows, fix icon not showing up in corner of window. - On X11, change DPI scaling factor behavior. First, winit tries to read it from "Xft.dpi" XResource, and uses DPI calculation from xrandr dimensions as fallback behavior. # Version 0.19.0 (2019-03-06) - On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available. - On macOS, fix keycodes being incorrect when using a non-US keyboard layout. - On Wayland, fix `with_title()` not setting the windows title - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. - **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency. - On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent - On Windows, fix malformed function pointer typecast that could invoke undefined behavior. - Refactored Windows state/flag-setting code. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. - On Windows, ignore the AltGr key when populating the `ModifersState` type. # Version 0.18.1 (2018-12-30) - On macOS, fix `Yen` (JIS) so applications receive the event. - On X11 with a tiling WM, fixed high CPU usage when moving windows across monitors. - On X11, fixed panic caused by dropping the window before running the event loop. - on macOS, added `WindowExt::set_simple_fullscreen` which does not require a separate space - Introduce `WindowBuilderExt::with_app_id` to allow setting the application ID on Wayland. - On Windows, catch panics in event loop child thread and forward them to the parent thread. This prevents an invocation of undefined behavior due to unwinding into foreign code. - On Windows, fix issue where resizing or moving window combined with grabbing the cursor would freeze program. - On Windows, fix issue where resizing or moving window would eat `Awakened` events. - On Windows, exiting fullscreen after entering fullscreen with disabled decorations no longer shrinks window. - On X11, fixed a segfault when using virtual monitors with XRandR. - Derive `Ord` and `PartialOrd` for `VirtualKeyCode` enum. - On Windows, fix issue where hovering or dropping a non file item would create a panic. - On Wayland, fix resizing and DPI calculation when a `wl_output` is removed without sending a `leave` event to the `wl_surface`, such as disconnecting a monitor from a laptop. - On Wayland, DPI calculation is handled by smithay-client-toolkit. - On X11, `WindowBuilder::with_min_dimensions` and `WindowBuilder::with_max_dimensions` now correctly account for DPI. - Added support for generating dummy `DeviceId`s and `WindowId`s to better support unit testing. - On macOS, fixed unsoundness in drag-and-drop that could result in drops being rejected. - On macOS, implemented `WindowEvent::Refresh`. - On macOS, all `MouseCursor` variants are now implemented and the cursor will no longer reset after unfocusing. - Removed minimum supported Rust version guarantee. # Version 0.18.0 (2018-11-07) - **Breaking:** `image` crate upgraded to 0.20. This is exposed as part of the `icon_loading` API. - On Wayland, pointer events will now provide the current modifiers state. - On Wayland, titles will now be displayed in the window header decoration. - On Wayland, key repetition is now ended when keyboard loses focus. - On Wayland, windows will now use more stylish and modern client side decorations. - On Wayland, windows will use server-side decorations when available. - **Breaking:** Added support for F16-F24 keys (variants were added to the `VirtualKeyCode` enum). - Fixed graphical glitches when resizing on Wayland. - On Windows, fix freezes when performing certain actions after a window resize has been triggered. Reintroduces some visual artifacts when resizing. - Updated window manager hints under X11 to v1.5 of [Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm140200472629520). - Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions. - Fixed UTF8 handling bug in X11 `set_title` function. - On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first. - On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented. - On Windows, fix `Window::set_maximized`. - On Windows 10, fix transparency (#260). - On macOS, fix modifiers during key repeat. - Implemented the `Debug` trait for `Window`, `EventsLoop`, `EventsLoopProxy` and `WindowBuilder`. - On X11, now a `Resized` event will always be generated after a DPI change to ensure the window's logical size is consistent with the new DPI. - Added further clarifications to the DPI docs. - On Linux, if neither X11 nor Wayland manage to initialize, the corresponding panic now consists of a single line only. - Add optional `serde` feature with implementations of `Serialize`/`Deserialize` for DPI types and various event types. - Add `PartialEq`, `Eq`, and `Hash` implementations on public types that could have them but were missing them. - On X11, drag-and-drop receiving an unsupported drop type can no longer cause the WM to freeze. - Fix issue whereby the OpenGL context would not appear at startup on macOS Mojave (#1069). - **Breaking:** Removed `From` impl from `ActivationPolicy` on macOS. - On macOS, the application can request the user's attention with `WindowExt::request_user_attention`. # Version 0.17.2 (2018-08-19) - On macOS, fix `` so applications receive the event. - On macOS, fix `` so applications receive the event. - On Wayland, key press events will now be repeated. # Version 0.17.1 (2018-08-05) - On X11, prevent a compilation failure in release mode for versions of Rust greater than or equal to 1.30. - Fixed deadlock that broke fullscreen mode on Windows. # Version 0.17.0 (2018-08-02) - Cocoa and core-graphics updates. - Fixed thread-safety issues in several `Window` functions on Windows. - On MacOS, the key state for modifiers key events is now properly set. - On iOS, the view is now set correctly. This makes it possible to render things (instead of being stuck on a black screen), and touch events work again. - Added NetBSD support. - **Breaking:** On iOS, `UIView` is now the default root view. `WindowBuilderExt::with_root_view_class` can be used to set the root view objective-c class to `GLKView` (OpenGLES) or `MTKView` (Metal/MoltenVK). - On iOS, the `UIApplication` is not started until `Window::new` is called. - Fixed thread unsafety with cursor hiding on macOS. - On iOS, fixed the size of the `JmpBuf` type used for `setjmp`/`longjmp` calls. Previously this was a buffer overflow on most architectures. - On Windows, use cached window DPI instead of repeatedly querying the system. This fixes sporadic crashes on Windows 7. # Version 0.16.2 (2018-07-07) - On Windows, non-resizable windows now have the maximization button disabled. This is consistent with behavior on macOS and popular X11 WMs. - Corrected incorrect `unreachable!` usage when guessing the DPI factor with no detected monitors. # Version 0.16.1 (2018-07-02) - Added logging through `log`. Logging will become more extensive over time. - On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation. - Fixed X11 backend compilation for environments where `c_char` is unsigned. # Version 0.16.0 (2018-06-25) - Windows additionally has `WindowBuilderExt::with_no_redirection_bitmap`. - **Breaking:** Removed `VirtualKeyCode::LMenu` and `VirtualKeyCode::RMenu`; Windows now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead. - On X11, exiting fullscreen no longer leaves the window in the monitor's top left corner. - **Breaking:** `Window::hidpi_factor` has been renamed to `Window::get_hidpi_factor` for better consistency. `WindowEvent::HiDPIFactorChanged` has been renamed to `WindowEvent::HiDpiFactorChanged`. DPI factors are always represented as `f64` instead of `f32` now. - The Windows backend is now DPI aware. `WindowEvent::HiDpiFactorChanged` is implemented, and `MonitorId::get_hidpi_factor` and `Window::hidpi_factor` return accurate values. - Implemented `WindowEvent::HiDpiFactorChanged` on X11. - On macOS, `Window::set_cursor_position` is now relative to the client area. - On macOS, setting the maximum and minimum dimensions now applies to the client area dimensions rather than to the window dimensions. - On iOS, `MonitorId::get_dimensions` has been implemented and both `MonitorId::get_hidpi_factor` and `Window::get_hidpi_factor` return accurate values. - On Emscripten, `MonitorId::get_hidpi_factor` now returns the same value as `Window::get_hidpi_factor` (it previously would always return 1.0). - **Breaking:** The entire API for sizes, positions, etc. has changed. In the majority of cases, winit produces and consumes positions and sizes as `LogicalPosition` and `LogicalSize`, respectively. The notable exception is `MonitorId` methods, which deal in `PhysicalPosition` and `PhysicalSize`. See the documentation for specifics and explanations of the types. Additionally, winit automatically conserves logical size when the DPI factor changes. - **Breaking:** All deprecated methods have been removed. For `Window::platform_display` and `Window::platform_window`, switch to the appropriate platform-specific `WindowExt` methods. For `Window::get_inner_size_points` and `Window::get_inner_size_pixels`, use the `LogicalSize` returned by `Window::get_inner_size` and convert as needed. - HiDPI support for Wayland. - `EventsLoop::get_available_monitors` and `EventsLoop::get_primary_monitor` now have identical counterparts on `Window`, so this information can be acquired without an `EventsLoop` borrow. - `AvailableMonitorsIter` now implements `Debug`. - Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down. - On X11, all event loops now share the same `XConnection`. - **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful. - **Breaking:** `Window::set_cursor_position` now returns `Result<(), String>`, thus allowing for `Box` conversion via `?`. # Version 0.15.1 (2018-06-13) - On X11, the `Moved` event is no longer sent when the window is resized without changing position. - `MouseCursor` and `CursorState` now implement `Default`. - `WindowBuilder::with_resizable` implemented for Windows, X11, Wayland, and macOS. - `Window::set_resizable` implemented for Windows, X11, Wayland, and macOS. - On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf. - On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor. - On X11, enabling transparency no longer causes the window contents to flicker when resizing. - On X11, `with_override_redirect` now actually enables override redirect. - macOS now generates `VirtualKeyCode::LAlt` and `VirtualKeyCode::RAlt` instead of `None` for both. - On macOS, `VirtualKeyCode::RWin` and `VirtualKeyCode::LWin` are no longer switched. - On macOS, windows without decorations can once again be resized. - Fixed race conditions when creating an `EventsLoop` on X11, most commonly manifesting as "[xcb] Unknown sequence number while processing queue". - On macOS, `CursorMoved` and `MouseInput` events are only generated if they occurs within the window's client area. - On macOS, resizing the window no longer generates a spurious `MouseInput` event. # Version 0.15.0 (2018-05-22) - `Icon::to_cardinals` is no longer public, since it was never supposed to be. - Wayland: improve diagnostics if initialization fails - Fix some system event key doesn't work when focused, do not block keyevent forward to system on macOS - On X11, the scroll wheel position is now correctly reset on i3 and other WMs that have the same quirk. - On X11, `Window::get_current_monitor` now reliably returns the correct monitor. - On X11, `Window::hidpi_factor` returns values from XRandR rather than the inaccurate values previously queried from the core protocol. - On X11, the primary monitor is detected correctly even when using versions of XRandR less than 1.5. - `MonitorId` now implements `Debug`. - Fixed bug on macOS where using `with_decorations(false)` would cause `set_decorations(true)` to produce a transparent titlebar with no title. - Implemented `MonitorId::get_position` on macOS. - On macOS, `Window::get_current_monitor` now returns accurate values. - Added `WindowBuilderExt::with_resize_increments` to macOS. - **Breaking:** On X11, `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` now take `u32` values rather than `i32`. - macOS keyboard handling has been overhauled, allowing for the use of dead keys, IME, etc. Right modifier keys are also no longer reported as being left. - Added the `Window::set_ime_spot(x: i32, y: i32)` method, which is implemented on X11 and macOS. - **Breaking**: `os::unix::WindowExt::send_xim_spot(x: i16, y: i16)` no longer exists. Switch to the new `Window::set_ime_spot(x: i32, y: i32)`, which has equivalent functionality. - Fixed detection of `Pause` and `Scroll` keys on Windows. - On Windows, alt-tabbing while the cursor is grabbed no longer makes it impossible to re-grab the cursor. - On Windows, using `CursorState::Hide` when the cursor is grabbed now ungrabs the cursor first. - Implemented `MouseCursor::NoneCursor` on Windows. - Added `WindowBuilder::with_always_on_top` and `Window::set_always_on_top`. Implemented on Windows, macOS, and X11. - On X11, `WindowBuilderExt` now has `with_class`, `with_override_redirect`, and `with_x11_window_type` to allow for more control over window creation. `WindowExt` additionally has `set_urgent`. - More hints are set by default on X11, including `_NET_WM_PID` and `WM_CLIENT_MACHINE`. Note that prior to this, the `WM_CLASS` hint was automatically set to whatever value was passed to `with_title`. It's now set to the executable name to better conform to expectations and the specification; if this is undesirable, you must explicitly use `WindowBuilderExt::with_class`. # Version 0.14.0 (2018-05-09) - Created the `Copy`, `Paste` and `Cut` `VirtualKeyCode`s and added support for them on X11 and Wayland - Fix `.with_decorations(false)` in macOS - On Mac, `NSWindow` and supporting objects might be alive long after they were `closed` which resulted in apps consuming more heap then needed. Mainly it was affecting multi window applications. Not expecting any user visible change of behaviour after the fix. - Fix regression of Window platform extensions for macOS where `NSFullSizeContentViewWindowMask` was not being correctly applied to `.fullsize_content_view`. - Corrected `get_position` on Windows to be relative to the screen rather than to the taskbar. - Corrected `Moved` event on Windows to use position values equivalent to those returned by `get_position`. It previously supplied client area positions instead of window positions, and would additionally interpret negative values as being very large (around `u16::MAX`). - Implemented `Moved` event on macOS. - On X11, the `Moved` event correctly use window positions rather than client area positions. Additionally, a stray `Moved` that unconditionally accompanied `Resized` with the client area position relative to the parent has been eliminated; `Moved` is still received alongside `Resized`, but now only once and always correctly. - On Windows, implemented all variants of `DeviceEvent` other than `Text`. Mouse `DeviceEvent`s are now received even if the window isn't in the foreground. - `DeviceId` on Windows is no longer a unit struct, and now contains a `u32`. For `WindowEvent`s, this will always be 0, but on `DeviceEvent`s it will be the handle to that device. `DeviceIdExt::get_persistent_identifier` can be used to acquire a unique identifier for that device that persists across replugs/reboots/etc. - Corrected `run_forever` on X11 to stop discarding `Awakened` events. - Various safety and correctness improvements to the X11 backend internals. - Fixed memory leak on X11 every time the mouse entered the window. - On X11, drag and drop now works reliably in release mode. - Added `WindowBuilderExt::with_resize_increments` and `WindowBuilderExt::with_base_size` to X11, allowing for more optional hints to be set. - Rework of the wayland backend, migrating it to use [Smithay's Client Toolkit](https://github.com/Smithay/client-toolkit). - Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage. - Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`. - On Windows, fix panic when trying to call `set_fullscreen(None)` on a window that has not been fullscreened prior. # Version 0.13.1 (2018-04-26) - Ensure necessary `x11-dl` version is used. # Version 0.13.0 (2018-04-25) - Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for MacOS. - Implement `WindowBuilder::with_maximized`, `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Windows. - On Windows, `WindowBuilder::with_fullscreen` no longer changing monitor display resolution. - Overhauled X11 window geometry calculations. `get_position` and `set_position` are more universally accurate across different window managers, and `get_outer_size` actually works now. - Fixed SIGSEGV/SIGILL crashes on macOS caused by stabilization of the `!` (never) type. - Implement `WindowEvent::HiDPIFactorChanged` for macOS - On X11, input methods now work completely out of the box, no longer requiring application developers to manually call `setlocale`. Additionally, when input methods are started, stopped, or restarted on the server end, it's correctly handled. - Implemented `Refresh` event on Windows. - Properly calculate the minimum and maximum window size on Windows, including window decorations. - Map more `MouseCursor` variants to cursor icons on Windows. - Corrected `get_position` on macOS to return outer frame position, not content area position. - Corrected `set_position` on macOS to set outer frame position, not content area position. - Added `get_inner_position` method to `Window`, which gets the position of the window's client area. This is implemented on all applicable platforms (all desktop platforms other than Wayland, where this isn't possible). - **Breaking:** the `Closed` event has been replaced by `CloseRequested` and `Destroyed`. To migrate, you typically just need to replace all usages of `Closed` with `CloseRequested`; see example programs for more info. The exception is iOS, where `Closed` must be replaced by `Destroyed`. # Version 0.12.0 (2018-04-06) - Added subclass to macos windows so they can be made resizable even with no decorations. - Dead keys now work properly on X11, no longer resulting in a panic. - On X11, input method creation first tries to use the value from the user's `XMODIFIERS` environment variable, so application developers should no longer need to manually call `XSetLocaleModifiers`. If that fails, fallbacks are tried, which should prevent input method initialization from ever outright failing. - Fixed thread safety issues with input methods on X11. - Add support for `Touch` for win32 backend. - Fixed `Window::get_inner_size` and friends to return the size in pixels instead of points when using HIDPI displays on OSX. # Version 0.11.3 (2018-03-28) - Added `set_min_dimensions` and `set_max_dimensions` methods to `Window`, and implemented on Windows, X11, Wayland, and OSX. - On X11, dropping a `Window` actually closes it now, and clicking the window's × button (or otherwise having the WM signal to close it) will result in the window closing. - Added `WindowBuilderExt` methods for macos: `with_titlebar_transparent`, `with_title_hidden`, `with_titlebar_buttons_hidden`, `with_fullsize_content_view`. - Mapped X11 numpad keycodes (arrows, Home, End, PageUp, PageDown, Insert and Delete) to corresponding virtual keycodes # Version 0.11.2 (2018-03-06) - Impl `Hash`, `PartialEq`, and `Eq` for `events::ModifiersState`. - Implement `MonitorId::get_hidpi_factor` for MacOS. - Added method `os::macos::MonitorIdExt::get_nsscreen() -> *mut c_void` that gets a `NSScreen` object matching the monitor ID. - Send `Awakened` event on Android when event loop is woken up. # Version 0.11.1 (2018-02-19) - Fixed windows not receiving mouse events when click-dragging the mouse outside the client area of a window, on Windows platforms. - Added method `os::android::EventsLoopExt:set_suspend_callback(Option ()>>)` that allows glutin to register a callback when a suspend event happens # Version 0.11.0 (2018-02-09) - Implement `MonitorId::get_dimensions` for Android. - Added method `os::macos::WindowBuilderExt::with_movable_by_window_background(bool)` that allows to move a window without a titlebar - `with_decorations(false)` - Implement `Window::set_fullscreen`, `Window::set_maximized` and `Window::set_decorations` for Wayland. - Added `Caret` as VirtualKeyCode and support OSX ^-Key with german input. # Version 0.10.1 (2018-02-05) _Yanked_ # Version 0.10.0 (2017-12-27) - Add support for `Touch` for emscripten backend. - Added support for `DroppedFile`, `HoveredFile`, and `HoveredFileCancelled` to X11 backend. - **Breaking:** `unix::WindowExt` no longer returns pointers for things that aren't actually pointers; `get_xlib_window` now returns `Option` and `get_xlib_screen_id` returns `Option`. Additionally, methods that previously returned `libc::c_void` have been changed to return `std::os::raw::c_void`, which are not interchangeable types, so users wanting the former will need to explicitly cast. - Added `set_decorations` method to `Window` to allow decorations to be toggled after the window is built. Presently only implemented on X11. - Raised the minimum supported version of Rust to 1.20 on MacOS due to usage of associated constants in new versions of cocoa and core-graphics. - Added `modifiers` field to `MouseInput`, `MouseWheel`, and `CursorMoved` events to track the modifiers state (`ModifiersState`). - Fixed the emscripten backend to return the size of the canvas instead of the size of the window. # Version 0.9.0 (2017-12-01) - Added event `WindowEvent::HiDPIFactorChanged`. - Added method `MonitorId::get_hidpi_factor`. - Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of `get_inner_size`. - **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints, but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`. - `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`. - Rewrite of the wayland backend to use wayland-client-0.11 - Support for dead keys on wayland for keyboard utf8 input - Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of `EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`. - On Windows added `MonitorIdExt::hmonitor` method - Impl `Clone` for `EventsLoopProxy` - `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found - Support for touch event on wayland - `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to `CursorMoved`, `CursorEntered`, and `CursorLeft`. - New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`. - Send `CursorMoved` event after `CursorEntered` and `Focused` events. - Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend. # Version 0.8.3 (2017-10-11) - Fixed issue of calls to `set_inner_size` blocking on Windows. - Mapped `ISO_Left_Tab` to `VirtualKeyCode::Tab` to make the key work with modifiers - Fixed the X11 backed on 32bit targets # Version 0.8.2 (2017-09-28) - Uniformize keyboard scancode values accross Wayland and X11 (#297). - Internal rework of the wayland event loop - Added method `os::linux::WindowExt::is_ready` # Version 0.8.1 (2017-09-22) - Added various methods to `os::linux::EventsLoopExt`, plus some hidden items necessary to make glutin work. # Version 0.8.0 (2017-09-21) - Added `Window::set_maximized`, `WindowAttributes::maximized` and `WindowBuilder::with_maximized`. - Added `Window::set_fullscreen`. - Changed `with_fullscreen` to take a `Option` instead of a `MonitorId`. - Removed `MonitorId::get_native_identifer()` in favor of platform-specific traits in the `os` module. - Changed `get_available_monitors()` and `get_primary_monitor()` to be methods of `EventsLoop` instead of stand-alone methods. - Changed `EventsLoop` to be tied to a specific X11 or Wayland connection. - Added a `os::linux::EventsLoopExt` trait that makes it possible to configure the connection. - Fixed the emscripten code, which now compiles. - Changed the X11 fullscreen code to use `xrandr` instead of `xxf86vm`. - Fixed the Wayland backend to produce `Refresh` event after window creation. - Changed the `Suspended` event to be outside of `WindowEvent`. - Fixed the X11 backend sometimes reporting the wrong virtual key (#273). winit-0.29.15/CONTRIBUTING.md000064400000000000000000000075471046102023000134020ustar 00000000000000# Winit Contributing Guidelines ## Scope [See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should consider whether or not it's directly related to window creation or input handling. If it isn't, it may be worth creating a separate crate that extends Winit's API to add that functionality. ## Reporting an issue When reporting an issue, in order to help the maintainers understand what the problem is, please make your description of the issue as detailed as possible: - if it is a bug, please provide a clear explanation of what happens, what should happen, and how to reproduce the issue, ideally by providing a minimal program exhibiting the problem - if it is a feature request, please provide a clear argumentation about why you believe this feature should be supported by winit ## Making a pull request When making a code contribution to winit, before opening your pull request, please make sure that: - your patch builds with Winit's minimal supported rust version - Rust 1.65. - you tested your modifications on all the platforms impacted, or if not possible, detail which platforms were not tested, and what should be tested, so that a maintainer or another contributor can test them - you updated any relevant documentation in winit - you left comments in your code explaining any part that is not straightforward, so that the maintainers and future contributors don't have to try to guess what your code is supposed to do - your PR adds an entry to the changelog file if the introduced change is relevant to winit users. You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will handle those for you when merging (see below). - if your PR affects the platform compatibility of one or more features or adds another feature, the relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features) should be updated. Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy is that a PR must be approved by at least two maintainers of winit before being merged, including at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it). Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in `CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the "give contributors write access to the branch" checkbox when creating the PR. ## Maintainers & Testers The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file. If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table! ## Release process Given that winit is a widely used library, we should be able to make a patch releases at any time we want without blocking the development of new features. To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch. The exact steps for an exemplary `0.2.0` release might look like this: 1. Initially, the version on the latest master is `0.1.0` 2. A new `v0.2.x` branch is created for the release 3. In the branch, the version is bumped to `v0.2.0` 4. The new commit in the branch is tagged `v0.2.0` 5. The version is pushed to crates.io 6. A GitHub release is created for the `v0.2.0` tag 7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated When doing a patch release, the process is similar: 1. Initially, the version of the latest release is `0.2.0` 2. Checkout the `v0.2.x` branch 3. Cherry-pick the required non-breaking changes into the `v0.2.x` 4. Follow steps 3-7 of the regular release example winit-0.29.15/Cargo.lock0000644000001440310000000000100103220ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ab_glyph" version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", ] [[package]] name = "ab_glyph_rasterizer" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", "zerocopy", ] [[package]] name = "android-activity" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", "bitflags 2.4.2", "cc", "cesu8", "jni", "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", "thiserror", ] [[package]] name = "android-properties" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "arrayref" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "as-raw-xcb-connection" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" dependencies = [ "objc-sys", ] [[package]] name = "block2" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ "block-sys", "objc2", ] [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytemuck" version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea31d69bda4949c1c1562c1e6f042a1caefac98cdc8a298260a2ff41c1e2d42b" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "calloop" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ "bitflags 2.4.2", "log", "polling", "rustix", "slab", "thiserror", ] [[package]] name = "calloop-wayland-source" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", "rustix", "wayland-backend", "wayland-client", ] [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", ] [[package]] name = "cesu8" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cmake" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] [[package]] name = "cocoa" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", "core-graphics", "foreign-types", "libc", "objc", ] [[package]] name = "cocoa-foundation" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", "libc", "objc", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "combine" version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ "bytes", "memchr", ] [[package]] name = "concurrent-queue" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] [[package]] name = "console_log" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" dependencies = [ "log", "web-sys", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", "libc", ] [[package]] name = "core-graphics-types" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", "libc", ] [[package]] name = "core-text" version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ "core-foundation", "core-graphics", "foreign-types", "libc", ] [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossfont" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89c65306ecd118368d875f48d69394b5c3ff6bb7c57ae6deb638782735a093c" dependencies = [ "cocoa", "core-foundation", "core-foundation-sys", "core-graphics", "core-text", "dwrote", "foreign-types", "freetype-rs", "libc", "log", "objc", "once_cell", "pkg-config", "servo-fontconfig", "winapi", ] [[package]] name = "ctor" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" dependencies = [ "quote", "syn", ] [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "cursor-icon" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" dependencies = [ "serde", ] [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading 0.8.1", ] [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drm" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" dependencies = [ "bitflags 2.4.2", "bytemuck", "drm-ffi", "drm-fourcc", "rustix", ] [[package]] name = "drm-ffi" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ "drm-sys", "rustix", ] [[package]] name = "drm-fourcc" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "drm-sys" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" dependencies = [ "libc", "linux-raw-sys 0.6.4", ] [[package]] name = "dwrote" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", "serde", "serde_derive", "winapi", "wio", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "expat-sys" version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" dependencies = [ "cmake", "pkg-config", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "foreign-types" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", "foreign-types-shared", ] [[package]] name = "foreign-types-macros" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "freetype-rs" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" dependencies = [ "bitflags 1.3.2", "freetype-sys", "libc", ] [[package]] name = "freetype-sys" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" dependencies = [ "cmake", "libc", "pkg-config", ] [[package]] name = "gethostname" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" dependencies = [ "libc", "winapi", ] [[package]] name = "gethostname" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ "libc", "windows-targets 0.48.5", ] [[package]] name = "getrandom" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "icrate" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ "block2", "dispatch", "objc2", ] [[package]] name = "image" version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" dependencies = [ "bytemuck", "byteorder", "color_quant", "num-traits", "png", ] [[package]] name = "indexmap" version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", "thiserror", "walkdir", "windows-sys 0.45.0", ] [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "libloading" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "libredox" version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ "bitflags 2.4.2", "libc", "redox_syscall 0.4.1", ] [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "linux-raw-sys" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b5399f6804fbab912acbd8878ed3532d506b7c951b8f9f164ef90fef39e3f4" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ "libc", ] [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", ] [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "mint" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "ndk" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ "bitflags 2.4.2", "jni-sys", "log", "ndk-sys", "num_enum", "raw-window-handle 0.4.3", "raw-window-handle 0.5.2", "raw-window-handle 0.6.0", "thiserror", ] [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", ] [[package]] name = "num-traits" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] [[package]] name = "num_enum" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", ] [[package]] name = "objc-sys" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" [[package]] name = "objc2" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ "objc-sys", "objc2-encode", ] [[package]] name = "objc2-encode" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "orbclient" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ "libredox", ] [[package]] name = "owned_ttf_parser" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" dependencies = [ "ttf-parser", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pkg-config" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "png" version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "polling" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "proc-macro-crate" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "raw-window-handle" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" dependencies = [ "cty", ] [[package]] name = "raw-window-handle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "raw-window-handle" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "rustix" version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", "libc", "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "sctk-adwaita" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" dependencies = [ "ab_glyph", "crossfont", "log", "memmap2", "smithay-client-toolkit", "tiny-skia", ] [[package]] name = "serde" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "servo-fontconfig" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" dependencies = [ "libc", "servo-fontconfig-sys", ] [[package]] name = "servo-fontconfig-sys" version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" dependencies = [ "expat-sys", "freetype-sys", "pkg-config", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simple_logger" version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" dependencies = [ "log", "windows-sys 0.48.0", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "smithay-client-toolkit" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ "bitflags 2.4.2", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2", "rustix", "thiserror", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", "xkeysym", ] [[package]] name = "smol_str" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" dependencies = [ "serde", ] [[package]] name = "softbuffer" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f266ce2aa23eaaaa4e758ed44495d505d00fb79f359d46f6c1900cb053123b62" dependencies = [ "as-raw-xcb-connection", "bytemuck", "cfg_aliases", "cocoa", "core-graphics", "drm", "fastrand", "foreign-types", "js-sys", "log", "memmap2", "objc", "raw-window-handle 0.5.2", "redox_syscall 0.4.1", "rustix", "tiny-xlib", "wasm-bindgen", "wayland-backend", "wayland-client", "wayland-sys", "web-sys", "windows-sys 0.48.0", "x11rb 0.12.0", ] [[package]] name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "syn" version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiny-skia" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", "log", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", "strict-num", ] [[package]] name = "tiny-xlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4098d49269baa034a8d1eae9bd63e9fa532148d772121dace3bcd6a6c98eb6d" dependencies = [ "as-raw-xcb-connection", "ctor", "libloading 0.8.1", "tracing", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] name = "ttf-parser" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wayland-backend" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", "rustix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ "bitflags 2.4.2", "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-csd-frame" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ "bitflags 2.4.2", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ "rustix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ "bitflags 2.4.2", "wayland-backend", "wayland-client", "wayland-scanner", ] [[package]] name = "wayland-protocols-plasma" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ "bitflags 2.4.2", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-protocols-wlr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ "bitflags 2.4.2", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" dependencies = [ "proc-macro2", "quick-xml", "quote", ] [[package]] name = "wayland-sys" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", "log", "once_cell", "pkg-config", ] [[package]] name = "web-sys" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-wsapoll" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winit" version = "0.29.15" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.4.2", "bytemuck", "calloop", "cfg_aliases", "console_log", "core-foundation", "core-graphics", "cursor-icon", "icrate", "image", "js-sys", "libc", "log", "memmap2", "mint", "ndk", "ndk-sys", "objc2", "once_cell", "orbclient", "percent-encoding", "raw-window-handle 0.4.3", "raw-window-handle 0.5.2", "raw-window-handle 0.6.0", "redox_syscall 0.3.5", "rustix", "sctk-adwaita", "serde", "simple_logger", "smithay-client-toolkit", "smol_str", "softbuffer", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", "web-sys", "web-time", "windows-sys 0.48.0", "x11-dl", "x11rb 0.13.0", "xkbcommon-dl", ] [[package]] name = "winnow" version = "0.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" dependencies = [ "memchr", ] [[package]] name = "wio" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi", ] [[package]] name = "x11-dl" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ "libc", "once_cell", "pkg-config", ] [[package]] name = "x11rb" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ "as-raw-xcb-connection", "gethostname 0.3.0", "libc", "libloading 0.7.4", "nix", "once_cell", "winapi", "winapi-wsapoll", "x11rb-protocol 0.12.0", ] [[package]] name = "x11rb" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ "as-raw-xcb-connection", "gethostname 0.4.3", "libc", "libloading 0.8.1", "once_cell", "rustix", "x11rb-protocol 0.13.0", ] [[package]] name = "x11rb-protocol" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" dependencies = [ "nix", ] [[package]] name = "x11rb-protocol" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" [[package]] name = "xcursor" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] name = "xkbcommon-dl" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ "bitflags 2.4.2", "dlib", "log", "once_cell", "xkeysym", ] [[package]] name = "xkeysym" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" [[package]] name = "zerocopy" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", "syn", ] winit-0.29.15/Cargo.toml0000644000000251510000000000100103460ustar # 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.65.0" name = "winit" version = "0.29.15" authors = [ "The winit contributors", "Pierre Krieger ", ] description = "Cross-platform window creation library." documentation = "https://docs.rs/winit" readme = "README.md" keywords = ["windowing"] categories = ["gui"] license = "Apache-2.0" repository = "https://github.com/rust-windowing/winit" [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" features = [ "rwh_04", "rwh_05", "rwh_06", "serde", "android-native-activity", ] rustdoc-args = [ "--cfg", "docsrs", ] targets = [ "i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "x86_64-apple-darwin", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-ios", "aarch64-linux-android", "wasm32-unknown-unknown", ] [dependencies.bitflags] version = "2" [dependencies.cursor-icon] version = "1.1.0" [dependencies.log] version = "0.4" [dependencies.mint] version = "0.5.6" optional = true [dependencies.once_cell] version = "1.12" [dependencies.rwh_04] version = "0.4" optional = true package = "raw-window-handle" [dependencies.rwh_05] version = "0.5.2" 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.smol_str] version = "0.2.0" [dev-dependencies.image] version = "0.24.0" features = ["png"] default-features = false [dev-dependencies.simple_logger] version = "4.2.0" default_features = false [build-dependencies.cfg_aliases] version = "0.1.1" [features] android-game-activity = ["android-activity/game-activity"] android-native-activity = ["android-activity/native-activity"] default = [ "rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita", ] rwh_04 = [ "dep:rwh_04", "ndk/rwh_04", ] rwh_05 = [ "dep:rwh_05", "ndk/rwh_05", ] rwh_06 = [ "dep:rwh_06", "ndk/rwh_06", ] serde = [ "dep:serde", "cursor-icon/serde", "smol_str/serde", ] wayland = [ "wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2", ] wayland-csd-adwaita = [ "sctk-adwaita", "sctk-adwaita/ab_glyph", ] wayland-csd-adwaita-crossfont = [ "sctk-adwaita", "sctk-adwaita/crossfont", ] wayland-csd-adwaita-notitle = ["sctk-adwaita"] wayland-dlopen = ["wayland-backend/dlopen"] x11 = [ "x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb", ] [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.ahash] version = "0.8.3" features = ["no-rng"] optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.bytemuck] version = "1.13.1" optional = true default-features = false [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.calloop] version = "0.12.3" [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.libc] version = "0.2.64" [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.memmap2] version = "0.9.0" optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.percent-encoding] version = "2.0" optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.rustix] version = "0.38.4" features = [ "std", "system", "thread", "process", ] default-features = false [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.sctk] version = "0.18.0" features = ["calloop"] optional = true default-features = false package = "smithay-client-toolkit" [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.sctk-adwaita] version = "0.8.0" optional = true default_features = false [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.wayland-backend] version = "0.3.0" features = ["client_system"] optional = true default_features = false [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.wayland-client] version = "0.31.1" optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.wayland-protocols] version = "0.31.0" features = ["staging"] optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.wayland-protocols-plasma] version = "0.2.0" features = ["client"] optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.x11-dl] version = "2.19.1" optional = true [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.x11rb] version = "0.13.0" features = [ "allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb", ] optional = true default-features = false [target."cfg(all(unix, not(any(target_os = \"redox\", target_family = \"wasm\", target_os = \"android\", target_os = \"ios\", target_os = \"macos\"))))".dependencies.xkbcommon-dl] version = "0.4.2" [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies.core-foundation] version = "0.9.3" [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies.objc2] version = "0.4.1" [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dev-dependencies.softbuffer] version = "0.3.0" [target."cfg(target_family = \"wasm\")".dependencies.atomic-waker] version = "1" [target."cfg(target_family = \"wasm\")".dependencies.js-sys] version = "0.3.64" [target."cfg(target_family = \"wasm\")".dependencies.wasm-bindgen] version = "0.2" [target."cfg(target_family = \"wasm\")".dependencies.wasm-bindgen-futures] version = "0.4" [target."cfg(target_family = \"wasm\")".dependencies.web-time] version = "0.2" [target."cfg(target_family = \"wasm\")".dependencies.web_sys] version = "0.3.64" features = [ "AbortController", "AbortSignal", "console", "CssStyleDeclaration", "Document", "DomRect", "DomRectReadOnly", "Element", "Event", "EventTarget", "FocusEvent", "HtmlCanvasElement", "HtmlElement", "IntersectionObserver", "IntersectionObserverEntry", "KeyboardEvent", "MediaQueryList", "MessageChannel", "MessagePort", "Node", "PageTransitionEvent", "PointerEvent", "ResizeObserver", "ResizeObserverBoxOptions", "ResizeObserverEntry", "ResizeObserverOptions", "ResizeObserverSize", "VisibilityState", "Window", "WheelEvent", ] package = "web-sys" [target."cfg(target_family = \"wasm\")".dev-dependencies.console_log] version = "1" [target."cfg(target_family = \"wasm\")".dev-dependencies.web-sys] version = "0.3.22" features = ["CanvasRenderingContext2d"] [target."cfg(target_os = \"android\")".dependencies.android-activity] version = "0.5.0" [target."cfg(target_os = \"android\")".dependencies.ndk] version = "0.8.0" default-features = false [target."cfg(target_os = \"android\")".dependencies.ndk-sys] version = "0.5.0" [target."cfg(target_os = \"ios\")".dependencies.icrate] version = "0.0.4" features = [ "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSString", "Foundation_NSProcessInfo", "Foundation_NSThread", "Foundation_NSSet", ] [target."cfg(target_os = \"macos\")".dependencies.core-graphics] version = "0.23.1" [target."cfg(target_os = \"macos\")".dependencies.icrate] version = "0.0.4" features = [ "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSAttributedString", "Foundation_NSMutableAttributedString", "Foundation_NSData", "Foundation_NSDictionary", "Foundation_NSString", "Foundation_NSProcessInfo", "Foundation_NSThread", "Foundation_NSNumber", ] [target."cfg(target_os = \"redox\")".dependencies.orbclient] version = "0.3.42" default-features = false [target."cfg(target_os = \"redox\")".dependencies.redox_syscall] version = "0.3" [target."cfg(target_os = \"windows\")".dependencies.unicode-segmentation] version = "1.7.1" [target."cfg(target_os = \"windows\")".dependencies.windows-sys] version = "0.48" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", "Win32_Globalization", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_Media", "Win32_System_Com_StructuredStorage", "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "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", ] winit-0.29.15/Cargo.toml.orig000064400000000000000000000160211046102023000140230ustar 00000000000000[package] name = "winit" version = "0.29.15" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2021" keywords = ["windowing"] license = "Apache-2.0" readme = "README.md" repository = "https://github.com/rust-windowing/winit" documentation = "https://docs.rs/winit" categories = ["gui"] rust-version = "1.65.0" [package.metadata.docs.rs] features = [ "rwh_04", "rwh_05", "rwh_06", "serde", # Enabled to get docs to compile "android-native-activity", ] default-target = "x86_64-unknown-linux-gnu" # These are all tested in CI targets = [ # Windows "i686-pc-windows-msvc", "x86_64-pc-windows-msvc", # macOS "x86_64-apple-darwin", # Unix (X11 & Wayland) "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", # iOS "x86_64-apple-ios", # Android "aarch64-linux-android", # WebAssembly "wasm32-unknown-unknown", ] rustdoc-args = ["--cfg", "docsrs"] [features] default = ["rwh_06", "x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"] wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "wayland-protocols-plasma", "sctk", "ahash", "memmap2"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-notitle = ["sctk-adwaita"] android-native-activity = ["android-activity/native-activity"] android-game-activity = ["android-activity/game-activity"] serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"] rwh_04 = ["dep:rwh_04", "ndk/rwh_04"] rwh_05 = ["dep:rwh_05", "ndk/rwh_05"] rwh_06 = ["dep:rwh_06", "ndk/rwh_06"] [build-dependencies] cfg_aliases = "0.1.1" [dependencies] bitflags = "2" cursor-icon = "1.1.0" log = "0.4" mint = { version = "0.5.6", optional = true } once_cell = "1.12" rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = ["std"], optional = true } rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"], optional = true } serde = { version = "1", optional = true, features = ["serde_derive"] } smol_str = "0.2.0" [dev-dependencies] image = { version = "0.24.0", default-features = false, features = ["png"] } simple_logger = { version = "4.2.0", default_features = false } winit = { path = ".", features = ["rwh_05"] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies] softbuffer = "0.3.0" [target.'cfg(target_os = "android")'.dependencies] android-activity = "0.5.0" ndk = { version = "0.8.0", default-features = false } ndk-sys = "0.5.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" objc2 = "0.4.1" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" [target.'cfg(target_os = "macos")'.dependencies.icrate] version = "0.0.4" features = [ "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSAttributedString", "Foundation_NSMutableAttributedString", "Foundation_NSData", "Foundation_NSDictionary", "Foundation_NSString", "Foundation_NSProcessInfo", "Foundation_NSThread", "Foundation_NSNumber", ] [target.'cfg(target_os = "ios")'.dependencies.icrate] version = "0.0.4" features = [ "dispatch", "Foundation", "Foundation_NSArray", "Foundation_NSString", "Foundation_NSProcessInfo", "Foundation_NSThread", "Foundation_NSSet", ] [target.'cfg(target_os = "windows")'.dependencies] unicode-segmentation = "1.7.1" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.48" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", "Win32_Globalization", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_Media", "Win32_System_Com_StructuredStorage", "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming", "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(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] ahash = { version = "0.8.3", features = ["no-rng"], optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true } calloop = "0.12.3" libc = "0.2.64" memmap2 = { version = "0.9.0", optional = true } percent-encoding = { version = "2.0", optional = true } rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = ["calloop"], optional = true } sctk-adwaita = { version = "0.8.0", default_features = false, optional = true } wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"], optional = true } wayland-client = { version = "0.31.1", optional = true } wayland-protocols = { version = "0.31.0", features = [ "staging"], optional = true } wayland-protocols-plasma = { version = "0.2.0", features = [ "client" ], optional = true } x11-dl = { version = "2.19.1", optional = true } x11rb = { version = "0.13.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } xkbcommon-dl = "0.4.2" [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } redox_syscall = "0.3" [target.'cfg(target_family = "wasm")'.dependencies.web_sys] package = "web-sys" version = "0.3.64" features = [ 'AbortController', 'AbortSignal', 'console', 'CssStyleDeclaration', 'Document', 'DomRect', 'DomRectReadOnly', 'Element', 'Event', 'EventTarget', 'FocusEvent', 'HtmlCanvasElement', 'HtmlElement', 'IntersectionObserver', 'IntersectionObserverEntry', 'KeyboardEvent', 'MediaQueryList', 'MessageChannel', 'MessagePort', 'Node', 'PageTransitionEvent', 'PointerEvent', 'ResizeObserver', 'ResizeObserverBoxOptions', 'ResizeObserverEntry', 'ResizeObserverOptions', 'ResizeObserverSize', 'VisibilityState', 'Window', 'WheelEvent' ] [target.'cfg(target_family = "wasm")'.dependencies] atomic-waker = "1" js-sys = "0.3.64" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-time = "0.2" [target.'cfg(target_family = "wasm")'.dev-dependencies] console_log = "1" web-sys = { version = "0.3.22", features = ['CanvasRenderingContext2d'] } [workspace] members = [ "run-wasm", ] winit-0.29.15/FEATURES.md000064400000000000000000000356051046102023000127050ustar 00000000000000# Winit Scope Winit aims to expose an interface that abstracts over window creation and input handling and can be used to create both games and applications. It supports the following main graphical platforms: - Desktop - Windows 7+ (10+ is tested regularly) - macOS 10.7+ (10.14+ is tested regularly) - Unix - via X11 - via Wayland - Redox OS, via Orbital - Mobile - iOS - Android - Web - Chrome - Firefox - Safari 13.1+ Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not aim to support every single feature of every platform, but rather to abstract over the common features available everywhere. In this context, APIs exposed in winit can be split into different "support tiers": - **Core:** Features that are essential to providing a well-formed abstraction over each platform's windowing and input APIs. - **Platform:** Platform-specific features that can't be meaningfully exposed through a common API and cannot be implemented outside of Winit without exposing a significant amount of Winit's internals or interfering with Winit's abstractions. - **Usability:** Features that are not strictly essential to Winit's functionality, but provide meaningful usability improvements and cannot be reasonably implemented in an external crate. These are generally optional and exposed through Cargo features. Core features are taken care of by the core Winit maintainers. Platform features are not. When a platform feature is submitted, the submitter is considered the expert in the feature and may be asked to support the feature should it break in the future. Winit ***does not*** directly expose functionality for drawing inside windows or creating native menus, but ***does*** commit to providing APIs that higher-level crates can use to implement that functionality. ## `1.0` and stability When all core features are implemented to the satisfaction of the Winit maintainers, Winit 1.0 will be released and the library will enter maintenance mode. For the most part, new core features will not be added past this point. New platform features may be accepted and exposed through point releases. ### Tier upgrades Some platform features could, in theory, be exposed across multiple platforms, but have not gone through the implementation work necessary to function on all platforms. When one of these features gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. If that gets accepted, the platform-specific functions get deprecated and become permanently exposed through the core, cross-platform API. # Features ## Extending this section If your PR makes notable changes to Winit's features, please update this section as follows: - If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core feature, add a row to the feature matrix and describe what platforms the feature has been implemented on. - If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the API rework on all relevant platforms, please move it to the `Completed API Reworks` table. - If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*, or mark it as *mostly completed* and link to an issue describing the problems with the implementation. ## Core ### Windowing - **Window initialization**: Winit allows the creation of a window - **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context - **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan - **Window decorations**: The windows created by winit are properly decorated, and the decorations can be deactivated - **Window decorations toggle**: Decorations can be turned on or off after window creation - **Window resizing**: The windows created by winit can be resized and generate the appropriate events when they are. The application can precisely control its window size if desired. - **Window resize increments**: When the window gets resized, the application can choose to snap the window's size to specific values. - **Window transparency**: Winit allows the creation of windows with a transparent background. - **Window maximization**: The windows created by winit can be maximized upon creation. - **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after creation. - **Window minimization**: The windows created by winit can be minimized after creation. - **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after creation. - **Exclusive fullscreen**: Winit allows changing the video mode of the monitor for fullscreen windows and, if applicable, captures the monitor for exclusive use by this application. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent windows can be disabled in favor of popup windows. This feature also guarantees that popup windows get drawn above their owner. ### System Information - **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary. - **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth). ### Input Handling - **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events. - **Mouse set location**: Forcibly changing the location of the pointer. - **Cursor locking**: Locking the cursor inside the window so it cannot move. - **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them. - **Cursor icon**: Changing the cursor icon or hiding the cursor. - **Cursor hittest**: Handle or ignore mouse events for a window. - **Touch events**: Single-touch events. - **Touch pressure**: Touch events contain information about the amount of force being applied. - **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Keyboard events**: Properly processing keyboard events using the user-specified keymap and translating keypresses into UTF-8 characters, handling dead keys and IMEs. - **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled. - **Raw Device Events**: Capturing input from input devices without any OS filtering. - **Gamepad/Joystick events**: Capturing input from gamepads and joysticks. - **Device movement events**: Capturing input from the device gyroscope and accelerometer. ## Platform ### Windows * Setting the name of the internal window class * Setting the taskbar icon * Setting the parent window * Setting a menu bar * `WS_EX_NOREDIRECTIONBITMAP` support * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme ### macOS * Window activation policy * Window movable by background * Transparent titlebar * Hidden titlebar * Hidden titlebar buttons * Full-size content view * Accepts first mouse * Set a preferred theme and get current theme. ### Unix * Window urgency * X11 Window Class * X11 Override Redirect Flag * GTK Theme Variant * Base window size * Setting the X11 parent window ### iOS * `winit` has a minimum OS requirement of iOS 8 * Get the `UIScreen` object pointer * Setting the `UIView` hidpi factor * Valid orientations * Home indicator visibility * Status bar visibility and style * Deferring system gestures * Getting the device idiom * Getting the preferred video mode ### Web * Get if the systems preferred color scheme is "dark" ## Usability * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) ## Compatibility Matrix Legend: - ✔ï¸: Works as intended - â–¢: Mostly works, but some bugs are known - âŒ: Missing feature or large bugs making it unusable - **N/A**: Not applicable for this platform - â“: Unknown status ### Windowing |Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS| |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |Window initialization |âœ”ï¸ |âœ”ï¸ |â–¢[#5] |âœ”ï¸ |â–¢[#33]|â–¢[#33] |âœ”ï¸ |âœ”ï¸ | |Providing pointer to init OpenGL |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ | |Providing pointer to init Vulkan |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |**N/A**|**N/A** | |Window decorations |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|âœ”ï¸ | |Window decorations toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window resizing |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |âœ”ï¸ | |Window resize increments |⌠|âœ”ï¸ |âœ”ï¸ |⌠|**N/A**|**N/A**|**N/A**|**N/A** | |Window transparency |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|N/A |âœ”ï¸ | |Window blur |⌠|⌠|⌠|âœ”ï¸ |**N/A**|**N/A**|N/A |⌠| |Window maximization |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window maximization toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Window minimization |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A**|**N/A** | |Fullscreen |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ |âœ”ï¸ |**N/A** | |Fullscreen toggle |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|âœ”ï¸ |âœ”ï¸ |**N/A** | |Exclusive fullscreen |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A** |⌠|âœ”ï¸ |**N/A**|**N/A** | |HiDPI support |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠| |Popup windows |⌠|⌠|⌠|⌠|⌠|⌠|**N/A**|**N/A** | ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ | |Monitor list |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|⌠| |Video mode query |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|⌠| ### Input handling |Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |Mouse events |âœ”ï¸ |â–¢[#63] |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |âœ”ï¸ | |Mouse set location |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |✔ï¸(when locked) |**N/A**|**N/A**|**N/A**|**N/A** | |Cursor locking |⌠|âœ”ï¸ |⌠|âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |⌠| |Cursor confining |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|⌠|⌠| |Cursor icon |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|âœ”ï¸ |**N/A** | |Cursor hittest |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|⌠|⌠| |Touch events |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A** | |Touch pressure |âœ”ï¸ |⌠|⌠|⌠|⌠|âœ”ï¸ |âœ”ï¸ |**N/A** | |Multitouch |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|**N/A** | |Keyboard events |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ | |Drag & Drop |â–¢[#720] |â–¢[#720] |â–¢[#720] |â–¢[#720] |**N/A**|**N/A**|â“ |**N/A** | |Raw Device Events |â–¢[#750] |â–¢[#750] |â–¢[#750] |⌠|⌠|⌠|â“ |**N/A** | |Gamepad/Joystick events |âŒ[#804] |⌠|⌠|⌠|⌠|⌠|â“ |**N/A** | |Device movement events |â“ |â“ |â“ |â“ |⌠|⌠|â“ |**N/A** | |Drag window with cursor |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A** |**N/A** | |Resize with cursor |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ |**N/A**|**N/A**|**N/A** |**N/A** | ### Pending API Reworks Changes in the API that have been agreed upon but aren't implemented across all platforms. |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | |New API for HiDPI ([#315] [#319]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |â“ | |Event Loop 2.0 ([#459]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |â“ |âœ”ï¸ | |Keyboard Input 2.0 ([#753]) |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |âœ”ï¸ |⌠|âœ”ï¸ |âœ”ï¸ | ### Completed API Reworks |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | [#165]: https://github.com/rust-windowing/winit/issues/165 [#219]: https://github.com/rust-windowing/winit/issues/219 [#242]: https://github.com/rust-windowing/winit/issues/242 [#306]: https://github.com/rust-windowing/winit/issues/306 [#315]: https://github.com/rust-windowing/winit/issues/315 [#319]: https://github.com/rust-windowing/winit/issues/319 [#33]: https://github.com/rust-windowing/winit/issues/33 [#459]: https://github.com/rust-windowing/winit/issues/459 [#5]: https://github.com/rust-windowing/winit/issues/5 [#63]: https://github.com/rust-windowing/winit/issues/63 [#720]: https://github.com/rust-windowing/winit/issues/720 [#721]: https://github.com/rust-windowing/winit/issues/721 [#750]: https://github.com/rust-windowing/winit/issues/750 [#753]: https://github.com/rust-windowing/winit/issues/753 [#804]: https://github.com/rust-windowing/winit/issues/804 winit-0.29.15/HALL_OF_CHAMPIONS.md000064400000000000000000000024371046102023000141710ustar 00000000000000# Hall of Champions The winit maintainers would like to recognize the following former winit contributors, without whom winit would not exist in its current form. We thank them deeply for their time and efforts and wish them the best of luck in their future endeavors: * [@tomaka]: For creating the winit project and guiding it through its early years of existence. * [@vberger]: For diligently creating the Wayland backend and being its extremely helpful and benevolent maintainer for years. * [@francesca64]: For taking over the responsibility of maintaining almost every winit backend and standardizing HiDPI support across all of them. * [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a vastly more sustainable era of winit. * [@goddessfreya]: For selflessly taking over maintainership of glutin and her stellar dedication to improving both winit and glutin. * [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API. [@tomaka]: https://github.com/tomaka [@vberger]: https://github.com/vberger [@francesca64]: https://github.com/francesca64 [@Osspial]: https://github.com/Osspial [@goddessfreya]: https://github.com/goddessfreya [@ArturKovacs]: https://github.com/ArturKovacs winit-0.29.15/LICENSE000064400000000000000000000260731046102023000121510ustar 00000000000000Apache 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.winit-0.29.15/README.md000064400000000000000000000210741046102023000124170ustar 00000000000000# winit - Cross-platform window creation and management in Rust [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) ```toml [dependencies] winit = "0.29.15" ``` ## [Documentation](https://docs.rs/winit) For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md). For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do. ## Contact Us Join us in any of these: [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit) ## Usage Winit is a window creation and management library. It can create windows and lets you handle events (for example: the window being resized, a key being pressed, a mouse movement, etc.) produced by the window. Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to show something on the window you need to use the platform-specific getters provided by winit, or another library. ### Cargo Features Winit 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). * `x11` (enabled by default): On Unix platform, compiles with the X11 backend * `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend * `mint`: Enables mint (math interoperability standard types) conversions. ## MSRV Policy This crate's Minimum Supported Rust Version (MSRV) is **1.65**. Changes to the MSRV will be accompanied by a minor version bump. As a **tentative** policy, the upper bound of the MSRV is given by the following formula: ``` min(sid, stable - 3) ``` Where `sid` is the current version of `rustc` provided by [Debian Sid], and `stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability. [Debian Sid]: https://packages.debian.org/sid/rustc The exception is for the Android platform, where a higher Rust version must be used for certain Android features. In this case, the MSRV will be capped at the latest stable version of Rust minus three. This inconsistency is not reflected in Cargo metadata, as it is not powerful enough to expose this restriction. All crates in the [`rust-windowing`] organizations have the same MSRV policy. [`rust-windowing`]: https://github.com/rust-windowing ### Platform-specific usage #### Wayland Note that windows don't appear on Wayland until you draw/present to them. #### WebAssembly To run the web example: `cargo run-wasm --example web` Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`. On the web platform, a Winit window is backed by a `` element. You can either [provide Winit with a `` element][web with_canvas], or [let Winit create a `` element which you can then retrieve][web canvas getter] and insert it into the DOM yourself. For the example code using Winit with WebAssembly, check out the [web example]. For information on using Rust on WebAssembly, check out the [Rust and WebAssembly book]. [web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas [web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas [web example]: ./examples/web.rs [Rust and WebAssembly book]: https://rustwasm.github.io/book/ #### Android The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate. Native Android applications need some form of "glue" crate that is responsible for defining the main entry point for your Rust application as well as tracking various life-cycle events and synchronizing with the main JVM thread. Winit uses the [android-activity](https://github.com/rib/android-activity) as a glue crate (prior to `0.28` it used [ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)). The version of the glue crate that your application depends on _must_ match the version that Winit depends on because the glue crate is responsible for your application's main entry point. If Cargo resolves multiple versions, they will clash. `winit` glue compatibility table: | winit | ndk-glue | | :---: | :--------------------------: | | 0.29 | `android-activity = "0.5"` | | 0.28 | `android-activity = "0.4"` | | 0.27 | `ndk-glue = "0.7"` | | 0.26 | `ndk-glue = "0.5"` | | 0.25 | `ndk-glue = "0.3"` | | 0.24 | `ndk-glue = "0.2"` | The recommended way to avoid a conflict with the glue version is to avoid explicitly depending on the `android-activity` crate, and instead consume the API that is re-exported by Winit under `winit::platform::android::activity::*` Running on an Android device needs a dynamic system library. Add this to Cargo.toml: ```toml [lib] name = "main" crate-type = ["cdylib"] ``` All Android applications are based on an `Activity` subclass, and the `android-activity` crate is designed to support different choices for this base class. Your application _must_ specify the base class it needs via a feature flag: | Base Class | Feature Flag | Notes | | :--------------: | :---------------: | :-----: | | `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.| | [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) | [`GameActivity`]: https://developer.android.com/games/agdk/game-activity [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input [`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity [agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries [Gradle]: https://developer.android.com/studio/build For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples). ##### Converting from `ndk-glue` to `android-activity` If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be: 1. Remove `ndk-glue` from your `Cargo.toml` 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.15", features = [ "android-native-activity" ] }` 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above). 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above). #### MacOS A lot of functionality expects the application to be ready before you start doing anything; this includes creating windows, fetching monitors, drawing, and so on, see issues [#2238], [#2051] and [#2087]. If you encounter problems, you should try doing your initialization inside `Event::Resumed`. #### iOS Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required by all UI-related code (see issue [#1705]). It would be best to consider creating your windows inside `Event::Resumed`. [#2238]: https://github.com/rust-windowing/winit/issues/2238 [#2051]: https://github.com/rust-windowing/winit/issues/2051 [#2087]: https://github.com/rust-windowing/winit/issues/2087 [#1705]: https://github.com/rust-windowing/winit/issues/1705 #### Redox OS Redox OS has some functionality not yet present that will be implemented when its orbital display server provides it. winit-0.29.15/build.rs000064400000000000000000000016531046102023000126060ustar 00000000000000use cfg_aliases::cfg_aliases; fn main() { // The script doesn't depend on our code println!("cargo:rerun-if-changed=build.rs"); // Setup cfg aliases cfg_aliases! { // Systems. android_platform: { target_os = "android" }, wasm_platform: { all(target_family = "wasm", not(target_os = "emscripten")) }, macos_platform: { target_os = "macos" }, ios_platform: { target_os = "ios" }, windows_platform: { target_os = "windows" }, apple: { any(target_os = "ios", target_os = "macos") }, free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) }, redox: { target_os = "redox" }, // Native displays. x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) }, wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) }, orbital_platform: { redox }, } } winit-0.29.15/clippy.toml000064400000000000000000000024521046102023000133340ustar 00000000000000disallowed-methods = [ { path = "web_sys::window", reason = "is not available in every context" }, { path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, ] winit-0.29.15/deny.toml000064400000000000000000000061371046102023000127770ustar 00000000000000# https://embarkstudios.github.io/cargo-deny/ # cargo install cargo-deny # cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check # Note: running just `cargo deny check` without a `--target` will result in # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 targets = [ { triple = "aarch64-apple-ios" }, { triple = "aarch64-linux-android" }, { triple = "i686-pc-windows-gnu" }, { triple = "i686-pc-windows-msvc" }, { triple = "i686-unknown-linux-gnu" }, { triple = "wasm32-unknown-unknown" }, { triple = "x86_64-apple-darwin" }, { triple = "x86_64-apple-ios" }, { triple = "x86_64-pc-windows-gnu" }, { triple = "x86_64-pc-windows-msvc" }, { triple = "x86_64-unknown-linux-gnu" }, { triple = "x86_64-unknown-redox" }, ] [advisories] vulnerability = "deny" unmaintained = "warn" yanked = "deny" ignore = [] [bans] multiple-versions = "deny" wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed deny = [] skip = [ { name = "raw-window-handle" }, # we intentionally have multiple versions of this { name = "bitflags" }, # the ecosystem is in the process of migrating. { name = "libloading" }, # x11rb uses a different version until the next update { name = "redox_syscall" }, # https://gitlab.redox-os.org/redox-os/orbclient/-/issues/46 ] skip-tree = [] [licenses] private = { ignore = true } unlicensed = "deny" allow-osi-fsf-free = "neither" confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text copyleft = "deny" allow = [ "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ "ISC", # https://tldrlegal.com/license/-isc-license "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 "MIT-0", # https://choosealicense.com/licenses/mit-0/ "MIT", # https://tldrlegal.com/license/mit-license "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] winit-0.29.15/docs/res/ATTRIBUTION.md000064400000000000000000000010711046102023000150020ustar 00000000000000# Image Attribution These images are used in the documentation of `winit`. ## keyboard_*.svg These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)" by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) License. Minor modifications have been made by [John Nunley](https://github.com/notgull), which have been released under the same license as a derivative work. winit-0.29.15/docs/res/keyboard_left_shift_key.svg000064400000000000000000002216701046102023000202650ustar 00000000000000winit-0.29.15/docs/res/keyboard_numpad_1_key.svg000064400000000000000000002216721046102023000176440ustar 00000000000000winit-0.29.15/docs/res/keyboard_right_shift_key.svg000064400000000000000000002216671046102023000204560ustar 00000000000000winit-0.29.15/docs/res/keyboard_standard_1_key.svg000064400000000000000000002216711046102023000201570ustar 00000000000000winit-0.29.15/examples/child_window.rs000064400000000000000000000067461046102023000160070ustar 00000000000000#[cfg(all( feature = "rwh_06", any(x11_platform, macos_platform, windows_platform) ))] #[path = "util/fill.rs"] mod fill; #[cfg(all( feature = "rwh_06", any(x11_platform, macos_platform, windows_platform) ))] #[allow(deprecated)] fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; use winit::{ dpi::{LogicalPosition, LogicalSize, Position}, event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{EventLoop, EventLoopWindowTarget}, raw_window_handle::HasRawWindowHandle, window::{Window, WindowBuilder, WindowId}, }; fn spawn_child_window( parent: &Window, event_loop: &EventLoopWindowTarget<()>, windows: &mut HashMap, ) { let parent = parent.raw_window_handle().unwrap(); let mut builder = WindowBuilder::new() .with_title("child window") .with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_visible(true); // `with_parent_window` is unsafe. Parent window must be a valid window. builder = unsafe { builder.with_parent_window(Some(parent)) }; let child_window = builder.build(event_loop).unwrap(); let id = child_window.id(); windows.insert(id, child_window); println!("child window created with id: {id:?}"); } let mut windows = HashMap::new(); let event_loop: EventLoop<()> = EventLoop::new().unwrap(); let parent_window = WindowBuilder::new() .with_title("parent window") .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_inner_size(LogicalSize::new(640.0f32, 480.0f32)) .build(&event_loop) .unwrap(); println!("parent window: {parent_window:?})"); event_loop.run(move |event: Event<()>, elwt| { if let Event::WindowEvent { event, window_id } = event { match event { WindowEvent::CloseRequested => { windows.clear(); elwt.exit(); } WindowEvent::CursorEntered { device_id: _ } => { // On x11, println when the cursor entered in a window even if the child window is created // by some key inputs. // the child windows are always placed at (0, 0) with size (200, 200) in the parent window, // so we also can see this log when we move the cursor arround (200, 200) in parent window. println!("cursor entered in the window {window_id:?}"); } WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Pressed, .. }, .. } => { spawn_child_window(&parent_window, elwt, &mut windows); } WindowEvent::RedrawRequested => { if let Some(window) = windows.get(&window_id) { fill::fill_window(window); } } _ => (), } } }) } #[cfg(not(all( feature = "rwh_06", any(x11_platform, macos_platform, windows_platform) )))] fn main() { panic!("This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature enabled."); } winit-0.29.15/examples/control_flow.rs000064400000000000000000000104011046102023000160230ustar 00000000000000#![allow(clippy::single_match)] use std::thread; #[cfg(not(wasm_platform))] use std::time; #[cfg(wasm_platform)] use web_time as time; use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, keyboard::{Key, NamedKey}, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; #[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); fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); 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().unwrap(); 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, elwt| { use winit::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: key, state: ElementState::Pressed, .. }, .. } => match key.as_ref() { // WARNING: Consider using `key_without_modifers()` if available on your platform. // See the `key_binding` example Key::Character("1") => { mode = Mode::Wait; println!("\nmode: {mode:?}\n"); } Key::Character("2") => { mode = Mode::WaitUntil; println!("\nmode: {mode:?}\n"); } Key::Character("3") => { mode = Mode::Poll; println!("\nmode: {mode:?}\n"); } Key::Character("r") => { request_redraw = !request_redraw; println!("\nrequest_redraw: {request_redraw}\n"); } Key::Named(NamedKey::Escape) => { close_requested = true; } _ => (), }, WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), }, Event::AboutToWait => { if request_redraw && !wait_cancelled && !close_requested { window.request_redraw(); } match mode { Mode::Wait => elwt.set_control_flow(ControlFlow::Wait), Mode::WaitUntil => { if !wait_cancelled { elwt.set_control_flow(ControlFlow::WaitUntil( time::Instant::now() + WAIT_TIME, )); } } Mode::Poll => { thread::sleep(POLL_SLEEP_TIME); elwt.set_control_flow(ControlFlow::Poll); } }; if close_requested { elwt.exit(); } } _ => (), } }) } winit-0.29.15/examples/cursor.rs000064400000000000000000000046121046102023000146400ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, window::{CursorIcon, WindowBuilder}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap(); window.set_title("A fantastic window!"); let mut cursor_idx = 0; event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match 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; } } WindowEvent::RedrawRequested => { fill::fill_window(&window); } WindowEvent::CloseRequested => { elwt.exit(); } _ => (), } } }) } const CURSORS: &[CursorIcon] = &[ CursorIcon::Default, CursorIcon::Crosshair, CursorIcon::Pointer, 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, ]; winit-0.29.15/examples/cursor_grab.rs000064400000000000000000000051111046102023000156260ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::{Key, ModifiersState, NamedKey}, window::{CursorGrabMode, WindowBuilder}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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, elwt| match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::KeyboardInput { event: KeyEvent { logical_key: key, state: ElementState::Released, .. }, .. } => { let result = match key { Key::Named(NamedKey::Escape) => { elwt.exit(); Ok(()) } Key::Character(ch) => match ch.to_lowercase().as_str() { "g" => window.set_cursor_grab(CursorGrabMode::Confined), "l" => window.set_cursor_grab(CursorGrabMode::Locked), "a" => window.set_cursor_grab(CursorGrabMode::None), "h" => { window.set_cursor_visible(modifiers.shift_key()); Ok(()) } _ => Ok(()), }, _ => Ok(()), }; if let Err(err) = result { println!("error: {err}"); } } WindowEvent::ModifiersChanged(new) => modifiers = new.state(), WindowEvent::RedrawRequested => fill::fill_window(&window), _ => (), }, 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"), }, _ => (), }, _ => (), }) } winit-0.29.15/examples/custom_events.rs000064400000000000000000000032211046102023000162140ustar 00000000000000#![allow(clippy::single_match)] #[cfg(not(wasm_platform))] fn main() -> Result<(), impl std::error::Error> { use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoopBuilder, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; #[derive(Debug, Clone, Copy)] enum CustomEvent { Timer, } SimpleLogger::new().init().unwrap(); let event_loop = EventLoopBuilder::::with_user_event() .build() .unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); // `EventLoopProxy` allows you to dispatch custom events to the main Winit 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, elwt| match event { Event::UserEvent(event) => println!("user event: {event:?}"), Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(), Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { fill::fill_window(&window); } _ => (), }) } #[cfg(wasm_platform)] fn main() { panic!("This example is not supported on web."); } winit-0.29.15/examples/drag_window.rs000064400000000000000000000072211046102023000156260ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event_loop::EventLoop, keyboard::Key, window::{Window, WindowBuilder, WindowId}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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(); let mut cursor_location = None; event_loop.run(move |event, elwt| 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 => elwt.exit(), WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position), WindowEvent::MouseInput { state, button, .. } => { let window = if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { &window_2 } else { &window_1 }; match (button, state) { (MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(), (MouseButton::Right, ElementState::Released) => { if let Some(position) = cursor_location { window.show_window_menu(position); } } _ => (), } } 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(c), .. }, .. } => match c.as_str() { "x" => { switched = !switched; name_windows(entered_id, switched, &window_1, &window_2); println!("Switched!") } "d" => { let window = if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { &window_2 } else { &window_1 }; window.set_decorations(!window.is_decorated()); } _ => (), }, WindowEvent::RedrawRequested => { if window_id == window_1.id() { fill::fill_window(&window_1); } else if window_id == window_2.id() { fill::fill_window(&window_2); } } _ => (), }, _ => (), }) } 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("winit window"); } winit-0.29.15/examples/focus.rs000064400000000000000000000033601046102023000144410ustar 00000000000000#![allow(clippy::single_match)] //! Example for focusing a window. use simple_logger::SimpleLogger; #[cfg(not(wasm_platform))] use std::time; #[cfg(wasm_platform)] use web_time as time; use winit::{ event::{Event, StartCause, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(&event_loop) .unwrap(); let mut deadline = time::Instant::now() + time::Duration::from_secs(3); event_loop.run(move |event, elwt| { match event { Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { // Timeout reached; focus the window. println!("Re-focusing the window."); deadline += time::Duration::from_secs(3); window.focus_window(); } Event::WindowEvent { event, window_id } if window_id == window.id() => match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::RedrawRequested => { // Notify the windowing system that we'll be presenting to the window. window.pre_present_notify(); fill::fill_window(&window); } _ => (), }, Event::AboutToWait => { window.request_redraw(); } _ => (), } elwt.set_control_flow(winit::event_loop::ControlFlow::WaitUntil(deadline)); }) } winit-0.29.15/examples/fullscreen.rs000064400000000000000000000150431046102023000154650ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::dpi::PhysicalSize; use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; use winit::keyboard::{Key, NamedKey}; use winit::window::{Fullscreen, WindowBuilder}; #[cfg(target_os = "macos")] use winit::platform::macos::WindowExtMacOS; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let mut decorations = true; let mut minimized = false; let mut with_min_size = false; let mut with_max_size = false; let window = WindowBuilder::new() .with_title("Hello world!") .build(&event_loop) .unwrap(); let mut monitor_index = 0; let mut monitor = event_loop .available_monitors() .next() .expect("no monitor found!"); println!("Monitor: {:?}", monitor.name()); let mut mode_index = 0; let mut mode = monitor.video_modes().next().expect("no mode found"); println!("Mode: {mode}"); println!("Keys:"); println!("- Esc\tExit"); println!("- F\tToggle exclusive fullscreen mode"); println!("- B\tToggle borderless mode"); #[cfg(target_os = "macos")] println!("- C\tToggle simple fullscreen mode"); println!("- S\tNext screen"); println!("- M\tNext mode for this screen"); println!("- D\tToggle window decorations"); println!("- X\tMaximize window"); println!("- Z\tMinimize window"); println!("- I\tToggle mIn size limit"); println!("- A\tToggle mAx size limit"); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::KeyboardInput { event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. }, .. } => match key { Key::Named(NamedKey::Escape) => elwt.exit(), // WARNING: Consider using `key_without_modifers()` if available on your platform. // See the `key_binding` example Key::Character(ch) => match ch.to_lowercase().as_str() { "f" | "b" if window.fullscreen().is_some() => { window.set_fullscreen(None); } "f" => { let fullscreen = Some(Fullscreen::Exclusive(mode.clone())); println!("Setting mode: {fullscreen:?}"); window.set_fullscreen(fullscreen); } "b" => { let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone()))); println!("Setting mode: {fullscreen:?}"); window.set_fullscreen(fullscreen); } #[cfg(target_os = "macos")] "c" => { window.set_simple_fullscreen(!window.simple_fullscreen()); } "s" => { monitor_index += 1; if let Some(mon) = elwt.available_monitors().nth(monitor_index) { monitor = mon; } else { monitor_index = 0; monitor = elwt.available_monitors().next().expect("no monitor found!"); } println!("Monitor: {:?}", monitor.name()); mode_index = 0; mode = monitor.video_modes().next().expect("no mode found"); println!("Mode: {mode}"); } "m" => { mode_index += 1; if let Some(m) = monitor.video_modes().nth(mode_index) { mode = m; } else { mode_index = 0; mode = monitor.video_modes().next().expect("no mode found"); } println!("Mode: {mode}"); } "d" => { decorations = !decorations; window.set_decorations(decorations); } "x" => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } "z" => { minimized = !minimized; window.set_minimized(minimized); } "i" => { with_min_size = !with_min_size; let min_size = if with_min_size { Some(PhysicalSize::new(100, 100)) } else { None }; window.set_min_inner_size(min_size); eprintln!( "Min: {with_min_size}: {min_size:?} => {:?}", window.inner_size() ); } "a" => { with_max_size = !with_max_size; let max_size = if with_max_size { Some(PhysicalSize::new(200, 200)) } else { None }; window.set_max_inner_size(max_size); eprintln!( "Max: {with_max_size}: {max_size:?} => {:?}", window.inner_size() ); } _ => (), }, _ => (), }, WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/handling_close.rs000064400000000000000000000070771046102023000163040ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::Key, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("Your faithful window") .build(&event_loop) .unwrap(); let mut close_requested = false; event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = 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, state: ElementState::Released, .. }, .. } => { // WARNING: Consider using `key_without_modifers()` if available on your platform. // See the `key_binding` example match key.as_ref() { Key::Character("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. elwt.exit(); } } Key::Character("n") => { if close_requested { println!("Your window will continue to stay by your side."); close_requested = false; } } _ => (), } } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/icon.png000064400000000000000000000056061046102023000144170ustar 00000000000000‰PNG  IHDR szzôbKGDÿÿÿ ½§“ pHYs  šœtIMEâC\C" IDATX×ytUÕ½Ç?{ŸáŽIBE Ðs£ Š JS,EÀꞥ¥Ë:DÑZÂ-H@JE*Ê+åÙªˆ.­.áÕDÉE ‚ÈL@„„$äæŽçœýþÈe°u½õöZgí³öÙgý¾¿ù÷üW0$Rðì­sCôn¦öî@`¦þJ À`?°ØJU Ž¹!X@ÿ?…VVƒ!a~ T †Ê€éÀP 7J€Jí. ›êÞ¨Ž»üøo”€úw)dTV 4)˜_âPò#™Šbž€nR(lG€¥u6mÒMSØ4ž;9Ý. 5å¤]g Ð@|‡¬Ô¾¨ôŠÆ?=Pe lG›çÎ®Í ÌŒÒÓ§‹×"Ç'yaÅ:^[¼ýkþvé'|±³¸(¦:ªJ`!ˆ«‚_:‹8såËÒxv„M0Ô ¥Þ8% æM0ºk;3old`V” ÓÂ-\éi`ºÀQDÃQ5êGóÄ®œió÷›¸Ü´ÅD*ŽÖS¨¿"V\õu¸–Oì8 VÏPˆUhºpãŸã—E#_^&b Ž@7tâ ‹Õý˜_Ÿâ†ž]xòá |ð÷Óî}À±’Àf`%Uõüv§ÀBñ\I @0$RA†˜óñ+Jº~†áƒÆ‡oÈ.M{ÉÖÚ¸·|L&‹!¥ sï´%äæî{Êùô“íØ»•7þ0“Ò·;–7[’ˆ¸jè(5ƒ…¥Çxí8œ¬G0g'?i~7-—Úú Ä&`°`h,ð Ø@e¨—dn¨ÁSn›«_8²iCâôÉõïÊ›o½Ã°aCéÞ½;………45·i­#/7ÛQL›|Ó¦?Dyù {ô¤°G/6lÜÂÈá=8×Ð ð½ë*©FU`sª€$’×%‚å¸Òþ“½ï¼ÖbáÙVý5U¿yO·¼Ë²ÿÈì§Ã÷‹oe×;y|ædf¤Ñ‰3cê”rYºd>ùs©¯;Ôñ73åÞÛ¨«mÄ#e1Ju¨ Øü6$R)xÐŒC ØFUààN`óôûGóç¥3ù¦¶‘¥+ÖràðYr;¥1kz#†ô¥=ë(mŽC~n&/®Ú@ç¼tÜ\È [zò?›÷R1ýÊoq8a]Ë4)J ÁУÀr=U"ŸHÊÕIj|öÑ>Ÿ¬ãÆÂ<žváö(†®#¥ ¥-‚}I ·Ç8pä,§75°öõÙÄâ_©e€&U¬àq¸|ÑÛäΣ²èXJ†JYä%‚¡'%ŠõTö§>æHóÑûr”ÙKßãPMšZQBbºM4C';ËßçB)…”‚X<ÉëËIç¼L~öè |]Ó8q¦ž.¶%‹z ‚Œ¼)HíÖŽ`¯þÁЄë²áUÁ*€ŠGiå{¶ÿ|¬ÏOŸìNüׯ/ùáæ/1LènƒN9™¸½&cn/¢âwn¡”âRk”åU2|újNú+'¶|FUV:ÉHØIøÇ²âWïÙJjkPN‚`h UuÀvÖÆmå{¶Oü¾×;¬Èã!©Ov#fÙ´Y6I¥Û6‘ aÖ_¼ÈžN~òJ¾ùœO{—²±ÏX~?¶’ó¿z›ûz ‚h„òšO˜n(2¥À’¨é%7|‘‹þ\,—Ÿ2¾f‡¢OÉL‘Œµ¡™óyz·¦ëø³ï >”ßÒ@:;lÞûÞd .Ÿ%/|‘Óy7sÛég² iógñëƒ,) r¾¥‘“—/’Õ=æ³^|yÈD„„îb֮׬#_¬55Ô=„i$zðI:LÕýét;ùïC©Í±À—C«'MÙti>Å”/ß#+ÑήÖïúŽV³µx 1w:qÝdìñ­øãmL<¸Žœö&„ãàÑ QÔvìÈ ¶©®(µØ­““ŒOt›‡×÷f}Û4Ç+Ža'ÁMM5Dca.µ4ÐÇ4¹»ï`¾8އ62äÜ>Nd2æ›ÏÜp”´Dþx;Ý[jiÕݲ³iÑ‘|~ðÁŽh³t‘ŒU׌šUz´õ¤êÛp\DMB]s†¦lPB9èv’6—\'AÏd˜,_[F?±6ØýÙÑf† ˜á"=ÞJR3T·Ÿ¨îÁ’Rf&Lyþ’q@,¤*°Wb[#4yFÔ¥w!=ÖŠ¸. 4GhÜÐTCN¸Ÿ¤ÿ¥Óä6Ÿå®ƒcÁ¦ùdE/Õ=Øš†BÔ ¢†O2J‹4X[¾ú+¾…¥Á«C©¨Üñ°2Ý+ü‘f5wóbqKý!"¦[hTüh)S÷½ÃèŸ1¼HåàH‰BoÅÃ{¸GHÜÉš²ù*¿H-ù¨s)³@±ð&µ0Pv¥C ~÷Oxf"Z¢tói¬“ö¿Ïm 'ÒDÄðà²âÄu¶Ð(!‘ª#e!¯YK) ÃNbÚ Nf÷bmѽlï{' Éø‹, T¤q/dåŽ')_ÀðÕRÇŽ~ÄÈšdGšq[qL;#$¶©IW!•B:6Ž$t“ˆááD§Þlì7Ž/» &áÎD$ÚÏIå Result<(), impl std::error::Error> { SimpleLogger::new() .with_level(LevelFilter::Trace) .init() .unwrap(); println!("IME position will system default"); println!("Click to set IME position to cursor's"); println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info"); println!("Press F3 to cycle through IME purposes."); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64)) .build(&event_loop) .unwrap(); let mut ime_purpose = ImePurpose::Normal; let mut ime_allowed = true; window.set_ime_allowed(ime_allowed); let mut may_show_ime = false; let mut cursor_position = PhysicalPosition::new(0.0, 0.0); let mut ime_pos = PhysicalPosition::new(0.0, 0.0); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::CursorMoved { position, .. } => { cursor_position = position; } WindowEvent::MouseInput { state: ElementState::Released, .. } => { println!( "Setting ime position to {}, {}", cursor_position.x, cursor_position.y ); ime_pos = cursor_position; if may_show_ime { window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10)); } } WindowEvent::Ime(event) => { println!("{event:?}"); may_show_ime = event != Ime::Disabled; if may_show_ime { window.set_ime_cursor_area(ime_pos, PhysicalSize::new(10, 10)); } } WindowEvent::KeyboardInput { event, .. } => { println!("key: {event:?}"); if event.state == ElementState::Pressed && event.logical_key == NamedKey::F2 { ime_allowed = !ime_allowed; window.set_ime_allowed(ime_allowed); println!("\nIME allowed: {ime_allowed}\n"); } if event.state == ElementState::Pressed && event.logical_key == NamedKey::F3 { ime_purpose = match ime_purpose { ImePurpose::Normal => ImePurpose::Password, ImePurpose::Password => ImePurpose::Terminal, _ => ImePurpose::Normal, }; window.set_ime_purpose(ime_purpose); println!("\nIME purpose: {ime_purpose:?}\n"); } } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/key_binding.rs000064400000000000000000000042611046102023000156050ustar 00000000000000#![allow(clippy::single_match)] #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] use winit::{ dpi::LogicalSize, event::{ElementState, Event, WindowEvent}, event_loop::EventLoop, keyboard::{Key, ModifiersState}, // WARNING: This is not available on all platforms (for example on the web). platform::modifier_supplement::KeyEventExtModifierSupplement, window::WindowBuilder, }; #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] fn main() { println!("This example is not supported on this platform"); } #[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] fn main() -> Result<(), impl std::error::Error> { #[path = "util/fill.rs"] mod fill; simple_logger::SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_inner_size(LogicalSize::new(400.0, 200.0)) .build(&event_loop) .unwrap(); let mut modifiers = ModifiersState::default(); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::ModifiersChanged(new) => { modifiers = new.state(); } WindowEvent::KeyboardInput { event, .. } => { if event.state == ElementState::Pressed && !event.repeat { match event.key_without_modifiers().as_ref() { Key::Character("1") => { if modifiers.shift_key() { println!("Shift + 1 | logical_key: {:?}", event.logical_key); } else { println!("1"); } } _ => (), } } } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } }; }) } winit-0.29.15/examples/monitor_list.rs000064400000000000000000000032241046102023000160430ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::monitor::MonitorHandle; use winit::{event_loop::EventLoop, window::WindowBuilder}; fn main() { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap(); if let Some(mon) = window.primary_monitor() { print_info("Primary output", mon); } for mon in window.available_monitors() { if Some(&mon) == window.primary_monitor().as_ref() { continue; } println!(); print_info("Output", mon); } } fn print_info(intro: &str, monitor: MonitorHandle) { if let Some(name) = monitor.name() { println!("{intro}: {name}"); } else { println!("{intro}: [no name]"); } let PhysicalSize { width, height } = monitor.size(); print!(" Current mode: {width}x{height}"); if let Some(m_hz) = monitor.refresh_rate_millihertz() { println!(" @ {}.{} Hz", m_hz / 1000, m_hz % 1000); } else { println!(); } let PhysicalPosition { x, y } = monitor.position(); println!(" Position: {x},{y}"); println!(" Scale factor: {}", monitor.scale_factor()); println!(" Available modes (width x height x bit-depth):"); for mode in monitor.video_modes() { let PhysicalSize { width, height } = mode.size(); let bits = mode.bit_depth(); let m_hz = mode.refresh_rate_millihertz(); println!( " {width}x{height}x{bits} @ {}.{} Hz", m_hz / 1000, m_hz % 1000 ); } } winit-0.29.15/examples/mouse_wheel.rs000064400000000000000000000046601046102023000156420ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("Mouse Wheel events") .build(&event_loop) .unwrap(); println!( r" When using so called 'natural scrolling' (scrolling that acts like on a touch screen), this is what to expect: Moving your finger downwards on a scroll wheel should make the window move down, and you should see a positive Y scroll value. When moving fingers on a trackpad down and to the right, you should see positive X and Y deltas, and the window should move down and to the right. With reverse scrolling, you should see the inverse behavior. In both cases the example window should move like the content of a scroll area in any other application. In other words, the deltas indicate the direction in which to move the content (in this case the window)." ); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::MouseWheel { delta, .. } => match delta { winit::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) } winit::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) } }, WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/multithreaded.rs000064400000000000000000000244001046102023000161530ustar 00000000000000#![allow(clippy::single_match)] #[cfg(not(wasm_platform))] fn main() -> Result<(), impl std::error::Error> { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use simple_logger::SimpleLogger; use winit::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::{Key, ModifiersState, NamedKey}, window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel}, }; const WINDOW_COUNT: usize = 3; const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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.get(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.get(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.get(video_mode_id).unwrap() ); } } WindowEvent::ModifiersChanged(new) => { modifiers = new.state(); } WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Released, logical_key: key, .. }, .. } => { use NamedKey::{ArrowLeft, ArrowRight}; window.set_title(&format!("{key:?}")); let state = !modifiers.shift_key(); match key { // Cycle through video modes Key::Named(ArrowRight) | Key::Named(ArrowLeft) => { if key == ArrowLeft { video_mode_id = video_mode_id.saturating_sub(1); } else if key == ArrowRight { video_mode_id = (video_modes.len() - 1).min(video_mode_id + 1); } println!("Picking video mode: {}", video_modes[video_mode_id]); } // WARNING: Consider using `key_without_modifers()` if available on your platform. // See the `key_binding` example Key::Character(ch) => match ch.to_lowercase().as_str() { "1" => window.set_window_level(WindowLevel::AlwaysOnTop), "2" => window.set_window_level(WindowLevel::AlwaysOnBottom), "3" => window.set_window_level(WindowLevel::Normal), "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[video_mode_id].clone(), )), (false, _) => None, }), "l" if state => { if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) { println!("error: {err}"); } } "g" if state => { if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) { println!("error: {err}"); } } "g" | "l" if !state => { if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) { println!("error: {err}"); } } "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" => { let _ = window.request_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); } _ => (), }, _ => (), } } _ => (), } } }); } event_loop.run(move |event, elwt| { if window_senders.is_empty() { elwt.exit() } match event { Event::WindowEvent { event, window_id } => match event { WindowEvent::CloseRequested | WindowEvent::Destroyed | WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Released, logical_key: Key::Named(NamedKey::Escape), .. }, .. } => { window_senders.remove(&window_id); } _ => { if let Some(tx) = window_senders.get(&window_id) { tx.send(event).unwrap(); } } }, _ => {} } }) } #[cfg(wasm_platform)] fn main() { panic!("Example not supported on Wasm"); } winit-0.29.15/examples/multiwindow.rs000064400000000000000000000040511046102023000157020ustar 00000000000000#![allow(clippy::single_match)] use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, WindowEvent}, event_loop::EventLoop, keyboard::{Key, NamedKey}, window::Window, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let mut windows = HashMap::new(); for _ in 0..3 { let window = Window::new(&event_loop).unwrap(); println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); } println!("Press N to open a new window."); event_loop.run(move |event, elwt| { 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() { elwt.exit(); } } WindowEvent::KeyboardInput { event, is_synthetic: false, .. } if event.state == ElementState::Pressed => match event.logical_key { Key::Named(NamedKey::Escape) => elwt.exit(), Key::Character(c) if c == "n" || c == "N" => { let window = Window::new(elwt).unwrap(); println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); } _ => (), }, WindowEvent::RedrawRequested => { if let Some(window) = windows.get(&window_id) { fill::fill_window(window); } } _ => (), } } }) } winit-0.29.15/examples/request_redraw.rs000064400000000000000000000021511046102023000163530ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); event_loop.run(move |event, elwt| { println!("{event:?}"); if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::MouseInput { state: ElementState::Released, .. } => { window.request_redraw(); } WindowEvent::RedrawRequested => { println!("\nredrawing!\n"); fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/request_redraw_threaded.rs000064400000000000000000000026251046102023000202210ustar 00000000000000#![allow(clippy::single_match)] #[cfg(not(wasm_platform))] fn main() -> Result<(), impl std::error::Error> { use std::{sync::Arc, thread, time}; use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = { let window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); Arc::new(window) }; thread::spawn({ let window = window.clone(); move || loop { thread::sleep(time::Duration::from_secs(1)); window.request_redraw(); } }); event_loop.run(move |event, elwt| { println!("{event:?}"); match event { Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(), Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { println!("\nredrawing!\n"); fill::fill_window(&window); } _ => (), } }) } #[cfg(wasm_platform)] fn main() { unimplemented!() // `Window` can't be sent between threads } winit-0.29.15/examples/resizable.rs000064400000000000000000000032401046102023000152770ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::{KeyCode, PhysicalKey}, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let mut resizable = false; let window = WindowBuilder::new() .with_title("Hit space to toggle resizability.") .with_inner_size(LogicalSize::new(600.0, 300.0)) .with_min_inner_size(LogicalSize::new(400.0, 200.0)) .with_max_inner_size(LogicalSize::new(800.0, 400.0)) .with_resizable(resizable) .build(&event_loop) .unwrap(); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(KeyCode::Space), state: ElementState::Released, .. }, .. } => { resizable = !resizable; println!("Resizable: {resizable}"); window.set_resizable(resizable); } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } }; }) } winit-0.29.15/examples/startup_notification.rs000064400000000000000000000101221046102023000175640ustar 00000000000000//! Demonstrates the use of startup notifications on Linux. #[cfg(any(x11_platform, wayland_platform))] #[path = "./util/fill.rs"] mod fill; #[cfg(any(x11_platform, wayland_platform))] mod example { use std::collections::HashMap; use std::rc::Rc; use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; use winit::platform::startup_notify::{ EventLoopExtStartupNotify, WindowBuilderExtStartupNotify, WindowExtStartupNotify, }; use winit::window::{Window, WindowBuilder, WindowId}; pub(super) fn main() -> Result<(), impl std::error::Error> { // Create the event loop and get the activation token. let event_loop = EventLoop::new().unwrap(); let mut current_token = match event_loop.read_token_from_env() { Some(token) => Some(token), None => { println!("No startup notification token found in environment."); None } }; let mut windows: HashMap> = HashMap::new(); let mut counter = 0; let mut create_first_window = false; event_loop.run(move |event, elwt| { match event { Event::Resumed => create_first_window = true, Event::WindowEvent { window_id, event } => match event { WindowEvent::KeyboardInput { event: KeyEvent { logical_key, state: ElementState::Pressed, .. }, .. } => { if logical_key == "n" { if let Some(window) = windows.get(&window_id) { // Request a new activation token on this window. // Once we get it we will use it to create a window. window .request_activation_token() .expect("Failed to request activation token."); } } } WindowEvent::CloseRequested => { // Remove the window from the map. windows.remove(&window_id); if windows.is_empty() { elwt.exit(); return; } } WindowEvent::ActivationTokenDone { token, .. } => { current_token = Some(token); } WindowEvent::RedrawRequested => { if let Some(window) = windows.get(&window_id) { super::fill::fill_window(window); } } _ => {} }, _ => (), } // See if we've passed the deadline. if current_token.is_some() || create_first_window { // Create the initial window. let window = { let mut builder = WindowBuilder::new().with_title(format!("Window {}", counter)); if let Some(token) = current_token.take() { println!("Creating a window with token {token:?}"); builder = builder.with_activation_token(token); } Rc::new(builder.build(elwt).unwrap()) }; // Add the window to the map. windows.insert(window.id(), window.clone()); counter += 1; create_first_window = false; } }) } } #[cfg(any(x11_platform, wayland_platform))] fn main() -> Result<(), impl std::error::Error> { example::main() } #[cfg(not(any(x11_platform, wayland_platform)))] fn main() { println!("This example is only supported on X11 and Wayland platforms."); } winit-0.29.15/examples/theme.rs000064400000000000000000000044111046102023000144220ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::Key, window::{Theme, WindowBuilder}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_theme(Some(Theme::Dark)) .build(&event_loop) .unwrap(); println!("Initial theme: {:?}", window.theme()); println!("debugging keys:"); println!(" (A) Automatic theme"); println!(" (L) Light theme"); println!(" (D) Dark theme"); event_loop.run(move |event, elwt| { if let Event::WindowEvent { window_id, event } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::ThemeChanged(theme) if window_id == window.id() => { println!("Theme is changed: {theme:?}") } WindowEvent::KeyboardInput { event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. }, .. } => match key.as_ref() { Key::Character("A" | "a") => { println!("Theme was: {:?}", window.theme()); window.set_theme(None); } Key::Character("L" | "l") => { println!("Theme was: {:?}", window.theme()); window.set_theme(Some(Theme::Light)); } Key::Character("D" | "d") => { println!("Theme was: {:?}", window.theme()); window.set_theme(Some(Theme::Dark)); } _ => (), }, WindowEvent::RedrawRequested => { println!("\nredrawing!\n"); fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/timer.rs000064400000000000000000000027321046102023000144440ustar 00000000000000#![allow(clippy::single_match)] use std::time::Duration; #[cfg(not(wasm_platform))] use std::time::Instant; #[cfg(wasm_platform)] use web_time::Instant; use simple_logger::SimpleLogger; use winit::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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, elwt| { println!("{event:?}"); match event { Event::NewEvents(StartCause::Init) => { elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length)); } Event::NewEvents(StartCause::ResumeTimeReached { .. }) => { elwt.set_control_flow(ControlFlow::WaitUntil(Instant::now() + timer_length)); println!("\nTimer\n"); } Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => elwt.exit(), Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { fill::fill_window(&window); } _ => (), } }) } winit-0.29.15/examples/touchpad_gestures.rs000064400000000000000000000030221046102023000170450ustar 00000000000000use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("Touchpad gestures") .build(&event_loop) .unwrap(); println!("Only supported on macOS at the moment."); event_loop.run(move |event, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::TouchpadMagnify { delta, .. } => { if delta > 0.0 { println!("Zoomed in {delta}"); } else { println!("Zoomed out {delta}"); } } WindowEvent::SmartMagnify { .. } => { println!("Smart zoom"); } WindowEvent::TouchpadRotate { delta, .. } => { if delta > 0.0 { println!("Rotated counterclockwise {delta}"); } else { println!("Rotated clockwise {delta}"); } } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/transparent.rs000064400000000000000000000016451046102023000156670ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_decorations(false) .with_transparent(true) .build(&event_loop) .unwrap(); window.set_title("A fantastic window!"); event_loop.run(move |event, elwt| { println!("{event:?}"); if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/util/fill.rs000064400000000000000000000075661046102023000152410ustar 00000000000000//! Fill the window buffer with a solid color. //! //! Launching a window without drawing to it has unpredictable results varying from platform to //! platform. In order to have well-defined examples, this module provides an easy way to //! fill the window buffer with a solid color. //! //! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could //! also be used to fill the window buffer, but they are more complicated to use. #[allow(unused_imports)] pub use platform::cleanup_window; pub use platform::fill_window; #[cfg(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios"))))] mod platform { use std::cell::RefCell; use std::collections::HashMap; use std::mem::ManuallyDrop; use std::num::NonZeroU32; use softbuffer::{Context, Surface}; use winit::window::Window; use winit::window::WindowId; thread_local! { // NOTE: You should never do things like that, create context and drop it before // you drop the event loop. We do this for brevity to not blow up examples. We use // ManuallyDrop to prevent destructors from running. // // A static, thread-local map of graphics contexts to open windows. static GC: ManuallyDrop>> = const { ManuallyDrop::new(RefCell::new(None)) }; } /// The graphics context used to draw to a window. struct GraphicsContext { /// The global softbuffer context. context: Context, /// The hash map of window IDs to surfaces. surfaces: HashMap, } impl GraphicsContext { fn new(w: &Window) -> Self { Self { context: unsafe { Context::new(w) }.expect("Failed to create a softbuffer context"), surfaces: HashMap::new(), } } fn create_surface(&mut self, window: &Window) -> &mut Surface { self.surfaces.entry(window.id()).or_insert_with(|| { unsafe { Surface::new(&self.context, window) } .expect("Failed to create a softbuffer surface") }) } fn destroy_surface(&mut self, window: &Window) { self.surfaces.remove(&window.id()); } } pub fn fill_window(window: &Window) { GC.with(|gc| { let size = window.inner_size(); let (Some(width), Some(height)) = (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) else { return; }; // Either get the last context used or create a new one. let mut gc = gc.borrow_mut(); let surface = gc .get_or_insert_with(|| GraphicsContext::new(window)) .create_surface(window); // Fill a buffer with a solid color. const DARK_GRAY: u32 = 0xFF181818; surface .resize(width, height) .expect("Failed to resize the softbuffer surface"); let mut buffer = surface .buffer_mut() .expect("Failed to get the softbuffer buffer"); buffer.fill(DARK_GRAY); buffer .present() .expect("Failed to present the softbuffer buffer"); }) } #[allow(dead_code)] pub fn cleanup_window(window: &Window) { GC.with(|gc| { let mut gc = gc.borrow_mut(); if let Some(context) = gc.as_mut() { context.destroy_surface(window); } }); } } #[cfg(not(all(feature = "rwh_05", not(any(target_os = "android", target_os = "ios")))))] mod platform { pub fn fill_window(_window: &winit::window::Window) { // No-op on mobile platforms. } #[allow(dead_code)] pub fn cleanup_window(_window: &winit::window::Window) { // No-op on mobile platforms. } } winit-0.29.15/examples/video_modes.rs000064400000000000000000000010161046102023000156130ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::event_loop::EventLoop; fn main() { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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}"); } } winit-0.29.15/examples/web.rs000064400000000000000000000115731046102023000141040ustar 00000000000000#![allow(clippy::disallowed_methods, clippy::single_match)] use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::Key, window::{Fullscreen, WindowBuilder}, }; pub fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new().unwrap(); let builder = WindowBuilder::new().with_title("A fantastic window!"); #[cfg(wasm_platform)] let builder = { use winit::platform::web::WindowBuilderExtWebSys; builder.with_append(true) }; let window = builder.build(&event_loop).unwrap(); #[cfg(wasm_platform)] let log_list = wasm::insert_canvas_and_create_log_list(&window); event_loop.run(move |event, elwt| { #[cfg(wasm_platform)] wasm::log_event(&log_list, &event); match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => elwt.exit(), Event::AboutToWait => { window.request_redraw(); } Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Character(c), state: ElementState::Released, .. }, .. }, } if window_id == window.id() && c == "f" => { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { window.set_fullscreen(Some(Fullscreen::Borderless(None))); } } _ => (), } }) } #[cfg(wasm_platform)] mod wasm { use std::num::NonZeroU32; use softbuffer::{Surface, SurfaceExtWeb}; use wasm_bindgen::prelude::*; use winit::{ event::{Event, WindowEvent}, window::Window, }; #[wasm_bindgen(start)] pub fn run() { console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); #[allow(clippy::main_recursion)] let _ = super::main(); } pub fn insert_canvas_and_create_log_list(window: &Window) -> web_sys::Element { use winit::platform::web::WindowExtWebSys; let canvas = window.canvas().unwrap(); let mut surface = Surface::from_canvas(canvas.clone()).unwrap(); surface .resize( NonZeroU32::new(canvas.width()).unwrap(), NonZeroU32::new(canvas.height()).unwrap(), ) .unwrap(); let mut buffer = surface.buffer_mut().unwrap(); buffer.fill(0xFFF0000); buffer.present().unwrap(); let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); let style = &canvas.style(); style.set_property("margin", "50px").unwrap(); // Use to test interactions with border and padding. //style.set_property("border", "50px solid black").unwrap(); //style.set_property("padding", "50px").unwrap(); let log_header = document.create_element("h2").unwrap(); log_header.set_text_content(Some("Event Log")); body.append_child(&log_header).unwrap(); let log_list = document.create_element("ul").unwrap(); body.append_child(&log_list).unwrap(); log_list } pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) { log::debug!("{:?}", event); // Getting access to browser logs requires a lot of setup on mobile devices. // So we implement this basic logging system into the page to give developers an easy alternative. // As a bonus its also kind of handy on desktop. let event = match event { Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => None, Event::WindowEvent { event, .. } => Some(format!("{event:?}")), Event::Resumed | Event::Suspended => Some(format!("{event:?}")), _ => None, }; if let Some(event) = event { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let log = document.create_element("li").unwrap(); let date = js_sys::Date::new_0(); log.set_text_content(Some(&format!( "{:02}:{:02}:{:02}.{:03}: {event}", date.get_hours(), date.get_minutes(), date.get_seconds(), date.get_milliseconds(), ))); log_list .insert_before(&log, log_list.first_child().as_ref()) .unwrap(); } } } winit-0.29.15/examples/web_aspect_ratio.rs000064400000000000000000000074111046102023000166350ustar 00000000000000#![allow(clippy::disallowed_methods)] pub fn main() { println!("This example must be run with cargo run-wasm --example web_aspect_ratio") } #[cfg(wasm_platform)] mod wasm { use wasm_bindgen::prelude::*; use web_sys::HtmlCanvasElement; use winit::{ dpi::PhysicalSize, event::{Event, WindowEvent}, event_loop::EventLoop, platform::web::WindowBuilderExtWebSys, window::{Window, WindowBuilder}, }; const EXPLANATION: &str = " This example draws a circle in the middle of a 4/1 aspect ratio canvas which acts as a useful demonstration of winit's resize handling on web. Even when the browser window is resized or aspect-ratio of the canvas changed the circle should always: * Fill the entire width or height of the canvas (whichever is smaller) without exceeding it. * Be perfectly round * Not be blurry or pixelated (there is no antialiasing so you may still see jagged edges depending on the DPI of your monitor) Currently winit does not handle resizes on web so the circle is rendered incorrectly. This example demonstrates the desired future functionality which will possibly be provided by https://github.com/rust-windowing/winit/pull/2074 "; #[wasm_bindgen(start)] pub fn run() { console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") // When running in a non-wasm environment this would set the window size to 100x100. // However in this example it just sets a default initial size of 100x100 that is immediately overwritten due to the layout + styling of the page. .with_inner_size(PhysicalSize::new(100, 100)) .with_append(true) .build(&event_loop) .unwrap(); let canvas = create_canvas(&window); // Render once with the size info we currently have render_circle(&canvas, window.inner_size()); let _ = event_loop.run(move |event, _| match event { Event::WindowEvent { event: WindowEvent::Resized(resize), window_id, } if window_id == window.id() => { render_circle(&canvas, resize); } _ => (), }); } pub fn create_canvas(window: &Window) -> HtmlCanvasElement { use winit::platform::web::WindowExtWebSys; let web_window = web_sys::window().unwrap(); let document = web_window.document().unwrap(); let body = document.body().unwrap(); // Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes. let canvas = window.canvas().unwrap(); canvas .style() .set_css_text("display: block; background-color: crimson; margin: auto; width: 50%; aspect-ratio: 4 / 1;"); let explanation = document.create_element("pre").unwrap(); explanation.set_text_content(Some(EXPLANATION)); body.append_child(&explanation).unwrap(); canvas } pub fn render_circle(canvas: &HtmlCanvasElement, size: PhysicalSize) { log::info!("rendering circle with canvas size: {:?}", size); let context = canvas .get_context("2d") .unwrap() .unwrap() .dyn_into::() .unwrap(); context.begin_path(); context .arc( size.width as f64 / 2.0, size.height as f64 / 2.0, size.width.min(size.height) as f64 / 2.0, 0.0, std::f64::consts::PI * 2.0, ) .unwrap(); context.fill(); } } winit-0.29.15/examples/window.rs000064400000000000000000000023071046102023000146310ustar 00000000000000#![allow(clippy::single_match)] use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(&event_loop) .unwrap(); event_loop.run(move |event, elwt| { println!("{event:?}"); match event { Event::WindowEvent { event, window_id } if window_id == window.id() => match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::RedrawRequested => { // Notify the windowing system that we'll be presenting to the window. window.pre_present_notify(); fill::fill_window(&window); } _ => (), }, Event::AboutToWait => { window.request_redraw(); } _ => (), } }) } winit-0.29.15/examples/window_buttons.rs000064400000000000000000000045201046102023000164060ustar 00000000000000#![allow(clippy::single_match)] // This example is used by developers to test various window functions. use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{DeviceEvents, EventLoop}, keyboard::Key, window::{WindowBuilder, WindowButtons}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_inner_size(LogicalSize::new(300.0, 300.0)) .build(&event_loop) .unwrap(); eprintln!("Window Button keys:"); eprintln!(" (F) Toggle close button"); eprintln!(" (G) Toggle maximize button"); eprintln!(" (H) Toggle minimize button"); event_loop.listen_device_events(DeviceEvents::Always); event_loop.run(move |event, elwt| { if let Event::WindowEvent { window_id, event } = event { match event { WindowEvent::KeyboardInput { event: KeyEvent { logical_key: key, state: ElementState::Pressed, .. }, .. } => match key.as_ref() { Key::Character("F" | "f") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE); } Key::Character("G" | "g") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE); } Key::Character("H" | "h") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE); } _ => (), }, WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(), WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), } } }) } winit-0.29.15/examples/window_debug.rs000064400000000000000000000120701046102023000157750ustar 00000000000000#![allow(clippy::single_match)] // This example is used by developers to test various window functions. use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, event_loop::{DeviceEvents, EventLoop}, keyboard::{Key, KeyCode, PhysicalKey}, window::{Fullscreen, WindowBuilder}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); 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 preffered monitor"); eprintln!(" (M) Toggle minimized"); eprintln!(" (Q) Quit event loop"); eprintln!(" (V) Toggle visibility"); eprintln!(" (X) Toggle maximized"); let mut minimized = false; let mut visible = true; event_loop.listen_device_events(DeviceEvents::Always); event_loop.run(move |event, elwt| { 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 { PhysicalKey::Code(KeyCode::KeyM) => { if minimized { minimized = !minimized; window.set_minimized(minimized); window.focus_window(); } } PhysicalKey::Code(KeyCode::KeyV) => { if !visible { visible = !visible; window.set_visible(visible); } } _ => (), }, Event::WindowEvent { window_id, event } => match event { WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Character(key_str), state: ElementState::Pressed, .. }, .. } => match key_str.as_ref() { // 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))); } } "m" => { minimized = !minimized; window.set_minimized(minimized); } "q" => { elwt.exit(); } "v" => { visible = !visible; window.set_visible(visible); } "x" => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } _ => (), }, WindowEvent::CloseRequested if window_id == window.id() => elwt.exit(), WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), }, _ => (), } }) } winit-0.29.15/examples/window_drag_resize.rs000064400000000000000000000113231046102023000172050ustar 00000000000000//! Demonstrates capability to create in-app draggable regions for client-side decoration support. use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event_loop::EventLoop, keyboard::Key, window::{CursorIcon, ResizeDirection, WindowBuilder}, }; const BORDER: f64 = 8.0; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0)) .with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0)) .with_decorations(false) .build(&event_loop) .unwrap(); let mut border = false; let mut cursor_location = None; event_loop.run(move |event, elwt| match event { Event::NewEvents(StartCause::Init) => { eprintln!("Press 'B' to toggle borderless") } Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::CursorMoved { position, .. } => { if !window.is_decorated() { let new_location = cursor_resize_direction(window.inner_size(), position, BORDER); if new_location != cursor_location { cursor_location = new_location; window.set_cursor_icon(cursor_direction_icon(cursor_location)) } } } WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => { if let Some(dir) = cursor_location { let _res = window.drag_resize_window(dir); } else if !window.is_decorated() { let _res = window.drag_window(); } } WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Released, logical_key: Key::Character(c), .. }, .. } if matches!(c.as_ref(), "B" | "b") => { border = !border; window.set_decorations(border); } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), }, _ => (), }) } fn cursor_direction_icon(resize_direction: Option) -> CursorIcon { match resize_direction { Some(resize_direction) => match resize_direction { ResizeDirection::East => CursorIcon::EResize, ResizeDirection::North => CursorIcon::NResize, ResizeDirection::NorthEast => CursorIcon::NeResize, ResizeDirection::NorthWest => CursorIcon::NwResize, ResizeDirection::South => CursorIcon::SResize, ResizeDirection::SouthEast => CursorIcon::SeResize, ResizeDirection::SouthWest => CursorIcon::SwResize, ResizeDirection::West => CursorIcon::WResize, }, None => CursorIcon::Default, } } fn cursor_resize_direction( win_size: winit::dpi::PhysicalSize, position: winit::dpi::PhysicalPosition, border_size: f64, ) -> Option { enum XDirection { West, East, Default, } enum YDirection { North, South, Default, } let xdir = if position.x < border_size { XDirection::West } else if position.x > (win_size.width as f64 - border_size) { XDirection::East } else { XDirection::Default }; let ydir = if position.y < border_size { YDirection::North } else if position.y > (win_size.height as f64 - border_size) { YDirection::South } else { YDirection::Default }; Some(match xdir { XDirection::West => match ydir { YDirection::North => ResizeDirection::NorthWest, YDirection::South => ResizeDirection::SouthWest, YDirection::Default => ResizeDirection::West, }, XDirection::East => match ydir { YDirection::North => ResizeDirection::NorthEast, YDirection::South => ResizeDirection::SouthEast, YDirection::Default => ResizeDirection::East, }, XDirection::Default => match ydir { YDirection::North => ResizeDirection::North, YDirection::South => ResizeDirection::South, YDirection::Default => return None, }, }) } winit-0.29.15/examples/window_icon.rs000064400000000000000000000040421046102023000156370ustar 00000000000000#![allow(clippy::single_match)] use std::path::Path; use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::{Icon, WindowBuilder}, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); // You'll have to choose an icon size at your own discretion. On X11, the desired size varies // by WM, and on Windows, you still 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().unwrap(); let window = WindowBuilder::new() .with_title("An iconic window!") // At present, this only does anything on Windows and X11, 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, elwt| { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::DroppedFile(path) => { window.set_window_icon(Some(load_icon(&path))); } WindowEvent::RedrawRequested => fill::fill_window(&window), _ => (), } } }) } fn load_icon(path: &Path) -> Icon { let (icon_rgba, icon_width, icon_height) = { 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") } winit-0.29.15/examples/window_on_demand.rs000064400000000000000000000065371046102023000166460ustar 00000000000000#![allow(clippy::single_match)] // Limit this example to only compatible platforms. #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))] fn main() -> Result<(), impl std::error::Error> { use std::time::Duration; use simple_logger::SimpleLogger; use winit::{ error::EventLoopError, event::{Event, WindowEvent}, event_loop::EventLoop, platform::run_on_demand::EventLoopExtRunOnDemand, window::{Window, WindowBuilder, WindowId}, }; #[path = "util/fill.rs"] mod fill; #[derive(Default)] struct App { window_id: Option, window: Option, } SimpleLogger::new().init().unwrap(); let mut event_loop = EventLoop::new().unwrap(); fn run_app(event_loop: &mut EventLoop<()>, idx: usize) -> Result<(), EventLoopError> { let mut app = App::default(); event_loop.run_on_demand(move |event, elwt| { println!("Run {idx}: {:?}", event); if let Some(window) = &app.window { match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window.id() == window_id => { println!("--------------------------------------------------------- Window {idx} CloseRequested"); fill::cleanup_window(window); app.window = None; } Event::AboutToWait => window.request_redraw(), Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { fill::fill_window(window); } _ => (), } } else if let Some(id) = app.window_id { match event { Event::WindowEvent { event: WindowEvent::Destroyed, window_id, } if id == window_id => { println!("--------------------------------------------------------- Window {idx} Destroyed"); app.window_id = None; elwt.exit(); } _ => (), } } else if let Event::Resumed = event { let window = WindowBuilder::new() .with_title("Fantastic window number one!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(elwt) .unwrap(); app.window_id = Some(window.id()); app.window = Some(window); } }) } run_app(&mut event_loop, 1)?; println!("--------------------------------------------------------- Finished first loop"); println!("--------------------------------------------------------- Waiting 5 seconds"); std::thread::sleep(Duration::from_secs(5)); let ret = run_app(&mut event_loop, 2); println!("--------------------------------------------------------- Finished second loop"); ret } #[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))] fn main() { println!("This example is not supported on this platform"); } winit-0.29.15/examples/window_option_as_alt.rs000064400000000000000000000043401046102023000175430ustar 00000000000000#![allow(clippy::single_match)] #[cfg(target_os = "macos")] use winit::platform::macos::{OptionAsAlt, WindowExtMacOS}; #[cfg(target_os = "macos")] use winit::{ event::ElementState, event::{Event, MouseButton, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; #[cfg(target_os = "macos")] #[path = "util/fill.rs"] mod fill; /// Prints the keyboard events characters received when option_is_alt is true versus false. /// A left mouse click will toggle option_is_alt. #[cfg(target_os = "macos")] fn main() -> Result<(), impl std::error::Error> { let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(&event_loop) .unwrap(); window.set_ime_allowed(true); let mut option_as_alt = window.option_as_alt(); event_loop.run(move |event, elwt| match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => elwt.exit(), Event::WindowEvent { event, .. } => match event { WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, .. } => { option_as_alt = match option_as_alt { OptionAsAlt::None => OptionAsAlt::OnlyLeft, OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight, OptionAsAlt::OnlyRight => OptionAsAlt::Both, OptionAsAlt::Both => OptionAsAlt::None, }; println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}"); window.set_option_as_alt(option_as_alt); } WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"), WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), }, Event::AboutToWait => { window.request_redraw(); } _ => (), }) } #[cfg(not(target_os = "macos"))] fn main() { println!("This example is only supported on MacOS"); } winit-0.29.15/examples/window_pump_events.rs000064400000000000000000000042441046102023000172600ustar 00000000000000#![allow(clippy::single_match)] // Limit this example to only compatible platforms. #[cfg(any( windows_platform, macos_platform, x11_platform, wayland_platform, android_platform, ))] fn main() -> std::process::ExitCode { use std::{process::ExitCode, thread::sleep, time::Duration}; use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; let mut event_loop = EventLoop::new().unwrap(); SimpleLogger::new().init().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) .unwrap(); 'main: loop { let timeout = Some(Duration::ZERO); let status = event_loop.pump_events(timeout, |event, elwt| { if let Event::WindowEvent { event, .. } = &event { // Print only Window events to reduce noise println!("{event:?}"); } match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => elwt.exit(), Event::AboutToWait => { window.request_redraw(); } Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { fill::fill_window(&window); } _ => (), } }); if let PumpStatus::Exit(exit_code) = status { break 'main ExitCode::from(exit_code as u8); } // Sleep for 1/60 second to simulate application work // // Since `pump_events` doesn't block it will be important to // throttle the loop in the app somehow. println!("Update()"); sleep(Duration::from_millis(16)); } } #[cfg(any(ios_platform, wasm_platform, orbital_platform))] fn main() { println!("This platform doesn't support pump_events."); } winit-0.29.15/examples/window_resize_increments.rs000064400000000000000000000032061046102023000204400ustar 00000000000000use log::debug; use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, event::{ElementState, Event, WindowEvent}, event_loop::EventLoop, keyboard::NamedKey, window::WindowBuilder, }; #[path = "util/fill.rs"] mod fill; fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("A fantastic window!") .with_inner_size(LogicalSize::new(128.0, 128.0)) .with_resize_increments(LogicalSize::new(25.0, 25.0)) .build(&event_loop) .unwrap(); let mut has_increments = true; event_loop.run(move |event, elwt| match event { Event::WindowEvent { event, window_id } if window_id == window.id() => match event { WindowEvent::CloseRequested => elwt.exit(), WindowEvent::KeyboardInput { event, .. } if event.logical_key == NamedKey::Space && event.state == ElementState::Released => { has_increments = !has_increments; let new_increments = match window.resize_increments() { Some(_) => None, None => Some(LogicalSize::new(25.0, 25.0)), }; debug!("Had increments: {}", new_increments.is_none()); window.set_resize_increments(new_increments); } WindowEvent::RedrawRequested => { fill::fill_window(&window); } _ => (), }, Event::AboutToWait => window.request_redraw(), _ => (), }) } winit-0.29.15/examples/window_tabbing.rs000064400000000000000000000076071046102023000163270ustar 00000000000000#![allow(clippy::single_match)] #[cfg(target_os = "macos")] use std::{collections::HashMap, num::NonZeroUsize}; #[cfg(target_os = "macos")] use simple_logger::SimpleLogger; #[cfg(target_os = "macos")] use winit::{ event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::{Key, NamedKey}, platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}, window::{Window, WindowBuilder}, }; #[cfg(target_os = "macos")] #[path = "util/fill.rs"] mod fill; #[cfg(target_os = "macos")] fn main() -> Result<(), impl std::error::Error> { SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new().unwrap(); let mut windows = HashMap::new(); let window = Window::new(&event_loop).unwrap(); println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); println!("Press N to open a new window."); event_loop.run(move |event, elwt| { 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() { elwt.exit(); } } WindowEvent::Resized(_) => { if let Some(window) = windows.get(&window_id) { window.request_redraw(); } } WindowEvent::KeyboardInput { event: KeyEvent { state: ElementState::Pressed, logical_key, .. }, is_synthetic: false, .. } => match logical_key.as_ref() { Key::Character("t") => { let tabbing_id = windows.get(&window_id).unwrap().tabbing_identifier(); let window = WindowBuilder::new() .with_tabbing_identifier(&tabbing_id) .build(elwt) .unwrap(); println!("Added a new tab: {:?}", window.id()); windows.insert(window.id(), window); } Key::Character("w") => { let _ = windows.remove(&window_id); } Key::Named(NamedKey::ArrowRight) => { windows.get(&window_id).unwrap().select_next_tab(); } Key::Named(NamedKey::ArrowLeft) => { windows.get(&window_id).unwrap().select_previous_tab(); } Key::Character(ch) => { if let Ok(index) = ch.parse::() { let index = index.get(); // Select the last tab when pressing `9`. let window = windows.get(&window_id).unwrap(); if index == 9 { window.select_tab_at_index(window.num_tabs() - 1) } else { window.select_tab_at_index(index - 1); } } } _ => (), }, WindowEvent::RedrawRequested => { if let Some(window) = windows.get(&window_id) { fill::fill_window(window); } } _ => (), } } }) } #[cfg(not(target_os = "macos"))] fn main() { println!("This example is only supported on MacOS"); } winit-0.29.15/examples/x11_embed.rs000064400000000000000000000041171046102023000150700ustar 00000000000000//! A demonstration of embedding a winit window in an existing X11 application. #[cfg(x11_platform)] #[path = "util/fill.rs"] mod fill; #[cfg(x11_platform)] mod imple { use super::fill; use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, platform::x11::WindowBuilderExtX11, window::WindowBuilder, }; pub(super) fn entry() -> Result<(), Box> { // First argument should be a 32-bit X11 window ID. let parent_window_id = std::env::args() .nth(1) .ok_or("Expected a 32-bit X11 window ID as the first argument.")? .parse::()?; SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new()?; let window = WindowBuilder::new() .with_title("An embedded window!") .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .with_embed_parent_window(parent_window_id) .build(&event_loop) .unwrap(); event_loop.run(move |event, elwt| { match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => elwt.exit(), Event::AboutToWait => { window.request_redraw(); } Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { // Notify the windowing system that we'll be presenting to the window. window.pre_present_notify(); fill::fill_window(&window); } _ => (), } })?; Ok(()) } } #[cfg(not(x11_platform))] mod imple { pub(super) fn entry() -> Result<(), Box> { println!("This example is only supported on X11 platforms."); Ok(()) } } fn main() -> Result<(), Box> { imple::entry() } winit-0.29.15/rustfmt.toml000064400000000000000000000001131046102023000135300ustar 00000000000000force_explicit_abi=true use_field_init_shorthand=true # merge_imports=true winit-0.29.15/src/dpi.rs000064400000000000000000001010121046102023000130400ustar 00000000000000//! UI scaling is important, so read the docs for this module if you don't want to be confused. //! //! ## Why should I care about UI scaling? //! //! Modern computer screens don't have a consistent relationship between resolution and size. //! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens //! typically being less than a quarter the size of their desktop counterparts. Moreover, neither //! desktop nor mobile screens have consistent resolutions within their own size classes - common //! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K //! and beyond. //! //! Given that, it's a mistake to assume that 2D content will only be displayed on screens with //! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen and //! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up //! about a quarter of the physical space as it did on the 1080p screen. That issue is especially //! problematic with text rendering, where quarter-sized text becomes a significant legibility //! problem. //! //! Failure to account for the scale factor can create a significantly degraded user experience. //! Most notably, it can make users feel like they have bad eyesight, which will potentially cause //! them to think about growing elderly, resulting in them having an existential crisis. Once users //! enter that state, they will no longer be focused on your application. //! //! ## How should I handle it? //! //! The solution to this problem is to account for the device's *scale factor*. The scale factor is //! the factor UI elements should be scaled by to be consistent with the rest of the user's system - //! for example, a button that's usually 50 pixels across would be 100 pixels across on a device //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. //! //! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's //! usually a mistake since there's no consistent mapping between the scale factor and the screen's //! actual DPI. Unless printing to a physical medium, you should work in scaled pixels rather //! than any DPI-dependent units. //! //! ### Position and Size types //! //! Winit's [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the //! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels //! divided by the scale factor. //! All of Winit's functions return physical types, but can take either logical or physical //! coordinates as input, allowing you to use the most convenient coordinate system for your //! particular application. //! //! Winit's position and size types are generic over their exact pixel type, `P`, to allow the //! API to have integer precision where appropriate (e.g. most window manipulation functions) and //! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch //! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so //! will truncate the fractional part of the float rather than properly round to the nearest //! integer. Use the provided `cast` function or [`From`]/[`Into`] conversions, which handle the //! rounding properly. Note that precision loss will still occur when rounding from a float to an //! int, although rounding lessens the problem. //! //! ### Events //! //! Winit will dispatch a [`ScaleFactorChanged`] event whenever a window's scale factor has changed. //! This can happen if the user drags their window from a standard-resolution monitor to a high-DPI //! monitor or if the user changes their DPI settings. This allows you to rescale your application's //! UI elements and adjust how the platform changes the window's size to reflect the new scale //! factor. If a window hasn't received a [`ScaleFactorChanged`] event, its scale factor //! can be found by calling [`window.scale_factor()`]. //! //! ## How is the scale factor calculated? //! //! The scale factor is calculated differently on different platforms: //! //! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the //! display settings. While users are free to select any option they want, they're only given a //! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7. The scale factor is //! global and changing it requires logging out. See [this article][windows_1] for technical //! details. //! - **macOS:** Recent macOS versions allow the user to change the scaling factor for specific //! displays. When available, the user may pick a per-monitor scaling factor from a set of //! pre-defined settings. All "retina displays" have a scaling factor above 1.0 by default, //! but the specific value varies across devices. //! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit //! currently uses a three-pronged approach: //! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable if present. //! + If not present, use the value set in `Xft.dpi` in Xresources. //! + Otherwise, calculate the scale factor based on the millimeter monitor dimensions provided by XRandR. //! //! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the //! XRandR scaling method. Generally speaking, you should try to configure the standard system //! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. //! - **Wayland:** The scale factor is suggested by the compositor for each window individually. The //! monitor scale factor may differ from the window scale factor. //! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range //! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more //! information. //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. //! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by //! both the screen scaling and the browser zoom level and can go below `1.0`. //! //! //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [`ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged //! [`window.scale_factor()`]: crate::window::Window::scale_factor //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ //! [android_1]: https://developer.android.com/training/multiscreen/screendensities //! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; fn cast(self) -> P { P::from_f64(self.into()) } } impl Pixel for u8 { fn from_f64(f: f64) -> Self { f.round() as u8 } } impl Pixel for u16 { fn from_f64(f: f64) -> Self { f.round() as u16 } } impl Pixel for u32 { fn from_f64(f: f64) -> Self { f.round() as u32 } } impl Pixel for i8 { fn from_f64(f: f64) -> Self { f.round() as i8 } } impl Pixel for i16 { fn from_f64(f: f64) -> Self { f.round() as i16 } } impl Pixel for i32 { fn from_f64(f: f64) -> Self { f.round() as i32 } } impl Pixel for f32 { fn from_f64(f: f64) -> Self { f as f32 } } impl Pixel for f64 { fn from_f64(f: f64) -> Self { f } } /// Checks that the scale factor is a normal positive `f64`. /// /// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; /// otherwise, you risk panics. #[inline] pub fn validate_scale_factor(scale_factor: f64) -> bool { scale_factor.is_sign_positive() && scale_factor.is_normal() } /// A position represented in logical pixels. /// /// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` /// implementation is provided which does the rounding for you. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalPosition

{ pub x: P, pub y: P, } impl

LogicalPosition

{ #[inline] pub const fn new(x: P, y: P) -> Self { LogicalPosition { x, y } } } impl LogicalPosition

{ #[inline] pub fn from_physical>, X: Pixel>( physical: T, scale_factor: f64, ) -> Self { physical.into().to_logical(scale_factor) } #[inline] pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition { assert!(validate_scale_factor(scale_factor)); let x = self.x.into() * scale_factor; let y = self.y.into() * scale_factor; PhysicalPosition::new(x, y).cast() } #[inline] pub fn cast(&self) -> LogicalPosition { LogicalPosition { x: self.x.cast(), y: self.y.cast(), } } } impl From<(X, X)> for LogicalPosition

{ fn from((x, y): (X, X)) -> LogicalPosition

{ LogicalPosition::new(x.cast(), y.cast()) } } impl From> for (X, X) { fn from(p: LogicalPosition

) -> (X, X) { (p.x.cast(), p.y.cast()) } } impl From<[X; 2]> for LogicalPosition

{ fn from([x, y]: [X; 2]) -> LogicalPosition

{ LogicalPosition::new(x.cast(), y.cast()) } } impl From> for [X; 2] { fn from(p: LogicalPosition

) -> [X; 2] { [p.x.cast(), p.y.cast()] } } #[cfg(feature = "mint")] impl From> for LogicalPosition

{ fn from(p: mint::Point2

) -> Self { Self::new(p.x, p.y) } } #[cfg(feature = "mint")] impl From> for mint::Point2

{ fn from(p: LogicalPosition

) -> Self { mint::Point2 { x: p.x, y: p.y } } } /// A position represented in physical pixels. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalPosition

{ pub x: P, pub y: P, } impl

PhysicalPosition

{ #[inline] pub const fn new(x: P, y: P) -> Self { PhysicalPosition { x, y } } } impl PhysicalPosition

{ #[inline] pub fn from_logical>, X: Pixel>( logical: T, scale_factor: f64, ) -> Self { logical.into().to_physical(scale_factor) } #[inline] pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { assert!(validate_scale_factor(scale_factor)); let x = self.x.into() / scale_factor; let y = self.y.into() / scale_factor; LogicalPosition::new(x, y).cast() } #[inline] pub fn cast(&self) -> PhysicalPosition { PhysicalPosition { x: self.x.cast(), y: self.y.cast(), } } } impl From<(X, X)> for PhysicalPosition

{ fn from((x, y): (X, X)) -> PhysicalPosition

{ PhysicalPosition::new(x.cast(), y.cast()) } } impl From> for (X, X) { fn from(p: PhysicalPosition

) -> (X, X) { (p.x.cast(), p.y.cast()) } } impl From<[X; 2]> for PhysicalPosition

{ fn from([x, y]: [X; 2]) -> PhysicalPosition

{ PhysicalPosition::new(x.cast(), y.cast()) } } impl From> for [X; 2] { fn from(p: PhysicalPosition

) -> [X; 2] { [p.x.cast(), p.y.cast()] } } #[cfg(feature = "mint")] impl From> for PhysicalPosition

{ fn from(p: mint::Point2

) -> Self { Self::new(p.x, p.y) } } #[cfg(feature = "mint")] impl From> for mint::Point2

{ fn from(p: PhysicalPosition

) -> Self { mint::Point2 { x: p.x, y: p.y } } } /// A size represented in logical pixels. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalSize

{ pub width: P, pub height: P, } impl

LogicalSize

{ #[inline] pub const fn new(width: P, height: P) -> Self { LogicalSize { width, height } } } impl LogicalSize

{ #[inline] pub fn from_physical>, X: Pixel>( physical: T, scale_factor: f64, ) -> Self { physical.into().to_logical(scale_factor) } #[inline] pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { assert!(validate_scale_factor(scale_factor)); let width = self.width.into() * scale_factor; let height = self.height.into() * scale_factor; PhysicalSize::new(width, height).cast() } #[inline] pub fn cast(&self) -> LogicalSize { LogicalSize { width: self.width.cast(), height: self.height.cast(), } } } impl From<(X, X)> for LogicalSize

{ fn from((x, y): (X, X)) -> LogicalSize

{ LogicalSize::new(x.cast(), y.cast()) } } impl From> for (X, X) { fn from(s: LogicalSize

) -> (X, X) { (s.width.cast(), s.height.cast()) } } impl From<[X; 2]> for LogicalSize

{ fn from([x, y]: [X; 2]) -> LogicalSize

{ LogicalSize::new(x.cast(), y.cast()) } } impl From> for [X; 2] { fn from(s: LogicalSize

) -> [X; 2] { [s.width.cast(), s.height.cast()] } } #[cfg(feature = "mint")] impl From> for LogicalSize

{ fn from(v: mint::Vector2

) -> Self { Self::new(v.x, v.y) } } #[cfg(feature = "mint")] impl From> for mint::Vector2

{ fn from(s: LogicalSize

) -> Self { mint::Vector2 { x: s.width, y: s.height, } } } /// A size represented in physical pixels. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalSize

{ pub width: P, pub height: P, } impl

PhysicalSize

{ #[inline] pub const fn new(width: P, height: P) -> Self { PhysicalSize { width, height } } } impl PhysicalSize

{ #[inline] pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { logical.into().to_physical(scale_factor) } #[inline] pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { assert!(validate_scale_factor(scale_factor)); let width = self.width.into() / scale_factor; let height = self.height.into() / scale_factor; LogicalSize::new(width, height).cast() } #[inline] pub fn cast(&self) -> PhysicalSize { PhysicalSize { width: self.width.cast(), height: self.height.cast(), } } } impl From<(X, X)> for PhysicalSize

{ fn from((x, y): (X, X)) -> PhysicalSize

{ PhysicalSize::new(x.cast(), y.cast()) } } impl From> for (X, X) { fn from(s: PhysicalSize

) -> (X, X) { (s.width.cast(), s.height.cast()) } } impl From<[X; 2]> for PhysicalSize

{ fn from([x, y]: [X; 2]) -> PhysicalSize

{ PhysicalSize::new(x.cast(), y.cast()) } } impl From> for [X; 2] { fn from(s: PhysicalSize

) -> [X; 2] { [s.width.cast(), s.height.cast()] } } #[cfg(feature = "mint")] impl From> for PhysicalSize

{ fn from(v: mint::Vector2

) -> Self { Self::new(v.x, v.y) } } #[cfg(feature = "mint")] impl From> for mint::Vector2

{ fn from(s: PhysicalSize

) -> Self { mint::Vector2 { x: s.width, y: s.height, } } } /// A size that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Size { Physical(PhysicalSize), Logical(LogicalSize), } impl Size { pub fn new>(size: S) -> Size { size.into() } pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ match *self { Size::Physical(size) => size.to_logical(scale_factor), Size::Logical(size) => size.cast(), } } pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ match *self { Size::Physical(size) => size.cast(), Size::Logical(size) => size.to_physical(scale_factor), } } pub fn clamp>(input: S, min: S, max: S, scale_factor: f64) -> Size { let (input, min, max) = ( input.into().to_physical::(scale_factor), min.into().to_physical::(scale_factor), max.into().to_physical::(scale_factor), ); let width = input.width.clamp(min.width, max.width); let height = input.height.clamp(min.height, max.height); PhysicalSize::new(width, height).into() } } impl From> for Size { #[inline] fn from(size: PhysicalSize

) -> Size { Size::Physical(size.cast()) } } impl From> for Size { #[inline] fn from(size: LogicalSize

) -> Size { Size::Logical(size.cast()) } } /// A position that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { Physical(PhysicalPosition), Logical(LogicalPosition), } impl Position { pub fn new>(position: S) -> Position { position.into() } pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition

{ match *self { Position::Physical(position) => position.to_logical(scale_factor), Position::Logical(position) => position.cast(), } } pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition

{ match *self { Position::Physical(position) => position.cast(), Position::Logical(position) => position.to_physical(scale_factor), } } } impl From> for Position { #[inline] fn from(position: PhysicalPosition

) -> Position { Position::Physical(position.cast()) } } impl From> for Position { #[inline] fn from(position: LogicalPosition

) -> Position { Position::Logical(position.cast()) } } #[cfg(test)] mod tests { use crate::dpi; use std::collections::HashSet; macro_rules! test_pixel_int_impl { ($($name:ident => $ty:ty),*) => {$( #[test] fn $name() { use dpi::Pixel; assert_eq!( <$ty as Pixel>::from_f64(37.0), 37, ); assert_eq!( <$ty as Pixel>::from_f64(37.4), 37, ); assert_eq!( <$ty as Pixel>::from_f64(37.5), 38, ); assert_eq!( <$ty as Pixel>::from_f64(37.9), 38, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); assert_eq!( <$ty as Pixel>::cast::(37), 37, ); } )*}; } test_pixel_int_impl! { test_pixel_int_u8 => u8, test_pixel_int_u16 => u16, test_pixel_int_u32 => u32, test_pixel_int_i8 => i8, test_pixel_int_i16 => i16 } macro_rules! assert_approx_eq { ($a:expr, $b:expr $(,)?) => { assert!( ($a - $b).abs() < 0.001, "{} is not approximately equal to {}", $a, $b ); }; } macro_rules! test_pixel_float_impl { ($($name:ident => $ty:ty),*) => {$( #[test] fn $name() { use dpi::Pixel; assert_approx_eq!( <$ty as Pixel>::from_f64(37.0), 37.0, ); assert_approx_eq!( <$ty as Pixel>::from_f64(37.4), 37.4, ); assert_approx_eq!( <$ty as Pixel>::from_f64(37.5), 37.5, ); assert_approx_eq!( <$ty as Pixel>::from_f64(37.9), 37.9, ); assert_eq!( <$ty as Pixel>::cast::(37.0), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.4), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.5), 38, ); assert_eq!( <$ty as Pixel>::cast::(37.0), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.4), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.5), 38, ); assert_eq!( <$ty as Pixel>::cast::(37.0), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.4), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.5), 38, ); assert_eq!( <$ty as Pixel>::cast::(37.0), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.4), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.5), 38, ); assert_eq!( <$ty as Pixel>::cast::(37.0), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.4), 37, ); assert_eq!( <$ty as Pixel>::cast::(37.5), 38, ); } )*}; } test_pixel_float_impl! { test_pixel_float_f32 => f32, test_pixel_float_f64 => f64 } #[test] fn test_validate_scale_factor() { assert!(dpi::validate_scale_factor(1.0)); assert!(dpi::validate_scale_factor(2.0)); assert!(dpi::validate_scale_factor(3.0)); assert!(dpi::validate_scale_factor(1.5)); assert!(dpi::validate_scale_factor(0.5)); assert!(!dpi::validate_scale_factor(0.0)); assert!(!dpi::validate_scale_factor(-1.0)); assert!(!dpi::validate_scale_factor(f64::INFINITY)); assert!(!dpi::validate_scale_factor(f64::NAN)); assert!(!dpi::validate_scale_factor(f64::NEG_INFINITY)); } #[test] fn test_logical_position() { let log_pos = dpi::LogicalPosition::new(1.0, 2.0); assert_eq!( log_pos.to_physical::(1.0), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( log_pos.to_physical::(2.0), dpi::PhysicalPosition::new(2, 4) ); assert_eq!(log_pos.cast::(), dpi::LogicalPosition::new(1, 2)); assert_eq!( log_pos, dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(1.0, 2.0), 1.0) ); assert_eq!( log_pos, dpi::LogicalPosition::from_physical(dpi::PhysicalPosition::new(2.0, 4.0), 2.0) ); assert_eq!( dpi::LogicalPosition::from((2.0, 2.0)), dpi::LogicalPosition::new(2.0, 2.0) ); assert_eq!( dpi::LogicalPosition::from([2.0, 3.0]), dpi::LogicalPosition::new(2.0, 3.0) ); let x: (f64, f64) = log_pos.into(); assert_eq!(x, (1.0, 2.0)); let x: [f64; 2] = log_pos.into(); assert_eq!(x, [1.0, 2.0]); } #[test] fn test_physical_position() { assert_eq!( dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(1.0, 2.0), 1.0), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( dpi::PhysicalPosition::from_logical(dpi::LogicalPosition::new(2.0, 4.0), 0.5), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( dpi::PhysicalPosition::from((2.0, 2.0)), dpi::PhysicalPosition::new(2.0, 2.0) ); assert_eq!( dpi::PhysicalPosition::from([2.0, 3.0]), dpi::PhysicalPosition::new(2.0, 3.0) ); let x: (f64, f64) = dpi::PhysicalPosition::new(1, 2).into(); assert_eq!(x, (1.0, 2.0)); let x: [f64; 2] = dpi::PhysicalPosition::new(1, 2).into(); assert_eq!(x, [1.0, 2.0]); } #[test] fn test_logical_size() { let log_size = dpi::LogicalSize::new(1.0, 2.0); assert_eq!( log_size.to_physical::(1.0), dpi::PhysicalSize::new(1, 2) ); assert_eq!( log_size.to_physical::(2.0), dpi::PhysicalSize::new(2, 4) ); assert_eq!(log_size.cast::(), dpi::LogicalSize::new(1, 2)); assert_eq!( log_size, dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(1.0, 2.0), 1.0) ); assert_eq!( log_size, dpi::LogicalSize::from_physical(dpi::PhysicalSize::new(2.0, 4.0), 2.0) ); assert_eq!( dpi::LogicalSize::from((2.0, 2.0)), dpi::LogicalSize::new(2.0, 2.0) ); assert_eq!( dpi::LogicalSize::from([2.0, 3.0]), dpi::LogicalSize::new(2.0, 3.0) ); let x: (f64, f64) = log_size.into(); assert_eq!(x, (1.0, 2.0)); let x: [f64; 2] = log_size.into(); assert_eq!(x, [1.0, 2.0]); } #[test] fn test_physical_size() { assert_eq!( dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(1.0, 2.0), 1.0), dpi::PhysicalSize::new(1, 2) ); assert_eq!( dpi::PhysicalSize::from_logical(dpi::LogicalSize::new(2.0, 4.0), 0.5), dpi::PhysicalSize::new(1, 2) ); assert_eq!( dpi::PhysicalSize::from((2.0, 2.0)), dpi::PhysicalSize::new(2.0, 2.0) ); assert_eq!( dpi::PhysicalSize::from([2.0, 3.0]), dpi::PhysicalSize::new(2.0, 3.0) ); let x: (f64, f64) = dpi::PhysicalSize::new(1, 2).into(); assert_eq!(x, (1.0, 2.0)); let x: [f64; 2] = dpi::PhysicalSize::new(1, 2).into(); assert_eq!(x, [1.0, 2.0]); } #[test] fn test_size() { assert_eq!( dpi::Size::new(dpi::PhysicalSize::new(1, 2)), dpi::Size::Physical(dpi::PhysicalSize::new(1, 2)) ); assert_eq!( dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)), dpi::Size::Logical(dpi::LogicalSize::new(1.0, 2.0)) ); assert_eq!( dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::(1.0), dpi::LogicalSize::new(1.0, 2.0) ); assert_eq!( dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_logical::(2.0), dpi::LogicalSize::new(0.5, 1.0) ); assert_eq!( dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_logical::(1.0), dpi::LogicalSize::new(1.0, 2.0) ); assert_eq!( dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::(1.0), dpi::PhysicalSize::new(1, 2) ); assert_eq!( dpi::Size::new(dpi::PhysicalSize::new(1, 2)).to_physical::(2.0), dpi::PhysicalSize::new(1, 2) ); assert_eq!( dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::(1.0), dpi::PhysicalSize::new(1, 2) ); assert_eq!( dpi::Size::new(dpi::LogicalSize::new(1.0, 2.0)).to_physical::(2.0), dpi::PhysicalSize::new(2, 4) ); let small = dpi::Size::Physical((1, 2).into()); let medium = dpi::Size::Logical((3, 4).into()); let medium_physical = dpi::Size::new(medium.to_physical::(1.0)); let large = dpi::Size::Physical((5, 6).into()); assert_eq!(dpi::Size::clamp(medium, small, large, 1.0), medium_physical); assert_eq!(dpi::Size::clamp(small, medium, large, 1.0), medium_physical); assert_eq!(dpi::Size::clamp(large, small, medium, 1.0), medium_physical); } #[test] fn test_position() { assert_eq!( dpi::Position::new(dpi::PhysicalPosition::new(1, 2)), dpi::Position::Physical(dpi::PhysicalPosition::new(1, 2)) ); assert_eq!( dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)), dpi::Position::Logical(dpi::LogicalPosition::new(1.0, 2.0)) ); assert_eq!( dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::(1.0), dpi::LogicalPosition::new(1.0, 2.0) ); assert_eq!( dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_logical::(2.0), dpi::LogicalPosition::new(0.5, 1.0) ); assert_eq!( dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_logical::(1.0), dpi::LogicalPosition::new(1.0, 2.0) ); assert_eq!( dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::(1.0), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( dpi::Position::new(dpi::PhysicalPosition::new(1, 2)).to_physical::(2.0), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::(1.0), dpi::PhysicalPosition::new(1, 2) ); assert_eq!( dpi::Position::new(dpi::LogicalPosition::new(1.0, 2.0)).to_physical::(2.0), dpi::PhysicalPosition::new(2, 4) ); } // Eat coverage for the Debug impls et al #[test] fn ensure_attrs_do_not_panic() { let _ = format!("{:?}", dpi::LogicalPosition::::default().clone()); HashSet::new().insert(dpi::LogicalPosition::::default()); let _ = format!("{:?}", dpi::PhysicalPosition::::default().clone()); HashSet::new().insert(dpi::PhysicalPosition::::default()); let _ = format!("{:?}", dpi::LogicalSize::::default().clone()); HashSet::new().insert(dpi::LogicalSize::::default()); let _ = format!("{:?}", dpi::PhysicalSize::::default().clone()); HashSet::new().insert(dpi::PhysicalSize::::default()); let _ = format!("{:?}", dpi::Size::Physical((1, 2).into()).clone()); let _ = format!("{:?}", dpi::Position::Physical((1, 2).into()).clone()); } } winit-0.29.15/src/error.rs000064400000000000000000000075401046102023000134300ustar 00000000000000use std::{error, fmt}; use crate::platform_impl; // TODO: Rename /// An error that may be generated when requesting Winit state #[derive(Debug)] pub enum ExternalError { /// The operation is not supported by the backend. NotSupported(NotSupportedError), /// The operation was ignored. Ignored, /// 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, } /// A general error that may occur while running the Winit event loop #[derive(Debug)] pub enum EventLoopError { /// The operation is not supported by the backend. NotSupported(NotSupportedError), /// The OS cannot perform the operation. Os(OsError), /// The event loop can't be re-run while it's already running AlreadyRunning, /// The event loop can't be re-created. RecreationAttempt, /// Application has exit with an error status. ExitFailure(i32), } impl From for EventLoopError { fn from(value: OsError) -> Self { Self::Os(value) } } 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::Ignored => write!(f, "Operation was ignored"), 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 Winit") } } impl fmt::Display for EventLoopError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { EventLoopError::AlreadyRunning => write!(f, "EventLoop is already running"), EventLoopError::RecreationAttempt => write!(f, "EventLoop can't be recreated"), EventLoopError::NotSupported(e) => e.fmt(f), EventLoopError::Os(e) => e.fmt(f), EventLoopError::ExitFailure(status) => write!(f, "Exit Failure: {status}"), } } } impl error::Error for OsError {} impl error::Error for ExternalError {} impl error::Error for NotSupportedError {} impl error::Error for EventLoopError {} #[cfg(test)] mod tests { #![allow(clippy::redundant_clone)] use super::*; // Eat attributes for testing #[test] fn ensure_fmt_does_not_panic() { let _ = format!( "{:?}, {}", NotSupportedError::new(), NotSupportedError::new().clone() ); let _ = format!( "{:?}, {}", ExternalError::NotSupported(NotSupportedError::new()), ExternalError::NotSupported(NotSupportedError::new()) ); } } winit-0.29.15/src/event.rs000064400000000000000000001505721046102023000134240ustar 00000000000000//! The [`Event`] enum and assorted supporting types. //! //! These are sent to the closure given to [`EventLoop::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(...)`] like this: //! //! ```rust,ignore //! let mut start_cause = StartCause::Init; //! //! while !elwt.exiting() { //! event_handler(NewEvents(start_cause), elwt); //! //! for e in (window events, user events, device events) { //! event_handler(e, elwt); //! } //! //! for w in (redraw windows) { //! event_handler(RedrawRequested(w), elwt); //! } //! //! event_handler(AboutToWait, elwt); //! start_cause = wait_if_necessary(); //! } //! //! event_handler(LoopExiting, elwt); //! ``` //! //! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully //! describes what happens in what order. //! //! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil use std::path::PathBuf; use std::sync::{Mutex, Weak}; #[cfg(not(wasm_platform))] use std::time::Instant; use smol_str::SmolStr; #[cfg(wasm_platform)] use web_time::Instant; use crate::error::ExternalError; #[cfg(doc)] use crate::window::Window; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event_loop::AsyncRequestSerial, keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}, platform_impl, window::{ActivationToken, Theme, WindowId}, }; /// Describes a generic event. /// /// See the module-level docs for more information on the event loop manages each event. #[derive(Debug, Clone, PartialEq)] pub enum Event { /// 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`] 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 winit window. WindowEvent { window_id: WindowId, event: WindowEvent, }, /// Emitted when the OS sends an event to a device. 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. /// /// # Portability /// /// Not all platforms support the notion of suspending applications, and there may be no /// technical way to guarantee being able to emit a `Suspended` event if the OS has /// no formal application lifecycle (currently only Android, iOS, and Web do). For this reason, /// Winit does not currently try to emit pseudo `Suspended` events before the application /// quits on platforms without an application lifecycle. /// /// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally /// driven by multiple platform-specific events, and that there may be subtle differences across /// platforms with how these internal events are delivered, it's recommended that applications /// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events. /// /// Also see [`Resumed`] notes. /// /// ## Android /// /// On Android, the `Suspended` event is only sent when the application's associated /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] /// lifecycle event but there may technically be a discrepancy. /// /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() /// /// Applications that need to run on Android should assume their [`SurfaceView`] has been /// destroyed, which indirectly invalidates any existing render surfaces that may have been /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). /// /// After being `Suspended` on Android applications must drop all render surfaces before /// the event callback completes, which may be re-created when the application is next [`Resumed`]. /// /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// /// ## iOS /// /// On iOS, the `Suspended` event is currently emitted in response to an /// [`applicationWillResignActive`] callback which means that the application is /// about to transition from the active to inactive state (according to the /// [iOS application lifecycle]). /// /// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ## Web /// /// On Web, the `Suspended` event is emitted in response to a [`pagehide`] event /// with the property [`persisted`] being true, which means that the page is being /// put in the [`bfcache`] (back/forward cache) - an in-memory cache that stores a /// complete snapshot of a page (including the JavaScript heap) as the user is /// navigating away. /// /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted /// [`bfcache`]: https://web.dev/bfcache/ /// /// [`Resumed`]: Self::Resumed Suspended, /// Emitted when the application has been resumed. /// /// For consistency, all platforms emit a `Resumed` event even if they don't themselves have a /// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle /// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init] /// event. /// /// # Portability /// /// It's recommended that applications should only initialize their graphics context and create /// a window after they have received their first `Resumed` event. Some systems /// (specifically Android) won't allow applications to create a render surface until they are /// resumed. /// /// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally /// driven by multiple platform-specific events, and that there may be subtle differences across /// platforms with how these internal events are delivered, it's recommended that applications /// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events. /// /// Also see [`Suspended`] notes. /// /// ## Android /// /// On Android, the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is /// expected to closely correlate with the [`onResume`] lifecycle event but there may technically /// be a discrepancy. /// /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() /// /// Applications that need to run on Android must wait until they have been `Resumed` /// before they will be able to create a render surface (such as an `EGLSurface`, /// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a /// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`], then their /// render surfaces are invalid and should be dropped. /// /// Also see [`Suspended`] notes. /// /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// /// ## iOS /// /// On iOS, the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`] /// callback which means the application is "active" (according to the /// [iOS application lifecycle]). /// /// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ## Web /// /// On Web, the `Resumed` event is emitted in response to a [`pageshow`] event /// with the property [`persisted`] being true, which means that the page is being /// restored from the [`bfcache`] (back/forward cache) - an in-memory cache that /// stores a complete snapshot of a page (including the JavaScript heap) as the /// user is navigating away. /// /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event /// [`persisted`]: https://developer.mozilla.org/en-US/docs/Web/API/PageTransitionEvent/persisted /// [`bfcache`]: https://web.dev/bfcache/ /// /// [`Suspended`]: Self::Suspended Resumed, /// Emitted when the event loop is about to block and wait for new events. /// /// Most applications shouldn't need to hook into this event since there is no real relationship /// between how often the event loop needs to wake up and the dispatching of any specific events. /// /// High frequency event sources, such as input devices could potentially lead to lots of wake /// ups and also lots of corresponding `AboutToWait` events. /// /// This is not an ideal event to drive application rendering from and instead applications /// should render in response to [`WindowEvent::RedrawRequested`] events. AboutToWait, /// 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 a "do on quit" event. LoopExiting, /// Emitted when the application has received a memory warning. /// /// ## Platform-specific /// /// ### Android /// /// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The application /// must [release memory] or risk being killed. /// /// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory() /// [release memory]: https://developer.android.com/topic/performance/memory#release /// /// ### iOS /// /// On iOS, the `MemoryWarning` event is emitted in response to an [`applicationDidReceiveMemoryWarning`] /// callback. The application must free as much memory as possible or risk being terminated, see /// [how to respond to memory warnings]. /// /// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni /// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings /// /// ### Others /// /// - **macOS / Wayland / Windows / Orbital:** Unsupported. MemoryWarning, } impl Event { #[allow(clippy::result_large_err)] pub fn map_nonuser_event(self) -> Result, Event> { 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)), AboutToWait => Ok(AboutToWait), LoopExiting => Ok(LoopExiting), Suspended => Ok(Suspended), Resumed => Ok(Resumed), MemoryWarning => Ok(MemoryWarning), } } } /// Describes the reason the event loop is resuming. #[derive(Debug, Clone, Copy, PartialEq, Eq)] 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. /// /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil 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. 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`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll Poll, /// Sent once, immediately after `run` is called. Indicates that the loop was just initialized. Init, } /// Describes an event from a [`Window`]. #[derive(Debug, Clone, PartialEq)] pub enum WindowEvent { /// The activation token was delivered back and now could be used. /// #[cfg_attr( not(any(x11_platform, wayland_platfrom)), allow(rustdoc::broken_intra_doc_links) )] /// Delivered in response to [`request_activation_token`]. /// /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken, }, /// 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 /// /// - **iOS / Android / Web / Wayland:** Unsupported. Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, /// The window has been destroyed. 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 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`. KeyboardInput { device_id: DeviceId, event: KeyEvent, /// If `true`, the event was generated synthetically by winit /// 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 X11 and Windows*** /// /// Otherwise, this value is always `false`. is_synthetic: bool, }, /// The keyboard modifiers have changed. ModifiersChanged(Modifiers), /// An event from an input method. /// /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`]. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Unsupported. Ime(Ime), /// The cursor has moved on the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform 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, }, /// The cursor has entered the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorEntered { device_id: DeviceId }, /// The cursor has left the window. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform CursorLeft { device_id: DeviceId }, /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, }, /// An mouse button press has been received. MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, }, /// Touchpad magnification event with two-finger pinch gesture. /// /// Positive delta values indicate magnification (zooming in) and /// negative delta values indicate shrinking (zooming out). /// /// ## Platform-specific /// /// - Only available on **macOS**. TouchpadMagnify { device_id: DeviceId, delta: f64, phase: TouchPhase, }, /// Smart magnification event. /// /// On a Mac, smart magnification is triggered by a double tap with two fingers /// on the trackpad and is commonly used to zoom on a certain object /// (e.g. a paragraph of a PDF) or (sort of like a toggle) to reset any zoom. /// The gesture is also supported in Safari, Pages, etc. /// /// The event is general enough that its generating gesture is allowed to vary /// across platforms. It could also be generated by another device. /// /// Unfortunatly, neither [Windows](https://support.microsoft.com/en-us/windows/touch-gestures-for-windows-a9d28305-4818-a5df-4e2b-e5590f850741) /// nor [Wayland](https://wayland.freedesktop.org/libinput/doc/latest/gestures.html) /// support this gesture or any other gesture with the same effect. /// /// ## Platform-specific /// /// - Only available on **macOS 10.8** and later. SmartMagnify { device_id: DeviceId }, /// Touchpad rotation event with two-finger rotation gesture. /// /// Positive delta values indicate rotation counterclockwise and /// negative delta values indicate rotation clockwise. /// /// ## Platform-specific /// /// - Only available on **macOS**. TouchpadRotate { device_id: DeviceId, delta: f32, phase: TouchPhase, }, /// 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 /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **macOS:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform 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, /// Handle to update inner size during scale changes. /// /// See [`InnerSizeWriter`] docs for more details. inner_size_writer: InnerSizeWriter, }, /// 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 /// /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported. ThemeChanged(Theme), /// The window has been occluded (completely hidden from view). /// /// This is different to window visibility as it depends on whether the window is closed, /// minimised, set invisible, or fully occluded by another window. /// /// ## Platform-specific /// /// ### iOS /// /// On iOS, the `Occluded(false)` event is emitted in response to an [`applicationWillEnterForeground`] /// callback which means the application should start preparing its data. The `Occluded(true)` event is /// emitted in response to an [`applicationDidEnterBackground`] callback which means the application /// should free resources (according to the [iOS application lifecycle]). /// /// [`applicationWillEnterForeground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623076-applicationwillenterforeground /// [`applicationDidEnterBackground`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622997-applicationdidenterbackground /// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle /// /// ### Others /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **Android / Wayland / Windows / Orbital:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform Occluded(bool), /// Emitted 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`]. /// /// Winit will aggregate duplicate redraw requests into a single event, to /// help avoid duplicating rendering work. RedrawRequested, } /// 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 { /// Returns a dummy id, useful for unit testing. /// /// # Safety /// /// 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 winit function will result in undefined behavior.** pub const unsafe fn dummy() -> Self { #[allow(unused_unsafe)] DeviceId(unsafe { 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. #[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`]. MouseMotion { /// (x, y) change in position in unspecified units. /// /// Different devices may use different units. delta: (f64, f64), }, /// Physical scroll event MouseWheel { delta: MouseScrollDelta, }, /// Motion on some analog axis. This event will be reported for all arbitrary input devices /// that winit supports on this platform, including mouse devices. If the device is a mouse /// device then this will be reported alongside the MouseMotion event. Motion { axis: AxisId, value: f64, }, Button { button: ButtonId, state: ElementState, }, Key(RawKeyEvent), } /// 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, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct RawKeyEvent { pub physical_key: keyboard::PhysicalKey, 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.) /// /// ## Caveats /// /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that /// implements DVORAK in hardware (or firmware) /// - Your application will likely have to handle keyboards which are missing keys that your /// own keyboard has. /// - Certain `KeyCode`s will move between a couple of different positions depending on what /// layout the keyboard was manufactured to support. /// /// **Because of these caveats, it is important that you provide users with a way to configure /// most (if not all) keybinds in your application.** /// /// ## `Fn` and `FnLock` /// /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If /// you somehow see this in the wild, we'd like to know :) pub physical_key: keyboard::PhysicalKey, // Allowing `broken_intra_doc_links` for `logical_key`, because // `key_without_modifiers` is not available on all platforms #[cfg_attr( not(any(windows_platform, macos_platform, x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links) )] /// 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`]. /// /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard /// shortcuts, **it is important that you provide users with a way to configure your /// application's shortcuts so you don't render your application unusable for users with an /// incompatible keyboard layout.** /// /// ## Platform-specific /// - **Web:** Dead keys might be reported as the real key instead /// of `Dead` depending on the browser/OS. /// /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers pub logical_key: keyboard::Key, /// 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::Named(NamedKey::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, /// Contains the location of this key on the keyboard. /// /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift" key /// appears on the left side of the QWERTY keyboard as well as the right side. However, both keys /// have the same symbolic value. Another example of this phenomenon is the "1" key, which appears /// both above the "Q" key and as the "Keypad 1" key. /// /// This field allows the user to differentiate between keys like this that have the same symbolic /// value but different locations on the keyboard. /// /// See the [`KeyLocation`] type for more details. /// /// [`KeyLocation`]: crate::keyboard::KeyLocation pub location: keyboard::KeyLocation, /// Whether the key is being pressed or released. /// /// See the [`ElementState`] type for more details. pub state: ElementState, /// Whether or not this key is a key repeat event. /// /// On some systems, holding down a key for some period of time causes that key to be repeated /// as though it were being pressed and released repeatedly. This field is `true` if and only if /// this event is the result of one of those repeats. pub repeat: bool, /// Platform-specific key event information. /// /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with all /// modifiers applied. /// /// On Android, iOS, Redox and Web, this type is a no-op. pub(crate) platform_specific: platform_impl::KeyEventExtra, } /// Describes keyboard modifiers event. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Modifiers { pub(crate) state: ModifiersState, // NOTE: Currently pressed modifiers keys. // // The field providing a metadata, it shouldn't be used as a source of truth. pub(crate) pressed_mods: ModifiersKeys, } impl Modifiers { /// The state of the modifiers. pub fn state(&self) -> ModifiersState { self.state } /// The state of the left shift key. pub fn lshift_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LSHIFT) } /// The state of the right shift key. pub fn rshift_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RSHIFT) } /// The state of the left alt key. pub fn lalt_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LALT) } /// The state of the right alt key. pub fn ralt_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RALT) } /// The state of the left control key. pub fn lcontrol_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LCONTROL) } /// The state of the right control key. pub fn rcontrol_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RCONTROL) } /// The state of the left super key. pub fn lsuper_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LSUPER) } /// The state of the right super key. pub fn rsuper_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::RSUPER) } fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState { if self.pressed_mods.contains(modifier) { ModifiersKeyState::Pressed } else { ModifiersKeyState::Unknown } } } impl From for Modifiers { fn from(value: ModifiersState) -> Self { Self { state: value, pressed_mods: Default::default(), } } } /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". /// /// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::KeyboardInput`]. /// However, one couldn't possibly have a key for every single unicode character that the user might want to type /// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead. /// /// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then /// the character you want to apply the accent to. In this case, some platforms will generate the following event sequence: /// ```ignore /// // Press "`" key /// Ime::Preedit("`", Some((0, 0))) /// // Press "E" key /// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. /// Ime::Commit("é") /// ``` /// /// Additionally, certain input devices are configured to display a candidate box that allow the user to select the /// desired character interactively. (To properly position this box, you must use [`Window::set_ime_cursor_area`].) /// /// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the following event /// sequence could be obtained: /// ```ignore /// // Press "A" key /// Ime::Preedit("a", Some((1, 1))) /// // Press "B" key /// Ime::Preedit("a b", Some((3, 3))) /// // Press left arrow key /// Ime::Preedit("a b", Some((1, 1))) /// // Press space key /// Ime::Preedit("啊b", Some((3, 3))) /// // Press space key /// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. /// Ime::Commit("啊ä¸") /// ``` #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Ime { /// Notifies when the IME was enabled. /// /// After getting this event you could receive [`Preedit`](Self::Preedit) and /// [`Commit`](Self::Commit) events. You should also start performing IME related requests /// like [`Window::set_ime_cursor_area`]. Enabled, /// Notifies when a new composing text should be set at the cursor position. /// /// The value represents a pair of the preedit string and the cursor begin position and end /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string /// this indicates that preedit was cleared. /// /// The cursor position is byte-wise indexed. Preedit(String, Option<(usize, usize)>), /// Notifies when text should be inserted into the editor widget. /// /// Right before this event winit will send empty [`Self::Preedit`] event. Commit(String), /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or /// [`Commit`](Self::Commit) events until the next [`Enabled`](Self::Enabled) event. You should /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear pending /// preedit text. Disabled, } /// Describes touch-screen input state. #[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 [`TouchPhase::Started`] event with an unique /// identifier for the finger is generated. When the finger is lifted, an [`TouchPhase::Ended`] /// event is generated with the same finger id. /// /// After a `Started` 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 `Ended` event. The user /// should assume that a new `Started` event received with the same id has nothing /// to do with the old finger and is a new finger. /// /// A [`TouchPhase::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. /// /// ## Platform-specific /// /// - **Web:** Doesn't take into account CSS [`border`], [`padding`], or [`transform`]. /// - **macOS:** Unsupported. /// /// [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border /// [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[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+, **Windows** 8+, **Web**, and **Android**. /// - **Android**: This will never be [None]. If the device doesn't support pressure /// sensitivity, force will either be 0.0 or 1.0. Also see the /// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE). pub force: Option, /// Unique identifier of a finger. pub id: u64, } /// Describes the force of a touch event #[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. 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. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ElementState { Pressed, Released, } impl ElementState { /// True if `self == Pressed`. pub fn is_pressed(self) -> bool { self == ElementState::Pressed } } /// Describes a button of a mouse controller. /// /// ## Platform-specific /// /// **macOS:** `Back` and `Forward` might not work with all hardware. /// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them. #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MouseButton { Left, Right, Middle, Back, Forward, Other(u16), } /// Describes a difference in the mouse scroll wheel state. #[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 that the content that is being scrolled should move /// right and down (revealing more content left and up). 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. /// /// Positive values indicate that the content being scrolled should /// move right/down. /// /// For a 'natural scrolling' touch pad (that acts like a touch screen) /// this means moving your fingers right and down should give positive values, /// and move the content right and down (to reveal more things left and up). PixelDelta(PhysicalPosition), } /// Handle to synchroniously change the size of the window from the /// [`WindowEvent`]. #[derive(Debug, Clone)] pub struct InnerSizeWriter { pub(crate) new_inner_size: Weak>>, } impl InnerSizeWriter { #[cfg(not(orbital_platform))] pub(crate) fn new(new_inner_size: Weak>>) -> Self { Self { new_inner_size } } /// Try to request inner size which will be set synchroniously on the window. pub fn request_inner_size( &mut self, new_inner_size: PhysicalSize, ) -> Result<(), ExternalError> { if let Some(inner) = self.new_inner_size.upgrade() { *inner.lock().unwrap() = new_inner_size; Ok(()) } else { Err(ExternalError::Ignored) } } } impl PartialEq for InnerSizeWriter { fn eq(&self, other: &Self) -> bool { self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr() } } #[cfg(test)] mod tests { use crate::event; use std::collections::{BTreeSet, HashSet}; macro_rules! foreach_event { ($closure:expr) => {{ #[allow(unused_mut)] let mut x = $closure; let did = unsafe { event::DeviceId::dummy() }; #[allow(deprecated)] { use crate::event::{Event::*, Ime::Enabled, WindowEvent::*}; use crate::window::WindowId; // Mainline events. let wid = unsafe { WindowId::dummy() }; x(UserEvent(())); x(NewEvents(event::StartCause::Init)); x(AboutToWait); x(LoopExiting); x(Suspended); x(Resumed); // Window events. let with_window_event = |wev| { x(WindowEvent { window_id: wid, event: wev, }) }; with_window_event(CloseRequested); with_window_event(Destroyed); with_window_event(Focused(true)); with_window_event(Moved((0, 0).into())); with_window_event(Resized((0, 0).into())); with_window_event(DroppedFile("x.txt".into())); with_window_event(HoveredFile("x.txt".into())); with_window_event(HoveredFileCancelled); with_window_event(Ime(Enabled)); with_window_event(CursorMoved { device_id: did, position: (0, 0).into(), }); with_window_event(ModifiersChanged(event::Modifiers::default())); with_window_event(CursorEntered { device_id: did }); with_window_event(CursorLeft { device_id: did }); with_window_event(MouseWheel { device_id: did, delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), phase: event::TouchPhase::Started, }); with_window_event(MouseInput { device_id: did, state: event::ElementState::Pressed, button: event::MouseButton::Other(0), }); with_window_event(TouchpadMagnify { device_id: did, delta: 0.0, phase: event::TouchPhase::Started, }); with_window_event(SmartMagnify { device_id: did }); with_window_event(TouchpadRotate { device_id: did, delta: 0.0, phase: event::TouchPhase::Started, }); with_window_event(TouchpadPressure { device_id: did, pressure: 0.0, stage: 0, }); with_window_event(AxisMotion { device_id: did, axis: 0, value: 0.0, }); with_window_event(Touch(event::Touch { device_id: did, phase: event::TouchPhase::Started, location: (0.0, 0.0).into(), id: 0, force: Some(event::Force::Normalized(0.0)), })); with_window_event(ThemeChanged(crate::window::Theme::Light)); with_window_event(Occluded(true)); } #[allow(deprecated)] { use event::DeviceEvent::*; let with_device_event = |dev_ev| { x(event::Event::DeviceEvent { device_id: did, event: dev_ev, }) }; with_device_event(Added); with_device_event(Removed); with_device_event(MouseMotion { delta: (0.0, 0.0).into(), }); with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0), }); with_device_event(Motion { axis: 0, value: 0.0, }); with_device_event(Button { button: 0, state: event::ElementState::Pressed, }); } }}; } #[allow(clippy::redundant_clone)] #[test] fn test_event_clone() { foreach_event!(|event: event::Event<()>| { let event2 = event.clone(); assert_eq!(event, event2); }) } #[test] fn test_map_nonuser_event() { foreach_event!(|event: event::Event<()>| { let is_user = matches!(event, event::Event::UserEvent(())); let event2 = event.map_nonuser_event::<()>(); if is_user { assert_eq!(event2, Err(event::Event::UserEvent(()))); } else { assert!(event2.is_ok()); } }) } #[test] fn test_force_normalize() { let force = event::Force::Normalized(0.0); assert_eq!(force.normalized(), 0.0); let force2 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: None, }; assert_eq!(force2.normalized(), 2.0); let force3 = event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: Some(std::f64::consts::PI / 2.0), }; assert_eq!(force3.normalized(), 2.0); } #[allow(clippy::clone_on_copy)] #[test] fn ensure_attrs_do_not_panic() { foreach_event!(|event: event::Event<()>| { let _ = format!("{:?}", event); }); let _ = event::StartCause::Init.clone(); let did = unsafe { crate::event::DeviceId::dummy() }.clone(); HashSet::new().insert(did); let mut set = [did, did, did]; set.sort_unstable(); let mut set2 = BTreeSet::new(); set2.insert(did); set2.insert(did); HashSet::new().insert(event::TouchPhase::Started.clone()); HashSet::new().insert(event::MouseButton::Left.clone()); HashSet::new().insert(event::Ime::Enabled); let _ = event::Touch { device_id: did, phase: event::TouchPhase::Started, location: (0.0, 0.0).into(), id: 0, force: Some(event::Force::Normalized(0.0)), } .clone(); let _ = event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None, } .clone(); } } winit-0.29.15/src/event_loop.rs000064400000000000000000000420351046102023000144470ustar 00000000000000//! The [`EventLoop`] struct and assorted supporting types, including //! [`ControlFlow`]. //! //! If you want to send custom events to the event loop, use //! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its //! [`send_event`](`EventLoopProxy::send_event`) method. //! //! See the root-level documentation for information on how to create and use an event loop to //! handle events. use std::marker::PhantomData; use std::ops::Deref; #[cfg(any(x11_platform, wayland_platform))] use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{error, fmt}; #[cfg(not(wasm_platform))] use std::time::{Duration, Instant}; #[cfg(wasm_platform)] use web_time::{Duration, Instant}; use crate::error::EventLoopError; use crate::{event::Event, monitor::MonitorHandle, platform_impl}; /// 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. For example on Linux creating /// an event loop opens a connection to the X or Wayland server. /// /// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs. /// /// Note that this 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 _can_ be sent to an other thread, and the /// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread. /// /// [`Window`]: crate::window::Window pub struct EventLoop { pub(crate) event_loop: platform_impl::EventLoop, pub(crate) _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 Winit 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`. pub struct EventLoopWindowTarget { pub(crate) p: platform_impl::EventLoopWindowTarget, pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync } /// 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() } } static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); 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, /// and only once per application.*** /// /// Calling this function will result in display backend initialisation. /// /// ## Panics /// /// Attempting to create the event loop off the main 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. /// /// ## Platform-specific /// /// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` /// or `DISPLAY` respectively when building the event loop. /// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling /// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic. /// /// [`platform`]: crate::platform #[cfg_attr( android, doc = "[`.with_android_app(app)`]: crate::platform::android::EventLoopBuilderExtAndroid::with_android_app" )] #[cfg_attr( not(android), doc = "[`.with_android_app(app)`]: #only-available-on-android" )] #[inline] pub fn build(&mut self) -> Result, EventLoopError> { if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { return Err(EventLoopError::RecreationAttempt); } // Certain platforms accept a mutable reference in their API. #[allow(clippy::unnecessary_mut_passed)] Ok(EventLoop { event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?, _marker: PhantomData, }) } #[cfg(wasm_platform)] pub(crate) fn allow_event_loop_recreation() { EVENT_LOOP_CREATED.store(false, Ordering::Relaxed); } } 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 { .. }") } } /// Set through [`EventLoopWindowTarget::set_control_flow()`]. /// /// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted. /// /// Defaults to [`Wait`]. /// /// [`Wait`]: Self::Wait #[derive(Copy, Clone, Debug, Default, 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. #[default] Wait, /// When the current loop iteration finishes, suspend the thread until either another event /// arrives or the given time is reached. /// /// Useful for implementing efficient timers. Applications which want to render at the display's /// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API /// to reduce odds of missed frames. /// /// [`Poll`]: Self::Poll WaitUntil(Instant), } impl ControlFlow { /// Creates a [`ControlFlow`] that waits until a timeout has expired. /// /// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is /// instead set to [`Wait`]. /// /// [`WaitUntil`]: Self::WaitUntil /// [`Wait`]: Self::Wait pub fn wait_duration(timeout: Duration) -> Self { match Instant::now().checked_add(timeout) { Some(instant) => Self::WaitUntil(instant), None => Self::Wait, } } } impl EventLoop<()> { /// Alias for [`EventLoopBuilder::new().build()`]. /// /// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build #[inline] pub fn new() -> Result, EventLoopError> { EventLoopBuilder::new().build() } } impl EventLoop { #[deprecated = "Use `EventLoopBuilder::::with_user_event().build()` instead."] pub fn with_user_event() -> Result, EventLoopError> { EventLoopBuilder::::with_user_event().build() } /// Runs the event loop in the calling thread and calls the given `event_handler` closure /// to dispatch any pending events. /// /// Since the closure is `'static`, it must be a `move` closure if it needs to /// access any data from the calling context. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// /// ## Platform-specific /// /// - **iOS:** Will never return to the caller and so values not passed to this function will /// *not* be dropped before the process exits. /// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript exception /// (that Rust doesn't see) that will also mean that the rest of the function is never executed /// and any values not passed to this function will *not* be dropped. /// /// Web applications are recommended to use #[cfg_attr( wasm_platform, doc = "[`EventLoopExtWebSys::spawn()`][crate::platform::web::EventLoopExtWebSys::spawn()]" )] #[cfg_attr(not(wasm_platform), doc = "`EventLoopExtWebSys::spawn()`")] /// [^1] instead of [`run()`] to avoid the need /// for the Javascript exception trick, and to make it clearer that the event loop runs /// asynchronously (via the browser's own, internal, event loop) and doesn't block the /// current thread of execution like it does on other platforms. /// /// This function won't be available with `target_feature = "exception-handling"`. /// /// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow() /// [`run()`]: Self::run() /// [^1]: `EventLoopExtWebSys::spawn()` is only available on WASM. #[inline] #[cfg(not(all(wasm_platform, target_feature = "exception-handling")))] pub fn run(self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &EventLoopWindowTarget), { 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(), } } } #[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 EventLoop { /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { rwh_05::HasRawDisplayHandle::raw_display_handle(&**self) } } #[cfg(any(x11_platform, wayland_platform))] impl AsFd for EventLoop { /// Get the underlying [EventLoop]'s `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// loop must be polled with the [`pump_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio /// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } #[cfg(any(x11_platform, wayland_platform))] impl AsRawFd for EventLoop { /// Get the underlying [EventLoop]'s raw `fd` which you can register /// into other event loop, like [`calloop`] or [`mio`]. When doing so, the /// loop must be polled with the [`pump_events`] API. /// /// [`calloop`]: https://crates.io/crates/calloop /// [`mio`]: https://crates.io/crates/mio /// [`pump_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_events fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } 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 { #[allow(clippy::useless_conversion)] // false positive on some platforms 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. /// /// ## Platform-specific /// /// **Wayland / Web:** Always returns `None`. #[inline] pub fn primary_monitor(&self) -> Option { self.p .primary_monitor() .map(|inner| MonitorHandle { inner }) } /// Change if or when [`DeviceEvent`]s are captured. /// /// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit /// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing /// this at runtime to explicitly capture them again. /// /// ## Platform-specific /// /// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported. /// /// [`DeviceEvent`]: crate::event::DeviceEvent pub fn listen_device_events(&self, allowed: DeviceEvents) { self.p.listen_device_events(allowed); } /// Sets the [`ControlFlow`]. pub fn set_control_flow(&self, control_flow: ControlFlow) { self.p.set_control_flow(control_flow) } /// Gets the current [`ControlFlow`]. pub fn control_flow(&self) -> ControlFlow { self.p.control_flow() } /// This exits the event loop. /// /// See [`LoopExiting`](Event::LoopExiting). pub fn exit(&self) { self.p.exit() } /// Returns if the [`EventLoop`] is about to stop. /// /// See [`exit()`](Self::exit). pub fn exiting(&self) -> bool { self.p.exiting() } } #[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) }) } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for EventLoopWindowTarget { /// Returns a [`rwh_05::RawDisplayHandle`] for the event loop. fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { self.p.raw_display_handle_rwh_05() } } /// 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. /// /// [`UserEvent(event)`]: Event::UserEvent 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 [`EventLoopProxy::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 {} /// Control when device events are captured. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum DeviceEvents { /// Report device events regardless of window focus. Always, /// Only capture device events while the window is focused. #[default] WhenFocused, /// Never capture device events. Never, } /// A unique identifier of the winit's async request. /// /// This could be used to identify the async request once it's done /// and a specific action must be taken. /// /// One of the handling scenarious could be to maintain a working list /// containing [`AsyncRequestSerial`] and some closure associated with it. /// Then once event is arriving the working list is being traversed and a job /// executed and removed from the list. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AsyncRequestSerial { serial: usize, } impl AsyncRequestSerial { // TODO(kchibisov): Remove `cfg` when the clipboard will be added. #[allow(dead_code)] pub(crate) fn get() -> Self { static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0); // NOTE: We rely on wrap around here, while the user may just request // in the loop usize::MAX times that's issue is considered on them. let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed); Self { serial } } } winit-0.29.15/src/icon.rs000064400000000000000000000077651046102023000132400ustar 00000000000000use 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::(); #[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. 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. DimensionsVsPixelCount { width: u32, height: u32, width_x_height: usize, pixel_count: usize, }, /// 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::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"), } } } impl Error for BadIcon {} #[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 { pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { if rgba.len() % PIXEL_SIZE != 0 { return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len(), }); } let pixel_count = rgba.len() / PIXEL_SIZE; if pixel_count != (width * height) as usize { Err(BadIcon::DimensionsVsPixelCount { width, height, width_x_height: (width * height) as usize, 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)?, }) } } winit-0.29.15/src/keyboard.rs000064400000000000000000002037131046102023000140770ustar 00000000000000//! 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 --------------------------------------------------------- // // winit: https://github.com/rust-windowing/winit // // 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 --------------------------------------------------------------- pub use smol_str::SmolStr; /// Contains the platform-native physical key identifier /// /// The exact values vary from platform to platform (which is part of why this is a per-platform /// enum), but the values are primarily tied to the key's physical location on the keyboard. /// /// This enum is primarily used to store raw keycodes when Winit doesn't map a given native /// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we /// haven't mapped for you yet, this lets you use use [`KeyCode`] to: /// /// - Correctly match key press and release events. /// - On non-web platforms, support assigning keybinds to virtually any key through a UI. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NativeKeyCode { Unidentified, /// An Android "scancode". Android(u32), /// A macOS "scancode". MacOS(u16), /// A Windows "scancode". Windows(u16), /// An XKB "keycode". Xkb(u32), } impl std::fmt::Debug for NativeKeyCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb}; let mut debug_tuple; match self { Unidentified => { debug_tuple = f.debug_tuple("Unidentified"); } Android(code) => { debug_tuple = f.debug_tuple("Android"); debug_tuple.field(&format_args!("0x{code:04X}")); } MacOS(code) => { debug_tuple = f.debug_tuple("MacOS"); debug_tuple.field(&format_args!("0x{code:04X}")); } Windows(code) => { debug_tuple = f.debug_tuple("Windows"); debug_tuple.field(&format_args!("0x{code:04X}")); } Xkb(code) => { debug_tuple = f.debug_tuple("Xkb"); debug_tuple.field(&format_args!("0x{code:04X}")); } } debug_tuple.finish() } } /// Contains the platform-native logical key identifier /// /// Exactly what that means differs from platform to platform, but the values are to some degree /// tied to the currently active keyboard layout. The same key on the same keyboard may also report /// different values on different platforms, which is one of the reasons this is a per-platform /// enum. /// /// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical /// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user /// define keybinds which work in the presence of identifiers we haven't mapped for you yet. #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NativeKey { Unidentified, /// An Android "keycode", which is similar to a "virtual-key code" on Windows. Android(u32), /// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or /// "virtual-key" codes in macOS, so we report the scancode instead. MacOS(u16), /// A Windows "virtual-key code". Windows(u16), /// An XKB "keysym". Xkb(u32), /// A "key value string". Web(SmolStr), } impl std::fmt::Debug for NativeKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb}; let mut debug_tuple; match self { Unidentified => { debug_tuple = f.debug_tuple("Unidentified"); } Android(code) => { debug_tuple = f.debug_tuple("Android"); debug_tuple.field(&format_args!("0x{code:04X}")); } MacOS(code) => { debug_tuple = f.debug_tuple("MacOS"); debug_tuple.field(&format_args!("0x{code:04X}")); } Windows(code) => { debug_tuple = f.debug_tuple("Windows"); debug_tuple.field(&format_args!("0x{code:04X}")); } Xkb(code) => { debug_tuple = f.debug_tuple("Xkb"); debug_tuple.field(&format_args!("0x{code:04X}")); } Web(code) => { debug_tuple = f.debug_tuple("Web"); debug_tuple.field(code); } } debug_tuple.finish() } } impl From for NativeKey { #[inline] fn from(code: NativeKeyCode) -> Self { match code { NativeKeyCode::Unidentified => NativeKey::Unidentified, NativeKeyCode::Android(x) => NativeKey::Android(x), NativeKeyCode::MacOS(x) => NativeKey::MacOS(x), NativeKeyCode::Windows(x) => NativeKey::Windows(x), NativeKeyCode::Xkb(x) => NativeKey::Xkb(x), } } } impl PartialEq for NativeKeyCode { #[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated #[inline] fn eq(&self, rhs: &NativeKey) -> bool { NativeKey::from(*self) == *rhs } } impl PartialEq for NativeKey { #[inline] fn eq(&self, rhs: &NativeKeyCode) -> bool { rhs == self } } /// Represents the location of a physical key. /// /// This type is a superset of [`KeyCode`], including an [`Unidentified`](Self::Unidentified) /// variant. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PhysicalKey { /// A known key code Code(KeyCode), /// This variant is used when the key cannot be translated to a [`KeyCode`] /// /// The native keycode is provided (if available) so you're able to more reliably match /// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use /// this for keybinds for non-standard keys, but such keybinds are tied to a given platform. Unidentified(NativeKeyCode), } impl From for PhysicalKey { #[inline] fn from(code: KeyCode) -> Self { PhysicalKey::Code(code) } } impl From for PhysicalKey { #[inline] fn from(code: NativeKeyCode) -> Self { PhysicalKey::Unidentified(code) } } impl PartialEq for PhysicalKey { #[inline] fn eq(&self, rhs: &KeyCode) -> bool { match self { PhysicalKey::Code(ref code) => code == rhs, _ => false, } } } impl PartialEq for KeyCode { #[inline] fn eq(&self, rhs: &PhysicalKey) -> bool { rhs == self } } impl PartialEq for PhysicalKey { #[inline] fn eq(&self, rhs: &NativeKeyCode) -> bool { match self { PhysicalKey::Unidentified(ref code) => code == rhs, _ => false, } } } impl PartialEq for NativeKeyCode { #[inline] fn eq(&self, rhs: &PhysicalKey) -> bool { rhs == self } } /// Code representing the location 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. /// /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyCode { /// ` 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, /// . 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, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. 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, } /// A [`Key::Named`] value /// /// 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. /// /// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NamedKey { /// 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, // Legacy modifier key. Also called "Super" in certain places. Meta, // Legacy modifier key. 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, } /// Key represents the meaning of a keypress. /// /// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with /// additions: /// - All simple variants are wrapped under the `Named` variant /// - 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/ #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Key { /// A simple (unparameterised) action Named(NamedKey), /// 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(Str), /// This variant is used when the key cannot be translated to any other variant. /// /// The native key is provided (if available) in order to allow the user to specify keybindings /// for keys which are not defined by this API, mainly through some sort of UI. Unidentified(NativeKey), /// Contains the text representation of the dead-key when available. /// /// ## Platform-specific /// - **Web:** Always contains `None` Dead(Option), } impl From for Key { #[inline] fn from(action: NamedKey) -> Self { Key::Named(action) } } impl From for Key { #[inline] fn from(code: NativeKey) -> Self { Key::Unidentified(code) } } impl PartialEq for Key { #[inline] fn eq(&self, rhs: &NamedKey) -> bool { match self { Key::Named(ref a) => a == rhs, _ => false, } } } impl> PartialEq for Key { #[inline] fn eq(&self, rhs: &str) -> bool { match self { Key::Character(ref s) => s == rhs, _ => false, } } } impl> PartialEq<&str> for Key { #[inline] fn eq(&self, rhs: &&str) -> bool { self == *rhs } } impl PartialEq for Key { #[inline] fn eq(&self, rhs: &NativeKey) -> bool { match self { Key::Unidentified(ref code) => code == rhs, _ => false, } } } impl PartialEq> for NativeKey { #[inline] fn eq(&self, rhs: &Key) -> bool { rhs == self } } impl Key { /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on /// `Key`. All other variants remain unchanged. pub fn as_ref(&self) -> Key<&str> { match self { Key::Named(a) => Key::Named(*a), Key::Character(ch) => Key::Character(ch.as_str()), Key::Dead(d) => Key::Dead(*d), Key::Unidentified(u) => Key::Unidentified(u.clone()), } } } impl NamedKey { /// Convert an action to its approximate textual equivalent. /// /// # Examples /// /// ``` /// use winit::keyboard::NamedKey; /// /// assert_eq!(NamedKey::Enter.to_text(), Some("\r")); /// assert_eq!(NamedKey::F20.to_text(), None); /// ``` pub fn to_text(&self) -> Option<&str> { match self { NamedKey::Enter => Some("\r"), NamedKey::Backspace => Some("\x08"), NamedKey::Tab => Some("\t"), NamedKey::Space => Some(" "), NamedKey::Escape => Some("\x1b"), _ => None, } } } impl Key { /// Convert a key to its approximate textual equivalent. /// /// # Examples /// /// ``` /// use winit::keyboard::{NamedKey, Key}; /// /// assert_eq!(Key::Character("a".into()).to_text(), Some("a")); /// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r")); /// assert_eq!(Key::Named(NamedKey::F20).to_text(), None); /// ``` pub fn to_text(&self) -> Option<&str> { match self { Key::Named(action) => action.to_text(), Key::Character(ch) => Some(ch.as_str()), _ => None, } } } /// The location of the key on the keyboard. /// /// Certain physical keys on the keyboard can have the same value, but are in different locations. /// For instance, the Shift key can be on the left or right side of the keyboard, or the number /// keys can be above the letters or on the numpad. This enum allows the user to differentiate /// them. /// /// See the documentation for the [`location`] field on the [`KeyEvent`] struct for more information. /// /// [`location`]: ../event/struct.KeyEvent.html#structfield.location /// [`KeyEvent`]: crate::event::KeyEvent #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyLocation { /// The key is in its "normal" location on the keyboard. /// /// For instance, the "1" key above the "Q" key on a QWERTY keyboard will use this location. This /// invariant is also returned when the location of the key cannot be identified. /// /// ![Standard 1 key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_standard_1_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Standard, /// The key is on the left side of the keyboard. /// /// For instance, the left Shift key below the Caps Lock key on a QWERTY keyboard will use this /// location. /// /// ![Left Shift key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_left_shift_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Left, /// The key is on the right side of the keyboard. /// /// For instance, the right Shift key below the Enter key on a QWERTY keyboard will use this /// location. /// /// ![Right Shift key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_right_shift_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Right, /// The key is on the numpad. /// /// For instance, the "1" key on the numpad will use this location. /// /// ![Numpad 1 key](https://raw.githubusercontent.com/rust-windowing/winit/master/docs/res/keyboard_numpad_1_key.svg) /// /// /// For image attribution, see the /// /// ATTRIBUTION.md /// /// file. /// Numpad, } bitflags! { /// Represents the current state of the keyboard modifiers /// /// Each flag represents a modifier and is set if this modifier is active. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ModifiersState: u32 { /// The "shift" key. const SHIFT = 0b100; /// The "control" key. const CONTROL = 0b100 << 3; /// The "alt" key. const ALT = 0b100 << 6; /// This is the "windows" key on PC and "command" key on Mac. const SUPER = 0b100 << 9; } } 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) } } /// The state of the particular modifiers key. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum ModifiersKeyState { /// The particular key is pressed. Pressed, /// The state of the key is unknown. #[default] Unknown, } // NOTE: the exact modifier key is not used to represent modifiers state in the // first place due to a fact that modifiers state could be changed without any // key being pressed and on some platforms like Wayland/X11 which key resulted // in modifiers change is hidden, also, not that it really matters. // // The reason this API is even exposed is mostly to provide a way for users // to treat modifiers differently based on their position, which is required // on macOS due to their AltGr/Option situation. bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct ModifiersKeys: u8 { const LSHIFT = 0b0000_0001; const RSHIFT = 0b0000_0010; const LCONTROL = 0b0000_0100; const RCONTROL = 0b0000_1000; const LALT = 0b0001_0000; const RALT = 0b0010_0000; const LSUPER = 0b0100_0000; const RSUPER = 0b1000_0000; } } #[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) } } } winit-0.29.15/src/lib.rs000064400000000000000000000177611046102023000130530ustar 00000000000000//! Winit is a cross-platform 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 winit::event_loop::EventLoop; //! let event_loop = EventLoop::new().unwrap(); //! ``` //! //! 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 [`Event::UserEvent`]s, if desired. //! //! You can retrieve events by calling [`EventLoop::run()`]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! will run until [`exit()`] is used, at which point [`Event::LoopExiting`]. //! //! Winit 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 #![cfg_attr( any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform ), doc = "[`EventLoopExtPumpEvents::pump_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_events()]" )] #![cfg_attr( not(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform )), doc = "`EventLoopExtPumpEvents::pump_events()`" )] //! [^1]. See that method's documentation for more reasons about why //! it's discouraged beyond compatibility reasons. //! //! //! ```no_run //! use winit::{ //! event::{Event, WindowEvent}, //! event_loop::{ControlFlow, EventLoop}, //! window::WindowBuilder, //! }; //! //! let event_loop = EventLoop::new().unwrap(); //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // dispatched any events. This is ideal for games and similar applications. //! event_loop.set_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. //! event_loop.set_control_flow(ControlFlow::Wait); //! //! event_loop.run(move |event, elwt| { //! match event { //! Event::WindowEvent { //! event: WindowEvent::CloseRequested, //! .. //! } => { //! println!("The close button was pressed; stopping"); //! elwt.exit(); //! }, //! Event::AboutToWait => { //! // 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 render here instead. //! window.request_redraw(); //! }, //! Event::WindowEvent { //! event: WindowEvent::RedrawRequested, //! .. //! } => { //! // Redraw the application. //! // //! // It's preferable for applications that do not render continuously to render in //! // this event rather than in AboutToWait, since rendering in here allows //! // the program to gracefully handle redraws requested by the OS. //! }, //! _ => () //! } //! }); //! ``` //! //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be //! compared to the value returned by [`Window::id()`] to determine which [`Window`] //! dispatched the event. //! //! # Drawing on the window //! //! Winit 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 //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [`EventLoop::run()`]: event_loop::EventLoop::run //! [`exit()`]: event_loop::EventLoopWindowTarget::exit //! [`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()`]: window::Window::id //! [`WindowEvent`]: event::WindowEvent //! [`DeviceEvent`]: event::DeviceEvent //! [`Event::UserEvent`]: event::Event::UserEvent //! [`Event::LoopExiting`]: event::Event::LoopExiting //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [^1]: `EventLoopExtPumpEvents::pump_events()` is only available on Windows, macOS, Android, X11 and Wayland. #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::all)] #![deny(unsafe_op_in_unsafe_fn)] #![cfg_attr(feature = "cargo-clippy", deny(warnings))] // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow(clippy::missing_safety_doc)] #[cfg(feature = "rwh_06")] pub use rwh_06 as raw_window_handle; #[allow(unused_imports)] #[macro_use] extern crate log; #[cfg(feature = "serde")] #[macro_use] extern crate serde; #[macro_use] extern crate bitflags; pub mod 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; /// Wrapper for objects which winit will access on the main thread so they are effectively `Send` /// and `Sync`, since they always execute on a single thread. /// /// # Safety /// /// Winit can run only one event loop at a time, and the event loop itself is tied to some thread. /// The objects could be sent across the threads, but once passed to winit, they execute on the /// main thread if the platform demands it. Thus, marking such objects as `Send + Sync` is safe. #[doc(hidden)] #[derive(Clone, Debug)] pub(crate) struct SendSyncWrapper(pub(crate) T); unsafe impl Send for SendSyncWrapper {} unsafe impl Sync for SendSyncWrapper {} winit-0.29.15/src/monitor.rs000064400000000000000000000123201046102023000137560ustar 00000000000000//! Types useful for interacting with a user's monitors. //! //! If you want to get basic information about a monitor, you can use the //! [`MonitorHandle`] type. This is retrieved from one of the following //! methods, which return an iterator of [`MonitorHandle`]: //! - [`EventLoopWindowTarget::available_monitors`](crate::event_loop::EventLoopWindowTarget::available_monitors). //! - [`Window::available_monitors`](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`]. #[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 { self.monitor().cmp(&other.monitor()).then( self.size() .cmp(&other.size()) .then( self.refresh_rate_millihertz() .cmp(&other.refresh_rate_millihertz()) .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 /// /// - **Wayland / Orbital:** Always returns 32. /// - **iOS:** Always returns 32. #[inline] pub fn bit_depth(&self) -> u16 { self.video_mode.bit_depth() } /// Returns the refresh rate of this video mode in mHz. #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.video_mode.refresh_rate_millihertz() } /// 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 { MonitorHandle { inner: self.video_mode.monitor(), } } } impl std::fmt::Display for VideoMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}x{} @ {} mHz ({} bpp)", self.size().width, self.size().height, self.refresh_rate_millihertz(), 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() } /// The monitor refresh rate used by the system. /// /// Return `Some` if succeed, or `None` if failed, which usually happens when the monitor /// the window is on is removed. /// /// When using exclusive fullscreen, the refresh rate of the [`VideoMode`] that was used to /// enter fullscreen should be used instead. #[inline] pub fn refresh_rate_millihertz(&self) -> Option { self.inner.refresh_rate_millihertz() } /// Returns the scale factor of the underlying monitor. To map logical pixels to physical /// pixels and vice versa, use [`Window::scale_factor`]. /// /// See the [`dpi`](crate::dpi) module for more information. /// /// ## Platform-specific /// /// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **Wayland:** May differ from [`Window::scale_factor`]. /// - **Android:** Always returns 1.0. /// /// [`Window::scale_factor`]: crate::window::Window::scale_factor #[inline] pub fn scale_factor(&self) -> f64 { self.inner.scale_factor() } /// Returns all fullscreen video modes supported by this monitor. /// /// ## Platform-specific /// /// - **Web:** Always returns an empty iterator #[inline] pub fn video_modes(&self) -> impl Iterator { self.inner .video_modes() .map(|video_mode| VideoMode { video_mode }) } } winit-0.29.15/src/platform/android.rs000064400000000000000000000063761046102023000155510ustar 00000000000000use crate::{ event_loop::{EventLoop, EventLoopBuilder, EventLoopWindowTarget}, window::{Window, WindowBuilder}, }; use android_activity::{AndroidApp, ConfigurationRef, Rect}; /// 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) -> ConfigurationRef; } impl WindowExtAndroid for Window { fn content_rect(&self) -> Rect { self.window.content_rect() } fn config(&self) -> ConfigurationRef { self.window.config() } } impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} /// Additional methods on [`WindowBuilder`] that are specific to Android. pub trait WindowBuilderExtAndroid {} impl WindowBuilderExtAndroid for WindowBuilder {} pub trait EventLoopBuilderExtAndroid { /// Associates the `AndroidApp` that was passed to `android_main()` with the event loop /// /// This must be called on Android since the `AndroidApp` is not global state. fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; /// Calling this will mark the volume keys to be manually handled by the application /// /// Default is to let the operating system handle the volume keys fn handle_volume_keys(&mut self) -> &mut Self; } impl EventLoopBuilderExtAndroid for EventLoopBuilder { fn with_android_app(&mut self, app: AndroidApp) -> &mut Self { self.platform_specific.android_app = Some(app); self } fn handle_volume_keys(&mut self) -> &mut Self { self.platform_specific.ignore_volume_keys = false; self } } /// Re-export of the `android_activity` API /// /// Winit re-exports the `android_activity` API for convenience so that most /// applications can rely on the Winit crate to resolve the required version of /// `android_activity` and avoid any chance of a conflict between Winit and the /// application crate. /// /// Unlike most libraries there can only be a single implementation /// of the `android_activity` glue crate linked with an application because /// it is responsible for the application's `android_main()` entry point. /// /// Since Winit depends on a specific version of `android_activity` the simplest /// way to avoid creating a conflict is for applications to avoid explicitly /// depending on the `android_activity` crate, and instead consume the API that /// is re-exported by Winit. /// /// For compatibility applications should then import the `AndroidApp` type for /// their `android_main(app: AndroidApp)` function like: /// ```rust /// #[cfg(target_os="android")] /// use winit::platform::android::activity::AndroidApp; /// ``` pub mod activity { // We enable the `"native-activity"` feature just so that we can build the // docs, but it'll be very confusing for users to see the docs with that // feature enabled, so we avoid inlining it so that they're forced to view // it on the crate's own docs.rs page. #[doc(no_inline)] pub use android_activity::*; } winit-0.29.15/src/platform/ios.rs000064400000000000000000000300661046102023000147140ustar 00000000000000use std::os::raw::c_void; use icrate::Foundation::MainThreadMarker; use objc2::rc::Id; use crate::{ event_loop::EventLoop, monitor::{MonitorHandle, VideoMode}, 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 { /// 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 sets the value of the /// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc) /// property. /// /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc) /// is also called for you. fn set_prefers_status_bar_hidden(&self, hidden: bool); /// Sets the preferred status bar style for the [`Window`]. /// /// The default is system-defined. /// /// This sets the value of the /// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc) /// property. /// /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc) /// is also called for you. fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle); } impl WindowExtIOS for Window { #[inline] fn set_scale_factor(&self, scale_factor: f64) { self.window .maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor)) } #[inline] fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { self.window .maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations)) } #[inline] fn set_prefers_home_indicator_hidden(&self, hidden: bool) { self.window .maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden)) } #[inline] fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { self.window.maybe_queue_on_main(move |w| { w.set_preferred_screen_edges_deferring_system_gestures(edges) }) } #[inline] fn set_prefers_status_bar_hidden(&self, hidden: bool) { self.window .maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden)) } #[inline] fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { self.window .maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style)) } } /// Additional methods on [`WindowBuilder`] that are specific to iOS. pub trait WindowBuilderExtIOS { /// 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) -> Self; /// 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) -> Self; /// 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) -> Self; /// 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) -> Self; /// 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) -> Self; /// Sets the style of the [`Window`]'s status bar. /// /// The default is system-defined. /// /// This sets the initial value returned by /// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc), fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self; } impl WindowBuilderExtIOS for WindowBuilder { #[inline] fn with_scale_factor(mut self, scale_factor: f64) -> Self { self.platform_specific.scale_factor = Some(scale_factor); self } #[inline] fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { self.platform_specific.valid_orientations = valid_orientations; self } #[inline] fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { self.platform_specific.prefers_home_indicator_hidden = hidden; self } #[inline] fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self { self.platform_specific .preferred_screen_edges_deferring_system_gestures = edges; self } #[inline] fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self { self.platform_specific.prefers_status_bar_hidden = hidden; self } #[inline] fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self { self.platform_specific.preferred_status_bar_style = status_bar_style; 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 { // SAFETY: The marker is only used to get the pointer of the screen let mtm = unsafe { MainThreadMarker::new_unchecked() }; Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void } #[inline] fn preferred_video_mode(&self) -> VideoMode { VideoMode { video_mode: self.inner.preferred_video_mode(), } } } /// Valid orientations for a particular [`Window`]. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ValidOrientations { /// Excludes `PortraitUpsideDown` on iphone #[default] LandscapeAndPortrait, Landscape, /// Excludes `PortraitUpsideDown` on iphone Portrait, } /// The device [idiom]. /// /// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 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(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] 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(); } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum StatusBarStyle { #[default] Default, LightContent, DarkContent, } winit-0.29.15/src/platform/macos.rs000064400000000000000000000331561046102023000152270ustar 00000000000000use std::os::raw::c_void; use objc2::rc::Id; use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; /// Additional methods on [`Window`] that are specific to MacOS. pub trait WindowExtMacOS { /// 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); /// 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; /// Select next tab. fn select_next_tab(&self); /// Select previous tab. fn select_previous_tab(&self); /// Select the tab with the given index. /// /// Will no-op when the index is out of bounds. fn select_tab_at_index(&self, index: usize); /// Get the number of tabs in the window tab group. fn num_tabs(&self) -> usize; /// Get the window's edit state. /// /// # Examples /// /// ```ignore /// WindowEvent::CloseRequested => { /// if window.is_document_edited() { /// // Show the user a save pop-up or similar /// } else { /// // Close the window /// drop(window); /// } /// } /// ``` fn is_document_edited(&self) -> bool; /// Put the window in a state which indicates a file save is required. fn set_document_edited(&self, edited: bool); /// Set option as alt behavior as described in [`OptionAsAlt`]. /// /// This will ignore diacritical marks and accent characters from /// being processed as received characters. Instead, the input /// device's raw character will be placed in event queues with the /// Alt modifier set. fn set_option_as_alt(&self, option_as_alt: OptionAsAlt); /// Getter for the [`WindowExtMacOS::set_option_as_alt`]. fn option_as_alt(&self) -> OptionAsAlt; } impl WindowExtMacOS for Window { #[inline] fn simple_fullscreen(&self) -> bool { self.window.maybe_wait_on_main(|w| w.simple_fullscreen()) } #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window .maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen)) } #[inline] fn has_shadow(&self) -> bool { self.window.maybe_wait_on_main(|w| w.has_shadow()) } #[inline] fn set_has_shadow(&self, has_shadow: bool) { self.window .maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow)) } #[inline] fn set_tabbing_identifier(&self, identifier: &str) { self.window .maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier)) } #[inline] fn tabbing_identifier(&self) -> String { self.window.maybe_wait_on_main(|w| w.tabbing_identifier()) } #[inline] fn select_next_tab(&self) { self.window.maybe_queue_on_main(|w| w.select_next_tab()) } #[inline] fn select_previous_tab(&self) { self.window.maybe_queue_on_main(|w| w.select_previous_tab()) } #[inline] fn select_tab_at_index(&self, index: usize) { self.window .maybe_queue_on_main(move |w| w.select_tab_at_index(index)) } #[inline] fn num_tabs(&self) -> usize { self.window.maybe_wait_on_main(|w| w.num_tabs()) } #[inline] fn is_document_edited(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_document_edited()) } #[inline] fn set_document_edited(&self, edited: bool) { self.window .maybe_queue_on_main(move |w| w.set_document_edited(edited)) } #[inline] fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { self.window .maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt)) } #[inline] fn option_as_alt(&self) -> OptionAsAlt { self.window.maybe_wait_on_main(|w| w.option_as_alt()) } } /// Corresponds to `NSApplicationActivationPolicy`. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] 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 [`WindowBuilder::with_decorations`] method: /// - `with_titlebar_transparent` /// - `with_title_hidden` /// - `with_titlebar_hidden` /// - `with_titlebar_buttons_hidden` /// - `with_fullsize_content_view` pub trait WindowBuilderExtMacOS { /// 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) -> Self; /// Makes the titlebar transparent and allows the content to appear behind it. fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self; /// Hides the window title. fn with_title_hidden(self, title_hidden: bool) -> Self; /// Hides the window titlebar. fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self; /// Hides the window titlebar buttons. fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self; /// Makes the window content appear behind the titlebar. fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self; fn with_has_shadow(self, has_shadow: bool) -> Self; /// Window accepts click-through mouse events. fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self; /// Defines the window tabbing identifier. /// /// fn with_tabbing_identifier(self, identifier: &str) -> Self; /// Set how the Option keys are interpreted. /// /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; } impl WindowBuilderExtMacOS for WindowBuilder { #[inline] fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self { self.platform_specific.movable_by_window_background = movable_by_window_background; self } #[inline] fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self { self.platform_specific.titlebar_transparent = titlebar_transparent; self } #[inline] fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self { self.platform_specific.titlebar_hidden = titlebar_hidden; self } #[inline] fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self { self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden; self } #[inline] fn with_title_hidden(mut self, title_hidden: bool) -> Self { self.platform_specific.title_hidden = title_hidden; self } #[inline] fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { self.platform_specific.fullsize_content_view = fullsize_content_view; self } #[inline] fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self { self.platform_specific.disallow_hidpi = disallow_hidpi; self } #[inline] fn with_has_shadow(mut self, has_shadow: bool) -> Self { self.platform_specific.has_shadow = has_shadow; self } #[inline] fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self { self.platform_specific.accepts_first_mouse = accepts_first_mouse; self } #[inline] fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self { self.platform_specific .tabbing_identifier .replace(tabbing_identifier.to_string()); self } #[inline] fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self { self.platform_specific.option_as_alt = option_as_alt; self } } pub trait EventLoopBuilderExtMacOS { /// Sets the activation policy for the application. /// /// It is set to [`ActivationPolicy::Regular`] by default. /// /// # Example /// /// Set the activation policy to "accessory". /// /// ``` /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "macos")] /// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy}; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "macos")] /// builder.with_activation_policy(ActivationPolicy::Accessory); /// # if false { // We can't test this part /// let event_loop = builder.build(); /// # } /// ``` fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self; /// Used to control whether a default menubar menu is created. /// /// Menu creation is enabled by default. /// /// # Example /// /// Disable creating a default menubar. /// /// ``` /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "macos")] /// use winit::platform::macos::EventLoopBuilderExtMacOS; /// /// let mut builder = EventLoopBuilder::new(); /// #[cfg(target_os = "macos")] /// builder.with_default_menu(false); /// # if false { // We can't test this part /// let event_loop = builder.build(); /// # } /// ``` fn with_default_menu(&mut self, enable: bool) -> &mut Self; /// 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. fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self; } impl EventLoopBuilderExtMacOS for EventLoopBuilder { #[inline] fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self { self.platform_specific.activation_policy = activation_policy; self } #[inline] fn with_default_menu(&mut self, enable: bool) -> &mut Self { self.platform_specific.default_menu = enable; self } #[inline] fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self { self.platform_specific.activate_ignoring_other_apps = ignore; self } } /// 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| Id::as_ptr(&s) as _) } } /// 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); /// Hide the other applications. In most applications this is typically triggered with Command+Option-H. fn hide_other_applications(&self); /// Set 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; } impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { fn hide_application(&self) { self.p.hide_application() } fn hide_other_applications(&self) { self.p.hide_other_applications() } fn set_allows_automatic_window_tabbing(&self, enabled: bool) { self.p.set_allows_automatic_window_tabbing(enabled); } fn allows_automatic_window_tabbing(&self) -> bool { self.p.allows_automatic_window_tabbing() } } /// Option as alt behavior. /// /// The default is `None`. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum OptionAsAlt { /// The left `Option` key is treated as `Alt`. OnlyLeft, /// The right `Option` key is treated as `Alt`. OnlyRight, /// Both `Option` keys are treated as `Alt`. Both, /// No special handling is applied for `Option` key. #[default] None, } winit-0.29.15/src/platform/mod.rs000064400000000000000000000025021046102023000146730ustar 00000000000000//! Contains traits with platform-specific methods in them. //! //! Contains the follow OS-specific modules: //! //! - `android` //! - `ios` //! - `macos` //! - `unix` //! - `windows` //! - `web` //! //! And the following platform-specific modules: //! //! - `run_on_demand` (available on `windows`, `unix`, `macos`, `android`) //! - `pump_events` (available on `windows`, `unix`, `macos`, `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. #[cfg(android_platform)] pub mod android; #[cfg(ios_platform)] pub mod ios; #[cfg(macos_platform)] pub mod macos; #[cfg(orbital_platform)] pub mod orbital; #[cfg(any(x11_platform, wayland_platform))] pub mod startup_notify; #[cfg(wayland_platform)] pub mod wayland; #[cfg(wasm_platform)] pub mod web; #[cfg(windows_platform)] pub mod windows; #[cfg(x11_platform)] pub mod x11; #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform ))] pub mod run_on_demand; #[cfg(any( windows_platform, macos_platform, android_platform, x11_platform, wayland_platform ))] pub mod pump_events; #[cfg(any( windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform, docsrs ))] pub mod modifier_supplement; pub mod scancode; winit-0.29.15/src/platform/modifier_supplement.rs000064400000000000000000000020001046102023000201570ustar 00000000000000#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] use crate::keyboard::Key; /// Additional methods for the `KeyEvent` which cannot be implemented on all /// platforms. pub trait KeyEventExtModifierSupplement { /// Identical to `KeyEvent::text` but this is affected by Ctrl. /// /// For example, pressing Ctrl+a produces `Some("\x01")`. fn text_with_all_modifiers(&self) -> Option<&str>; /// 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`. fn key_without_modifiers(&self) -> Key; } winit-0.29.15/src/platform/orbital.rs000064400000000000000000000000551046102023000155510ustar 00000000000000// There are no Orbital specific traits yet. winit-0.29.15/src/platform/pump_events.rs000064400000000000000000000176011046102023000164670ustar 00000000000000use std::time::Duration; use crate::{ event::Event, event_loop::{EventLoop, EventLoopWindowTarget}, }; /// The return status for `pump_events` pub enum PumpStatus { /// Continue running external loop. Continue, /// Exit external loop. Exit(i32), } /// Additional methods on [`EventLoop`] for pumping events within an external event loop pub trait EventLoopExtPumpEvents { /// A type provided by the user that can be passed through [`Event::UserEvent`]. type UserEvent; /// Pump the `EventLoop` to check for and dispatch pending events. /// /// This API is designed to enable applications to integrate Winit into an /// external event loop, for platforms that can support this. /// /// The given `timeout` limits how long it may block waiting for new events. /// /// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external /// event loop is never blocked but you would likely need to consider how /// to throttle your own external loop. /// /// Passing a `timeout` of `None` means that it may wait indefinitely for new /// events before returning control back to the external loop. /// /// ## Example /// /// ```rust,no_run /// # // Copied from examples/window_pump_events.rs /// # #[cfg(any( /// # windows_platform, /// # macos_platform, /// # x11_platform, /// # wayland_platform, /// # android_platform, /// # ))] /// fn main() -> std::process::ExitCode { /// # use std::{process::ExitCode, thread::sleep, time::Duration}; /// # /// # use simple_logger::SimpleLogger; /// # use winit::{ /// # event::{Event, WindowEvent}, /// # event_loop::EventLoop, /// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, /// # window::WindowBuilder, /// # }; /// let mut event_loop = EventLoop::new().unwrap(); /// # /// # SimpleLogger::new().init().unwrap(); /// let window = WindowBuilder::new() /// .with_title("A fantastic window!") /// .build(&event_loop) /// .unwrap(); /// /// 'main: loop { /// let timeout = Some(Duration::ZERO); /// let status = event_loop.pump_events(timeout, |event, elwt| { /// # if let Event::WindowEvent { event, .. } = &event { /// # // Print only Window events to reduce noise /// # println!("{event:?}"); /// # } /// # /// match event { /// Event::WindowEvent { /// event: WindowEvent::CloseRequested, /// window_id, /// } if window_id == window.id() => elwt.exit(), /// Event::AboutToWait => { /// window.request_redraw(); /// } /// _ => (), /// } /// }); /// if let PumpStatus::Exit(exit_code) = status { /// break 'main ExitCode::from(exit_code as u8); /// } /// /// // Sleep for 1/60 second to simulate application work /// // /// // Since `pump_events` doesn't block it will be important to /// // throttle the loop in the app somehow. /// println!("Update()"); /// sleep(Duration::from_millis(16)); /// } /// } /// ``` /// /// **Note:** This is not a portable API, and its usage involves a number of /// caveats and trade offs that should be considered before using this API! /// /// You almost certainly shouldn't use this API, unless you absolutely know it's /// the only practical option you have. /// /// ## Synchronous events /// /// Some events _must_ only be handled synchronously via the closure that /// is passed to Winit so that the handler will also be synchronized with /// the window system and operating system. /// /// This is because some events are driven by a window system callback /// where the window systems expects the application to have handled the /// event before returning. /// /// **These events can not be buffered and handled outside of the closure /// passed to Winit.** /// /// As a general rule it is not recommended to ever buffer events to handle /// them outside of the closure passed to Winit since it's difficult to /// provide guarantees about which events are safe to buffer across all /// operating systems. /// /// Notable events that will certainly create portability problems if /// buffered and handled outside of Winit include: /// - `RedrawRequested` events, used to schedule rendering. /// /// macOS for example uses a `drawRect` callback to drive rendering /// within applications and expects rendering to be finished before /// the `drawRect` callback returns. /// /// For portability it's strongly recommended that applications should /// keep their rendering inside the closure provided to Winit. /// - Any lifecycle events, such as `Suspended` / `Resumed`. /// /// The handling of these events needs to be synchronized with the /// operating system and it would never be appropriate to buffer a /// notification that your application has been suspended or resumed and /// then handled that later since there would always be a chance that /// other lifecycle events occur while the event is buffered. /// /// ## Supported Platforms /// - Windows /// - Linux /// - MacOS /// - Android /// /// ## Unsupported Platforms /// - **Web:** This API is fundamentally incompatible with the event-based way in which /// Web browsers work because it's not possible to have a long-running external /// loop that would block the browser and there is nothing that can be /// polled to ask for new new events. Events are delivered via callbacks based /// on an event loop that is internal to the browser itself. /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so /// there's no way to support the same approach to polling as on MacOS. /// /// ## Platform-specific /// - **Windows**: The implementation will use `PeekMessage` when checking for /// window messages to avoid blocking your external event loop. /// /// - **MacOS**: The implementation works in terms of stopping the global `NSApp` /// whenever the application `RunLoop` indicates that it is preparing to block /// and wait for new events. /// /// This is very different to the polling APIs that are available on other /// platforms (the lower level polling primitives on MacOS are private /// implementation details for `NSApp` which aren't accessible to application /// developers) /// /// It's likely this will be less efficient than polling on other OSs and /// it also means the `NSApp` is stopped while outside of the Winit /// event loop - and that's observable (for example to crates like `rfd`) /// because the `NSApp` is global state. /// /// If you render outside of Winit you are likely to see window resizing artifacts /// since MacOS expects applications to render synchronously during any `drawRect` /// callback. fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut(Event, &EventLoopWindowTarget); } impl EventLoopExtPumpEvents for EventLoop { type UserEvent = T; fn pump_events(&mut self, timeout: Option, event_handler: F) -> PumpStatus where F: FnMut(Event, &EventLoopWindowTarget), { self.event_loop.pump_events(timeout, event_handler) } } winit-0.29.15/src/platform/run_on_demand.rs000064400000000000000000000075531046102023000167370ustar 00000000000000use crate::{ error::EventLoopError, event::Event, event_loop::{EventLoop, EventLoopWindowTarget}, }; #[cfg(doc)] use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window}; /// Additional methods on [`EventLoop`] to return control flow to the caller. pub trait EventLoopExtRunOnDemand { /// A type provided by the user that can be passed through [`Event::UserEvent`]. type UserEvent; /// Runs the event loop in the calling thread and calls the given `event_handler` closure /// to dispatch any window system events. /// /// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures /// and it is possible to return control back to the caller without /// consuming the `EventLoop` (by using [`exit()`]) and /// so the event loop can be re-run after it has exit. /// /// It's expected that each run of the loop will be for orthogonal instantiations of your /// Winit application, but internally each instantiation may re-use some common window /// system resources, such as a display server connection. /// /// This API is not designed to run an event loop in bursts that you can exit from and return /// to while maintaining the full state of your application. (If you need something like this /// you can look at the [`EventLoopExtPumpEvents::pump_events()`] API) /// /// Each time `run_on_demand` is called the `event_handler` can expect to receive a /// `NewEvents(Init)` and `Resumed` event (even on platforms that have no suspend/resume /// lifecycle) - which can be used to consistently initialize application state. /// /// See the [`set_control_flow()`] docs on how to change the event loop's behavior. /// /// # Caveats /// - This extension isn't available on all platforms, since it's not always possible to return /// to the caller (specifically this is impossible on iOS and Web - though with the Web /// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead). /// - No [`Window`] state can be carried between separate runs of the event loop. /// /// You are strongly encouraged to use [`EventLoop::run()`] for portability, unless you specifically need /// the ability to re-run a single event loop more than once /// /// # Supported Platforms /// - Windows /// - Linux /// - macOS /// - Android /// /// # Unsupported Platforms /// - **Web:** This API is fundamentally incompatible with the event-based way in which /// Web browsers work because it's not possible to have a long-running external /// loop that would block the browser and there is nothing that can be /// polled to ask for new events. Events are delivered via callbacks based /// on an event loop that is internal to the browser itself. /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS. /// #[cfg_attr( not(wasm_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms." )] /// /// [`exit()`]: EventLoopWindowTarget::exit() /// [`set_control_flow()`]: EventLoopWindowTarget::set_control_flow() fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &EventLoopWindowTarget); } impl EventLoopExtRunOnDemand for EventLoop { type UserEvent = T; fn run_on_demand(&mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &EventLoopWindowTarget), { self.event_loop.window_target().clear_exit(); self.event_loop.run_on_demand(event_handler) } } impl EventLoopWindowTarget { /// Clear exit status. pub(crate) fn clear_exit(&self) { self.p.clear_exit() } } winit-0.29.15/src/platform/scancode.rs000064400000000000000000000032221046102023000156730ustar 00000000000000#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] use crate::keyboard::{KeyCode, PhysicalKey}; // TODO: Describe what this value contains for each platform /// Additional methods for the [`PhysicalKey`] type that allow the user to access the platform-specific /// scancode. /// /// [`PhysicalKey`]: crate::keyboard::PhysicalKey pub trait PhysicalKeyExtScancode { /// The raw value of the platform-specific physical key identifier. /// /// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise. /// /// ## Platform-specific /// - **Windows:** A 16bit extended scancode /// - **Wayland/X11**: A 32-bit linux scancode, which is X11/Wayland keycode subtracted by 8. fn to_scancode(self) -> Option; /// Constructs a `PhysicalKey` from a platform-specific physical key identifier. /// /// Note that this conversion may be lossy, i.e. converting the returned `PhysicalKey` back /// using `to_scancode` might not yield the original value. /// /// ## Platform-specific /// - **Wayland/X11**: A 32-bit linux scancode. When building from X11/Wayland keycode subtract /// `8` to get the value you wanted. fn from_scancode(scancode: u32) -> PhysicalKey; } impl PhysicalKeyExtScancode for KeyCode where PhysicalKey: PhysicalKeyExtScancode, { #[inline] fn from_scancode(scancode: u32) -> PhysicalKey { ::from_scancode(scancode) } #[inline] fn to_scancode(self) -> Option { ::to_scancode(PhysicalKey::Code(self)) } } winit-0.29.15/src/platform/startup_notify.rs000064400000000000000000000071511046102023000172130ustar 00000000000000//! Window startup notification to handle window raising. //! //! The [`ActivationToken`] is essential to ensure that your newly //! created window will obtain the focus, otherwise the user could //! be requered to click on the window. //! //! Such token is usually delivered via the environment variable and //! could be read from it with the [`EventLoopExtStartupNotify::read_token_from_env`]. //! //! Such token must also be reset after reading it from your environment with //! [`reset_activation_token_env`] otherwise child processes could inherit it. //! //! When starting a new child process with a newly obtained [`ActivationToken`] from //! [`WindowExtStartupNotify::request_activation_token`] the [`set_activation_token_env`] //! must be used to propagate it to the child //! //! To ensure the delivery of such token by other processes to you, the user should //! set `StartupNotify=true` inside the `.desktop` file of their application. //! //! The specification could be found [`here`]. //! //! [`here`]: https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt use std::env; use crate::error::NotSupportedError; use crate::event_loop::{AsyncRequestSerial, EventLoopWindowTarget}; use crate::window::{ActivationToken, Window, WindowBuilder}; /// The variable which is used mostly on X11. const X11_VAR: &str = "DESKTOP_STARTUP_ID"; /// The variable which is used mostly on Wayland. const WAYLAND_VAR: &str = "XDG_ACTIVATION_TOKEN"; pub trait EventLoopExtStartupNotify { /// Read the token from the environment. /// /// It's recommended **to unset** this environment variable for child processes. fn read_token_from_env(&self) -> Option; } pub trait WindowExtStartupNotify { /// Request a new activation token. /// /// The token will be delivered inside fn request_activation_token(&self) -> Result; } pub trait WindowBuilderExtStartupNotify { /// Use this [`ActivationToken`] during window creation. /// /// Not using such a token upon a window could make your window not gaining /// focus until the user clicks on the window. fn with_activation_token(self, token: ActivationToken) -> Self; } impl EventLoopExtStartupNotify for EventLoopWindowTarget { fn read_token_from_env(&self) -> Option { match self.p { #[cfg(wayland_platform)] crate::platform_impl::EventLoopWindowTarget::Wayland(_) => env::var(WAYLAND_VAR), #[cfg(x11_platform)] crate::platform_impl::EventLoopWindowTarget::X(_) => env::var(X11_VAR), } .ok() .map(ActivationToken::_new) } } impl WindowExtStartupNotify for Window { fn request_activation_token(&self) -> Result { self.window.request_activation_token() } } impl WindowBuilderExtStartupNotify for WindowBuilder { fn with_activation_token(mut self, token: ActivationToken) -> Self { self.platform_specific.activation_token = Some(token); self } } /// Remove the activation environment variables from the current process. /// /// This is wise to do before running child processes, /// which may not to support the activation token. pub fn reset_activation_token_env() { env::remove_var(X11_VAR); env::remove_var(WAYLAND_VAR); } /// Set environment variables responsible for activation token. /// /// This could be used before running daemon processes. pub fn set_activation_token_env(token: ActivationToken) { env::set_var(X11_VAR, &token._token); env::set_var(WAYLAND_VAR, token._token); } winit-0.29.15/src/platform/wayland.rs000064400000000000000000000054041046102023000155570ustar 00000000000000use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; use crate::platform_impl::{ApplicationName, Backend}; pub use crate::window::Theme; /// Additional methods on [`EventLoopWindowTarget`] that are specific to Wayland. pub trait EventLoopWindowTargetExtWayland { /// True if the [`EventLoopWindowTarget`] uses Wayland. fn is_wayland(&self) -> bool; } impl EventLoopWindowTargetExtWayland for EventLoopWindowTarget { #[inline] fn is_wayland(&self) -> bool { self.p.is_wayland() } } /// Additional methods on [`EventLoopBuilder`] that are specific to Wayland. pub trait EventLoopBuilderExtWayland { /// Force using Wayland. fn with_wayland(&mut self) -> &mut Self; /// 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. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } impl EventLoopBuilderExtWayland for EventLoopBuilder { #[inline] fn with_wayland(&mut self) -> &mut Self { self.platform_specific.forced_backend = Some(Backend::Wayland); self } #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; self } } /// Additional methods on [`Window`] that are specific to Wayland. pub trait WindowExtWayland {} impl WindowExtWayland for Window {} /// Additional methods on [`WindowBuilder`] that are specific to Wayland. pub trait WindowBuilderExtWayland { /// Build window with the given name. /// /// The `general` name sets an application ID, which should match the `.desktop` /// file destributed with your program. The `instance` is a `no-op`. /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; } impl WindowBuilderExtWayland for WindowBuilder { #[inline] fn with_name(mut self, general: impl Into, instance: impl Into) -> Self { self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into())); self } } /// Additional methods on `MonitorHandle` that are specific to Wayland. pub trait MonitorHandleExtWayland { /// Returns the inner identifier of the monitor. fn native_id(&self) -> u32; } impl MonitorHandleExtWayland for MonitorHandle { #[inline] fn native_id(&self) -> u32 { self.inner.native_identifier() } } winit-0.29.15/src/platform/web.rs000064400000000000000000000171421046102023000146770ustar 00000000000000//! The web target does not automatically insert the canvas element object into the web page, to //! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait //! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait //! to provide your own canvas. //! //! It is recommended **not** to apply certain CSS properties to the canvas: //! - [`transform`] //! - [`border`] //! - [`padding`] //! //! The following APIs can't take them into account and will therefore provide inaccurate results: //! - [`WindowEvent::Resized`] and [`Window::(set_)inner_size()`] //! - [`WindowEvent::Occluded`] //! - [`WindowEvent::CursorMoved`], [`WindowEvent::CursorEntered`], [`WindowEvent::CursorLeft`], //! and [`WindowEvent::Touch`]. //! - [`Window::set_outer_position()`] //! //! [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized //! [`Window::(set_)inner_size()`]: crate::window::Window::inner_size() //! [`WindowEvent::Occluded`]: crate::event::WindowEvent::Occluded //! [`WindowEvent::CursorMoved`]: crate::event::WindowEvent::CursorMoved //! [`WindowEvent::CursorEntered`]: crate::event::WindowEvent::CursorEntered //! [`WindowEvent::CursorLeft`]: crate::event::WindowEvent::CursorLeft //! [`WindowEvent::Touch`]: crate::event::WindowEvent::Touch //! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position() //! [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform //! [`border`]: https://developer.mozilla.org/en-US/docs/Web/CSS/border //! [`padding`]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding use crate::event::Event; use crate::event_loop::EventLoop; use crate::event_loop::EventLoopWindowTarget; use crate::window::{Window, WindowBuilder}; use crate::SendSyncWrapper; use web_sys::HtmlCanvasElement; pub trait WindowExtWebSys { /// Only returns the canvas if called from inside the window. fn canvas(&self) -> Option; } impl WindowExtWebSys for Window { #[inline] fn canvas(&self) -> Option { self.window.canvas() } } pub trait WindowBuilderExtWebSys { /// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`], /// [`WindowBuilder::build()`] will create one. /// /// In any case, the canvas won't be automatically inserted into the web page. /// /// [`None`] by default. fn with_canvas(self, canvas: Option) -> Self; /// Whether `event.preventDefault` should be automatically called to prevent event propagation /// when appropriate. /// /// For example, mouse wheel events are only handled by the canvas by default. This avoids /// the default behavior of scrolling the page. /// /// Some events are impossible to prevent. E.g. Firefox allows to access the native browser /// context menu with Shift+Rightclick. /// /// Enabled by default. fn with_prevent_default(self, prevent_default: bool) -> Self; /// Whether the canvas should be focusable using the tab key. This is necessary to capture /// canvas keyboard events. /// /// Enabled by default. fn with_focusable(self, focusable: bool) -> Self; /// On window creation, append the canvas element to the web page if it isn't already. /// /// Disabled by default. fn with_append(self, append: bool) -> Self; } impl WindowBuilderExtWebSys for WindowBuilder { fn with_canvas(mut self, canvas: Option) -> Self { self.platform_specific.canvas = SendSyncWrapper(canvas); self } fn with_prevent_default(mut self, prevent_default: bool) -> Self { self.platform_specific.prevent_default = prevent_default; self } fn with_focusable(mut self, focusable: bool) -> Self { self.platform_specific.focusable = focusable; self } fn with_append(mut self, append: bool) -> Self { self.platform_specific.append = append; self } } /// Additional methods on `EventLoop` that are specific to the web. pub trait EventLoopExtWebSys { /// A type provided by the user that can be passed through `Event::UserEvent`. type UserEvent; /// Initializes the winit event loop. /// /// Unlike #[cfg_attr( all(wasm_platform, target_feature = "exception-handling"), doc = "`run()`" )] #[cfg_attr( not(all(wasm_platform, target_feature = "exception-handling")), doc = "[`run()`]" )] /// [^1], this returns immediately, and doesn't throw an exception in order to /// satisfy its [`!`] return type. /// /// Once the event loop has been destroyed, it's possible to reinitialize another event loop /// by calling this function again. This can be useful if you want to recreate the event loop /// while the WebAssembly module is still loaded. For example, this can be used to recreate the /// event loop when switching between tabs on a single page application. /// #[cfg_attr( not(all(wasm_platform, target_feature = "exception-handling")), doc = "[`run()`]: EventLoop::run()" )] /// [^1]: `run()` is _not_ available on WASM when the target supports `exception-handling`. fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &EventLoopWindowTarget); } impl EventLoopExtWebSys for EventLoop { type UserEvent = T; fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &EventLoopWindowTarget), { self.event_loop.spawn(event_handler) } } pub trait EventLoopWindowTargetExtWebSys { /// Sets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn set_poll_strategy(&self, strategy: PollStrategy); /// Gets the strategy for [`ControlFlow::Poll`]. /// /// See [`PollStrategy`]. /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; } impl EventLoopWindowTargetExtWebSys for EventLoopWindowTarget { #[inline] fn set_poll_strategy(&self, strategy: PollStrategy) { self.p.set_poll_strategy(strategy); } #[inline] fn poll_strategy(&self) -> PollStrategy { self.p.poll_strategy() } } /// Strategy used for [`ControlFlow::Poll`](crate::event_loop::ControlFlow::Poll). #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum PollStrategy { /// Uses [`Window.requestIdleCallback()`] to queue the next event loop. If not available /// this will fallback to [`setTimeout()`]. /// /// This strategy will wait for the browser to enter an idle period before running and might /// be affected by browser throttling. /// /// [`Window.requestIdleCallback()`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout IdleCallback, /// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available /// this will fallback to [`setTimeout()`]. /// /// This strategy will run as fast as possible without disturbing users from interacting with /// the page and is not affected by browser throttling. /// /// This is the default strategy. /// /// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout #[default] Scheduler, } winit-0.29.15/src/platform/windows.rs000064400000000000000000000311141046102023000156070ustar 00000000000000use std::{ffi::c_void, path::Path}; use crate::{ dpi::PhysicalSize, event::{DeviceId, KeyEvent}, event_loop::EventLoopBuilder, keyboard::Key, monitor::MonitorHandle, platform::modifier_supplement::KeyEventExtModifierSupplement, platform_impl::WinIcon, window::{BadIcon, Icon, Window, WindowBuilder}, }; /// Window Handle type used by Win32 API pub type HWND = isize; /// Menu Handle type used by Win32 API pub type HMENU = isize; /// Monitor Handle type used by Win32 API pub type HMONITOR = 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, `winit` 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 winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "windows")] /// use winit::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 winit's internal message dispatching. /// /// # Example /// /// ``` /// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG}; /// use winit::event_loop::EventLoopBuilder; /// #[cfg(target_os = "windows")] /// use winit::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 translated = unsafe { /// TranslateAcceleratorW( /// (*msg).hwnd, /// CreateAcceleratorTableW(accels.as_ptr() as _, 1), /// msg, /// ) == 1 /// }; /// translated /// }); /// ``` fn with_msg_hook(&mut self, callback: F) -> &mut Self where F: FnMut(*const c_void) -> bool + 'static; } 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 c_void) -> bool + 'static, { self.platform_specific.msg_hook = Some(Box::new(callback)); self } } /// Additional methods on `Window` that are specific to Windows. pub trait WindowExtWindows { /// 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); /// Whether to show or hide the window icon in the taskbar. fn set_skip_taskbar(&self, skip: bool); /// 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); } impl WindowExtWindows for Window { #[inline] fn set_enable(&self, enabled: bool) { self.window.set_enable(enabled) } #[inline] fn set_taskbar_icon(&self, taskbar_icon: Option) { self.window.set_taskbar_icon(taskbar_icon) } #[inline] fn set_skip_taskbar(&self, skip: bool) { self.window.set_skip_taskbar(skip) } #[inline] fn set_undecorated_shadow(&self, shadow: bool) { self.window.set_undecorated_shadow(shadow) } } /// Additional methods on `WindowBuilder` that are specific to Windows. #[allow(rustdoc::broken_intra_doc_links)] pub trait WindowBuilderExtWindows { /// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// This only works when [`WindowBuilder::with_parent_window`] isn't called or set to `None`. /// 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) -> Self; /// 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 [`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. /// /// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu fn with_menu(self, menu: HMENU) -> Self; /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn with_taskbar_icon(self, taskbar_icon: Option) -> Self; /// This sets `WS_EX_NOREDIRECTIONBITMAP`. fn with_no_redirection_bitmap(self, flag: bool) -> Self; /// 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 winit 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 winit functions. /// See for more information. fn with_drag_and_drop(self, flag: bool) -> Self; /// Whether show or hide the window icon in the taskbar. fn with_skip_taskbar(self, skip: bool) -> Self; /// Customize the window class name. fn with_class_name>(self, class_name: S) -> Self; /// 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) -> Self; } impl WindowBuilderExtWindows for WindowBuilder { #[inline] fn with_owner_window(mut self, parent: HWND) -> Self { self.platform_specific.owner = Some(parent); self } #[inline] fn with_menu(mut self, menu: HMENU) -> Self { self.platform_specific.menu = Some(menu); self } #[inline] fn with_taskbar_icon(mut self, taskbar_icon: Option) -> Self { self.platform_specific.taskbar_icon = taskbar_icon; self } #[inline] fn with_no_redirection_bitmap(mut self, flag: bool) -> Self { self.platform_specific.no_redirection_bitmap = flag; self } #[inline] fn with_drag_and_drop(mut self, flag: bool) -> Self { self.platform_specific.drag_and_drop = flag; self } #[inline] fn with_skip_taskbar(mut self, skip: bool) -> Self { self.platform_specific.skip_taskbar = skip; self } #[inline] fn with_class_name>(mut self, class_name: S) -> Self { self.platform_specific.class_name = class_name.into(); self } #[inline] fn with_undecorated_shadow(mut self, shadow: bool) -> Self { self.platform_specific.decoration_shadow = shadow; 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) -> HMONITOR; } impl MonitorHandleExtWindows for MonitorHandle { #[inline] fn native_id(&self) -> String { self.inner.native_identifier() } #[inline] fn hmonitor(&self) -> HMONITOR { self.inner.hmonitor() } } /// 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 }) } } impl KeyEventExtModifierSupplement for KeyEvent { #[inline] fn text_with_all_modifiers(&self) -> Option<&str> { self.platform_specific .text_with_all_modifers .as_ref() .map(|s| s.as_str()) } #[inline] fn key_without_modifiers(&self) -> Key { self.platform_specific.key_without_modifiers.clone() } } winit-0.29.15/src/platform/x11.rs000064400000000000000000000155131046102023000145330ustar 00000000000000use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; use crate::dpi::Size; use crate::platform_impl::{ApplicationName, Backend, XLIB_ERROR_HOOKS}; pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; /// The first argument in the provided hook will be the pointer to `XDisplay` /// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an /// indicator whether the error was handled by the callback. /// /// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent pub type XlibErrorHook = Box bool + Send + Sync>; /// A unique identifer for an X11 visual. pub type XVisualID = u32; /// A unique identifier for an X11 window. pub type XWindow = u32; /// Hook to winit's xlib error handling callback. /// /// This method is provided as a safe way to handle the errors comming from X11 /// when using xlib in external crates, like glutin for GLX access. Trying to /// handle errors by speculating with `XSetErrorHandler` is [`unsafe`]. /// /// **Be aware that your hook is always invoked and returning `true` from it will /// prevent `winit` from getting the error itself. It's wise to always return /// `false` if you're not initiated the `Sync`.** /// /// [`unsafe`]: https://www.remlab.net/op/xlib.shtml #[inline] pub fn register_xlib_error_hook(hook: XlibErrorHook) { // Append new hook. unsafe { XLIB_ERROR_HOOKS.lock().unwrap().push(hook); } } /// Additional methods on [`EventLoopWindowTarget`] that are specific to X11. pub trait EventLoopWindowTargetExtX11 { /// True if the [`EventLoopWindowTarget`] uses X11. fn is_x11(&self) -> bool; } impl EventLoopWindowTargetExtX11 for EventLoopWindowTarget { #[inline] fn is_x11(&self) -> bool { !self.p.is_wayland() } } /// Additional methods on [`EventLoopBuilder`] that are specific to X11. pub trait EventLoopBuilderExtX11 { /// Force using X11. fn with_x11(&mut self) -> &mut Self; /// 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. fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; } impl EventLoopBuilderExtX11 for EventLoopBuilder { #[inline] fn with_x11(&mut self) -> &mut Self { self.platform_specific.forced_backend = Some(Backend::X); self } #[inline] fn with_any_thread(&mut self, any_thread: bool) -> &mut Self { self.platform_specific.any_thread = any_thread; self } } /// Additional methods on [`Window`] that are specific to X11. pub trait WindowExtX11 {} impl WindowExtX11 for Window {} /// Additional methods on [`WindowBuilder`] that are specific to X11. pub trait WindowBuilderExtX11 { /// Create this window with a specific X11 visual. fn with_x11_visual(self, visual_id: XVisualID) -> Self; fn with_x11_screen(self, screen_id: i32) -> Self; /// Build window with the given `general` and `instance` names. /// /// The `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the /// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "instance", "general"`. /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; /// Build window with override-redirect flag; defaults to false. Only relevant on X11. fn with_override_redirect(self, override_redirect: bool) -> Self; /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11. fn with_x11_window_type(self, x11_window_type: Vec) -> Self; /// Build window with base size hint. Only implemented on X11. /// /// ``` /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::window::WindowBuilder; /// # use winit::platform::x11::WindowBuilderExtX11; /// // Specify the size in logical dimensions like this: /// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0)); /// /// // Or specify the size in physical dimensions like this: /// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200)); /// ``` fn with_base_size>(self, base_size: S) -> Self; /// Embed this window into another parent window. /// /// # Example /// /// ```no_run /// use winit::window::WindowBuilder; /// use winit::platform::x11::{XWindow, WindowBuilderExtX11}; /// # fn main() -> Result<(), Box> { /// let event_loop = winit::event_loop::EventLoop::new().unwrap(); /// let parent_window_id = std::env::args().nth(1).unwrap().parse::()?; /// let window = WindowBuilder::new() /// .with_embed_parent_window(parent_window_id) /// .build(&event_loop)?; /// # Ok(()) } /// ``` fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self; } impl WindowBuilderExtX11 for WindowBuilder { #[inline] fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { self.platform_specific.x11.visual_id = Some(visual_id); self } #[inline] fn with_x11_screen(mut self, screen_id: i32) -> Self { self.platform_specific.x11.screen_id = Some(screen_id); self } #[inline] fn with_name(mut self, general: impl Into, instance: impl Into) -> Self { self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into())); self } #[inline] fn with_override_redirect(mut self, override_redirect: bool) -> Self { self.platform_specific.x11.override_redirect = override_redirect; self } #[inline] fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { self.platform_specific.x11.x11_window_types = x11_window_types; self } #[inline] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.x11.base_size = Some(base_size.into()); self } #[inline] fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self { self.platform_specific.x11.embed_window = Some(parent_window_id); self } } /// Additional methods on `MonitorHandle` that are specific to X11. pub trait MonitorHandleExtX11 { /// Returns the inner identifier of the monitor. fn native_id(&self) -> u32; } impl MonitorHandleExtX11 for MonitorHandle { #[inline] fn native_id(&self) -> u32 { self.inner.native_identifier() } } winit-0.29.15/src/platform_impl/android/keycodes.rs000064400000000000000000000663011046102023000203720ustar 00000000000000use android_activity::{ input::{KeyAction, KeyEvent, KeyMapChar, Keycode}, AndroidApp, }; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; pub fn to_physical_key(keycode: Keycode) -> PhysicalKey { PhysicalKey::Code(match keycode { Keycode::A => KeyCode::KeyA, Keycode::B => KeyCode::KeyB, Keycode::C => KeyCode::KeyC, Keycode::D => KeyCode::KeyD, Keycode::E => KeyCode::KeyE, Keycode::F => KeyCode::KeyF, Keycode::G => KeyCode::KeyG, Keycode::H => KeyCode::KeyH, Keycode::I => KeyCode::KeyI, Keycode::J => KeyCode::KeyJ, Keycode::K => KeyCode::KeyK, Keycode::L => KeyCode::KeyL, Keycode::M => KeyCode::KeyM, Keycode::N => KeyCode::KeyN, Keycode::O => KeyCode::KeyO, Keycode::P => KeyCode::KeyP, Keycode::Q => KeyCode::KeyQ, Keycode::R => KeyCode::KeyR, Keycode::S => KeyCode::KeyS, Keycode::T => KeyCode::KeyT, Keycode::U => KeyCode::KeyU, Keycode::V => KeyCode::KeyV, Keycode::W => KeyCode::KeyW, Keycode::X => KeyCode::KeyX, Keycode::Y => KeyCode::KeyY, Keycode::Z => KeyCode::KeyZ, Keycode::Keycode0 => KeyCode::Digit0, Keycode::Keycode1 => KeyCode::Digit1, Keycode::Keycode2 => KeyCode::Digit2, Keycode::Keycode3 => KeyCode::Digit3, Keycode::Keycode4 => KeyCode::Digit4, Keycode::Keycode5 => KeyCode::Digit5, Keycode::Keycode6 => KeyCode::Digit6, Keycode::Keycode7 => KeyCode::Digit7, Keycode::Keycode8 => KeyCode::Digit8, Keycode::Keycode9 => KeyCode::Digit9, Keycode::Numpad0 => KeyCode::Numpad0, Keycode::Numpad1 => KeyCode::Numpad1, Keycode::Numpad2 => KeyCode::Numpad2, Keycode::Numpad3 => KeyCode::Numpad3, Keycode::Numpad4 => KeyCode::Numpad4, Keycode::Numpad5 => KeyCode::Numpad5, Keycode::Numpad6 => KeyCode::Numpad6, Keycode::Numpad7 => KeyCode::Numpad7, Keycode::Numpad8 => KeyCode::Numpad8, Keycode::Numpad9 => KeyCode::Numpad9, Keycode::NumpadAdd => KeyCode::NumpadAdd, Keycode::NumpadSubtract => KeyCode::NumpadSubtract, Keycode::NumpadMultiply => KeyCode::NumpadMultiply, Keycode::NumpadDivide => KeyCode::NumpadDivide, Keycode::NumpadEnter => KeyCode::NumpadEnter, Keycode::NumpadEquals => KeyCode::NumpadEqual, Keycode::NumpadComma => KeyCode::NumpadComma, Keycode::NumpadDot => KeyCode::NumpadDecimal, Keycode::NumLock => KeyCode::NumLock, Keycode::DpadLeft => KeyCode::ArrowLeft, Keycode::DpadRight => KeyCode::ArrowRight, Keycode::DpadUp => KeyCode::ArrowUp, Keycode::DpadDown => KeyCode::ArrowDown, Keycode::F1 => KeyCode::F1, Keycode::F2 => KeyCode::F2, Keycode::F3 => KeyCode::F3, Keycode::F4 => KeyCode::F4, Keycode::F5 => KeyCode::F5, Keycode::F6 => KeyCode::F6, Keycode::F7 => KeyCode::F7, Keycode::F8 => KeyCode::F8, Keycode::F9 => KeyCode::F9, Keycode::F10 => KeyCode::F10, Keycode::F11 => KeyCode::F11, Keycode::F12 => KeyCode::F12, Keycode::Space => KeyCode::Space, Keycode::Escape => KeyCode::Escape, Keycode::Enter => KeyCode::Enter, // not on the Numpad Keycode::Tab => KeyCode::Tab, Keycode::PageUp => KeyCode::PageUp, Keycode::PageDown => KeyCode::PageDown, Keycode::MoveHome => KeyCode::Home, Keycode::MoveEnd => KeyCode::End, Keycode::Insert => KeyCode::Insert, Keycode::Del => KeyCode::Backspace, // Backspace (above Enter) Keycode::ForwardDel => KeyCode::Delete, // Delete (below Insert) Keycode::Copy => KeyCode::Copy, Keycode::Paste => KeyCode::Paste, Keycode::Cut => KeyCode::Cut, Keycode::VolumeUp => KeyCode::AudioVolumeUp, Keycode::VolumeDown => KeyCode::AudioVolumeDown, Keycode::VolumeMute => KeyCode::AudioVolumeMute, //Keycode::Mute => None, // Microphone mute Keycode::MediaPlayPause => KeyCode::MediaPlayPause, Keycode::MediaStop => KeyCode::MediaStop, Keycode::MediaNext => KeyCode::MediaTrackNext, Keycode::MediaPrevious => KeyCode::MediaTrackPrevious, Keycode::Plus => KeyCode::Equal, Keycode::Minus => KeyCode::Minus, // Winit doesn't differentiate both '+' and '=', considering they are usually // on the same physical key Keycode::Equals => KeyCode::Equal, Keycode::Semicolon => KeyCode::Semicolon, Keycode::Slash => KeyCode::Slash, Keycode::Backslash => KeyCode::Backslash, Keycode::Comma => KeyCode::Comma, Keycode::Period => KeyCode::Period, Keycode::Apostrophe => KeyCode::Quote, Keycode::Grave => KeyCode::Backquote, // Winit doesn't expose a SysRq code, so map to PrintScreen since it's // usually the same physical key Keycode::Sysrq => KeyCode::PrintScreen, // These are usually the same (Pause/Break) Keycode::Break => KeyCode::Pause, // These are exactly the same Keycode::ScrollLock => KeyCode::ScrollLock, Keycode::Yen => KeyCode::IntlYen, Keycode::Kana => KeyCode::Lang1, Keycode::KatakanaHiragana => KeyCode::KanaMode, Keycode::CtrlLeft => KeyCode::ControlLeft, Keycode::CtrlRight => KeyCode::ControlRight, Keycode::ShiftLeft => KeyCode::ShiftLeft, Keycode::ShiftRight => KeyCode::ShiftRight, Keycode::AltLeft => KeyCode::AltLeft, Keycode::AltRight => KeyCode::AltRight, Keycode::MetaLeft => KeyCode::SuperLeft, Keycode::MetaRight => KeyCode::SuperRight, Keycode::LeftBracket => KeyCode::BracketLeft, Keycode::RightBracket => KeyCode::BracketRight, Keycode::Power => KeyCode::Power, Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep? Keycode::Wakeup => KeyCode::WakeUp, keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())), }) } /// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent /// /// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and /// uses that to try and map the `key_code` + `meta_state` to a unicode /// character or a dead key that can be combined with the next key press. pub fn character_map_and_combine_key( app: &AndroidApp, key_event: &KeyEvent<'_>, combining_accent: &mut Option, ) -> Option { let device_id = key_event.device_id(); let key_map = match app.device_key_character_map(device_id) { Ok(key_map) => key_map, Err(err) => { log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}"); return None; } }; match key_map.get(key_event.key_code(), key_event.meta_state()) { Ok(KeyMapChar::Unicode(unicode)) => { // Only do dead key combining on key down if key_event.action() == KeyAction::Down { let combined_unicode = if let Some(accent) = combining_accent { match key_map.get_dead_char(*accent, unicode) { Ok(Some(key)) => Some(key), Ok(None) => None, Err(err) => { log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}"); None } } } else { Some(unicode) }; *combining_accent = None; combined_unicode.map(KeyMapChar::Unicode) } else { Some(KeyMapChar::Unicode(unicode)) } } Ok(KeyMapChar::CombiningAccent(accent)) => { if key_event.action() == KeyAction::Down { *combining_accent = Some(accent); } Some(KeyMapChar::CombiningAccent(accent)) } Ok(KeyMapChar::None) => { // Leave any combining_accent state in tact (seems to match how other // Android apps work) None } Err(err) => { log::warn!("KeyEvent: Failed to get key map character: {err:?}"); *combining_accent = None; None } } } pub fn to_logical(key_char: Option, keycode: Keycode) -> Key { use android_activity::input::Keycode::*; let native = NativeKey::Android(keycode.into()); match key_char { Some(KeyMapChar::Unicode(c)) => Key::Character(smol_str::SmolStr::from_iter([c])), Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)), None | Some(KeyMapChar::None) => match keycode { // Using `BrowserHome` instead of `GoHome` according to // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values Home => Key::Named(NamedKey::BrowserHome), Back => Key::Named(NamedKey::BrowserBack), Call => Key::Named(NamedKey::Call), Endcall => Key::Named(NamedKey::EndCall), //------------------------------------------------------------------------------- // These should be redundant because they should have already been matched // as `KeyMapChar::Unicode`, but also matched here as a fallback Keycode0 => Key::Character("0".into()), Keycode1 => Key::Character("1".into()), Keycode2 => Key::Character("2".into()), Keycode3 => Key::Character("3".into()), Keycode4 => Key::Character("4".into()), Keycode5 => Key::Character("5".into()), Keycode6 => Key::Character("6".into()), Keycode7 => Key::Character("7".into()), Keycode8 => Key::Character("8".into()), Keycode9 => Key::Character("9".into()), Star => Key::Character("*".into()), Pound => Key::Character("#".into()), A => Key::Character("a".into()), B => Key::Character("b".into()), C => Key::Character("c".into()), D => Key::Character("d".into()), E => Key::Character("e".into()), F => Key::Character("f".into()), G => Key::Character("g".into()), H => Key::Character("h".into()), I => Key::Character("i".into()), J => Key::Character("j".into()), K => Key::Character("k".into()), L => Key::Character("l".into()), M => Key::Character("m".into()), N => Key::Character("n".into()), O => Key::Character("o".into()), P => Key::Character("p".into()), Q => Key::Character("q".into()), R => Key::Character("r".into()), S => Key::Character("s".into()), T => Key::Character("t".into()), U => Key::Character("u".into()), V => Key::Character("v".into()), W => Key::Character("w".into()), X => Key::Character("x".into()), Y => Key::Character("y".into()), Z => Key::Character("z".into()), Comma => Key::Character(",".into()), Period => Key::Character(".".into()), Grave => Key::Character("`".into()), Minus => Key::Character("-".into()), Equals => Key::Character("=".into()), LeftBracket => Key::Character("[".into()), RightBracket => Key::Character("]".into()), Backslash => Key::Character("\\".into()), Semicolon => Key::Character(";".into()), Apostrophe => Key::Character("'".into()), Slash => Key::Character("/".into()), At => Key::Character("@".into()), Plus => Key::Character("+".into()), //------------------------------------------------------------------------------- DpadUp => Key::Named(NamedKey::ArrowUp), DpadDown => Key::Named(NamedKey::ArrowDown), DpadLeft => Key::Named(NamedKey::ArrowLeft), DpadRight => Key::Named(NamedKey::ArrowRight), DpadCenter => Key::Named(NamedKey::Enter), VolumeUp => Key::Named(NamedKey::AudioVolumeUp), VolumeDown => Key::Named(NamedKey::AudioVolumeDown), Power => Key::Named(NamedKey::Power), Camera => Key::Named(NamedKey::Camera), Clear => Key::Named(NamedKey::Clear), AltLeft => Key::Named(NamedKey::Alt), AltRight => Key::Named(NamedKey::Alt), ShiftLeft => Key::Named(NamedKey::Shift), ShiftRight => Key::Named(NamedKey::Shift), Tab => Key::Named(NamedKey::Tab), Space => Key::Named(NamedKey::Space), Sym => Key::Named(NamedKey::Symbol), Explorer => Key::Named(NamedKey::LaunchWebBrowser), Envelope => Key::Named(NamedKey::LaunchMail), Enter => Key::Named(NamedKey::Enter), Del => Key::Named(NamedKey::Backspace), // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM Num => Key::Named(NamedKey::Alt), Headsethook => Key::Named(NamedKey::HeadsetHook), Focus => Key::Named(NamedKey::CameraFocus), Notification => Key::Named(NamedKey::Notification), Search => Key::Named(NamedKey::BrowserSearch), MediaPlayPause => Key::Named(NamedKey::MediaPlayPause), MediaStop => Key::Named(NamedKey::MediaStop), MediaNext => Key::Named(NamedKey::MediaTrackNext), MediaPrevious => Key::Named(NamedKey::MediaTrackPrevious), MediaRewind => Key::Named(NamedKey::MediaRewind), MediaFastForward => Key::Named(NamedKey::MediaFastForward), Mute => Key::Named(NamedKey::MicrophoneVolumeMute), PageUp => Key::Named(NamedKey::PageUp), PageDown => Key::Named(NamedKey::PageDown), Escape => Key::Named(NamedKey::Escape), ForwardDel => Key::Named(NamedKey::Delete), CtrlLeft => Key::Named(NamedKey::Control), CtrlRight => Key::Named(NamedKey::Control), CapsLock => Key::Named(NamedKey::CapsLock), ScrollLock => Key::Named(NamedKey::ScrollLock), MetaLeft => Key::Named(NamedKey::Super), MetaRight => Key::Named(NamedKey::Super), Function => Key::Named(NamedKey::Fn), Sysrq => Key::Named(NamedKey::PrintScreen), Break => Key::Named(NamedKey::Pause), MoveHome => Key::Named(NamedKey::Home), MoveEnd => Key::Named(NamedKey::End), Insert => Key::Named(NamedKey::Insert), Forward => Key::Named(NamedKey::BrowserForward), MediaPlay => Key::Named(NamedKey::MediaPlay), MediaPause => Key::Named(NamedKey::MediaPause), MediaClose => Key::Named(NamedKey::MediaClose), MediaEject => Key::Named(NamedKey::Eject), MediaRecord => Key::Named(NamedKey::MediaRecord), F1 => Key::Named(NamedKey::F1), F2 => Key::Named(NamedKey::F2), F3 => Key::Named(NamedKey::F3), F4 => Key::Named(NamedKey::F4), F5 => Key::Named(NamedKey::F5), F6 => Key::Named(NamedKey::F6), F7 => Key::Named(NamedKey::F7), F8 => Key::Named(NamedKey::F8), F9 => Key::Named(NamedKey::F9), F10 => Key::Named(NamedKey::F10), F11 => Key::Named(NamedKey::F11), F12 => Key::Named(NamedKey::F12), NumLock => Key::Named(NamedKey::NumLock), Numpad0 => Key::Character("0".into()), Numpad1 => Key::Character("1".into()), Numpad2 => Key::Character("2".into()), Numpad3 => Key::Character("3".into()), Numpad4 => Key::Character("4".into()), Numpad5 => Key::Character("5".into()), Numpad6 => Key::Character("6".into()), Numpad7 => Key::Character("7".into()), Numpad8 => Key::Character("8".into()), Numpad9 => Key::Character("9".into()), NumpadDivide => Key::Character("/".into()), NumpadMultiply => Key::Character("*".into()), NumpadSubtract => Key::Character("-".into()), NumpadAdd => Key::Character("+".into()), NumpadDot => Key::Character(".".into()), NumpadComma => Key::Character(",".into()), NumpadEnter => Key::Named(NamedKey::Enter), NumpadEquals => Key::Character("=".into()), NumpadLeftParen => Key::Character("(".into()), NumpadRightParen => Key::Character(")".into()), VolumeMute => Key::Named(NamedKey::AudioVolumeMute), Info => Key::Named(NamedKey::Info), ChannelUp => Key::Named(NamedKey::ChannelUp), ChannelDown => Key::Named(NamedKey::ChannelDown), ZoomIn => Key::Named(NamedKey::ZoomIn), ZoomOut => Key::Named(NamedKey::ZoomOut), Tv => Key::Named(NamedKey::TV), Guide => Key::Named(NamedKey::Guide), Dvr => Key::Named(NamedKey::DVR), Bookmark => Key::Named(NamedKey::BrowserFavorites), Captions => Key::Named(NamedKey::ClosedCaptionToggle), Settings => Key::Named(NamedKey::Settings), TvPower => Key::Named(NamedKey::TVPower), TvInput => Key::Named(NamedKey::TVInput), StbPower => Key::Named(NamedKey::STBPower), StbInput => Key::Named(NamedKey::STBInput), AvrPower => Key::Named(NamedKey::AVRPower), AvrInput => Key::Named(NamedKey::AVRInput), ProgRed => Key::Named(NamedKey::ColorF0Red), ProgGreen => Key::Named(NamedKey::ColorF1Green), ProgYellow => Key::Named(NamedKey::ColorF2Yellow), ProgBlue => Key::Named(NamedKey::ColorF3Blue), AppSwitch => Key::Named(NamedKey::AppSwitch), LanguageSwitch => Key::Named(NamedKey::GroupNext), MannerMode => Key::Named(NamedKey::MannerMode), Keycode3dMode => Key::Named(NamedKey::TV3DMode), Contacts => Key::Named(NamedKey::LaunchContacts), Calendar => Key::Named(NamedKey::LaunchCalendar), Music => Key::Named(NamedKey::LaunchMusicPlayer), Calculator => Key::Named(NamedKey::LaunchApplication2), ZenkakuHankaku => Key::Named(NamedKey::ZenkakuHankaku), Eisu => Key::Named(NamedKey::Eisu), Muhenkan => Key::Named(NamedKey::NonConvert), Henkan => Key::Named(NamedKey::Convert), KatakanaHiragana => Key::Named(NamedKey::HiraganaKatakana), Kana => Key::Named(NamedKey::KanjiMode), BrightnessDown => Key::Named(NamedKey::BrightnessDown), BrightnessUp => Key::Named(NamedKey::BrightnessUp), MediaAudioTrack => Key::Named(NamedKey::MediaAudioTrack), Sleep => Key::Named(NamedKey::Standby), Wakeup => Key::Named(NamedKey::WakeUp), Pairing => Key::Named(NamedKey::Pairing), MediaTopMenu => Key::Named(NamedKey::MediaTopMenu), LastChannel => Key::Named(NamedKey::MediaLast), TvDataService => Key::Named(NamedKey::TVDataService), VoiceAssist => Key::Named(NamedKey::VoiceDial), TvRadioService => Key::Named(NamedKey::TVRadioService), TvTeletext => Key::Named(NamedKey::Teletext), TvNumberEntry => Key::Named(NamedKey::TVNumberEntry), TvTerrestrialAnalog => Key::Named(NamedKey::TVTerrestrialAnalog), TvTerrestrialDigital => Key::Named(NamedKey::TVTerrestrialDigital), TvSatellite => Key::Named(NamedKey::TVSatellite), TvSatelliteBs => Key::Named(NamedKey::TVSatelliteBS), TvSatelliteCs => Key::Named(NamedKey::TVSatelliteCS), TvSatelliteService => Key::Named(NamedKey::TVSatelliteToggle), TvNetwork => Key::Named(NamedKey::TVNetwork), TvAntennaCable => Key::Named(NamedKey::TVAntennaCable), TvInputHdmi1 => Key::Named(NamedKey::TVInputHDMI1), TvInputHdmi2 => Key::Named(NamedKey::TVInputHDMI2), TvInputHdmi3 => Key::Named(NamedKey::TVInputHDMI3), TvInputHdmi4 => Key::Named(NamedKey::TVInputHDMI4), TvInputComposite1 => Key::Named(NamedKey::TVInputComposite1), TvInputComposite2 => Key::Named(NamedKey::TVInputComposite2), TvInputComponent1 => Key::Named(NamedKey::TVInputComponent1), TvInputComponent2 => Key::Named(NamedKey::TVInputComponent2), TvInputVga1 => Key::Named(NamedKey::TVInputVGA1), TvAudioDescription => Key::Named(NamedKey::TVAudioDescription), TvAudioDescriptionMixUp => Key::Named(NamedKey::TVAudioDescriptionMixUp), TvAudioDescriptionMixDown => Key::Named(NamedKey::TVAudioDescriptionMixDown), TvZoomMode => Key::Named(NamedKey::ZoomToggle), TvContentsMenu => Key::Named(NamedKey::TVContentsMenu), TvMediaContextMenu => Key::Named(NamedKey::TVMediaContext), TvTimerProgramming => Key::Named(NamedKey::TVTimer), Help => Key::Named(NamedKey::Help), NavigatePrevious => Key::Named(NamedKey::NavigatePrevious), NavigateNext => Key::Named(NamedKey::NavigateNext), NavigateIn => Key::Named(NamedKey::NavigateIn), NavigateOut => Key::Named(NamedKey::NavigateOut), MediaSkipForward => Key::Named(NamedKey::MediaSkipForward), MediaSkipBackward => Key::Named(NamedKey::MediaSkipBackward), MediaStepForward => Key::Named(NamedKey::MediaStepForward), MediaStepBackward => Key::Named(NamedKey::MediaStepBackward), Cut => Key::Named(NamedKey::Cut), Copy => Key::Named(NamedKey::Copy), Paste => Key::Named(NamedKey::Paste), Refresh => Key::Named(NamedKey::BrowserRefresh), // ----------------------------------------------------------------- // Keycodes that don't have a logical Key mapping // ----------------------------------------------------------------- Unknown => Key::Unidentified(native), // Can be added on demand SoftLeft => Key::Unidentified(native), SoftRight => Key::Unidentified(native), Menu => Key::Unidentified(native), 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), // ----------------------------------------------------------------- Window => Key::Unidentified(native), 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), Yen => Key::Unidentified(native), Ro => Key::Unidentified(native), Assist => Key::Unidentified(native), Keycode11 => Key::Unidentified(native), Keycode12 => Key::Unidentified(native), 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), SoftSleep => Key::Unidentified(native), SystemNavigationUp => Key::Unidentified(native), SystemNavigationDown => Key::Unidentified(native), SystemNavigationLeft => Key::Unidentified(native), SystemNavigationRight => Key::Unidentified(native), AllApps => Key::Unidentified(native), ThumbsUp => Key::Unidentified(native), ThumbsDown => Key::Unidentified(native), ProfileSwitch => Key::Unidentified(native), // It's always possible that new versions of Android could introduce // key codes we can't know about at compile time. _ => Key::Unidentified(native), }, } } pub fn to_location(keycode: Keycode) -> KeyLocation { use android_activity::input::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, } } winit-0.29.15/src/platform_impl/android/mod.rs000064400000000000000000001106021046102023000173350ustar 00000000000000#![cfg(android_platform)] use std::{ cell::Cell, collections::VecDeque, hash::Hash, sync::{ atomic::{AtomicBool, Ordering}, mpsc, Arc, Mutex, RwLock, }, time::{Duration, Instant}, }; use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; use android_activity::{ AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, }; use once_cell::sync::Lazy; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, event::{self, Force, InnerSizeWriter, StartCause}, event_loop::{self, ControlFlow, DeviceEvents, EventLoopWindowTarget as RootELW}, platform::pump_events::PumpStatus, window::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }, }; use crate::{error::EventLoopError, platform_impl::Fullscreen}; mod keycodes; static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| { b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) }) } struct PeekableReceiver { recv: mpsc::Receiver, first: Option, } impl PeekableReceiver { pub fn from_recv(recv: mpsc::Receiver) -> Self { Self { recv, first: None } } pub fn has_incoming(&mut self) -> bool { if self.first.is_some() { return true; } match self.recv.try_recv() { Ok(v) => { self.first = Some(v); true } Err(mpsc::TryRecvError::Empty) => false, Err(mpsc::TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); false } } } pub fn try_recv(&mut self) -> Result { if let Some(first) = self.first.take() { return Ok(first); } self.recv.try_recv() } } #[derive(Clone)] struct SharedFlagSetter { flag: Arc, } impl SharedFlagSetter { pub fn set(&self) -> bool { self.flag .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) .is_ok() } } struct SharedFlag { flag: Arc, } // Used for queuing redraws from arbitrary threads. We don't care how many // times a redraw is requested (so don't actually need to queue any data, // we just need to know at the start of a main loop iteration if a redraw // was queued and be able to read and clear the state atomically) impl SharedFlag { pub fn new() -> Self { Self { flag: Arc::new(AtomicBool::new(false)), } } pub fn setter(&self) -> SharedFlagSetter { SharedFlagSetter { flag: self.flag.clone(), } } pub fn get_and_reset(&self) -> bool { self.flag.swap(false, std::sync::atomic::Ordering::AcqRel) } } #[derive(Clone)] pub struct RedrawRequester { flag: SharedFlagSetter, waker: AndroidAppWaker, } impl RedrawRequester { fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { RedrawRequester { flag: flag.setter(), waker, } } pub fn request_redraw(&self) { if self.flag.set() { // Only explicitly try to wake up the main loop when the flag // value changes self.waker.wake(); } } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra {} pub struct EventLoop { android_app: AndroidApp, window_target: event_loop::EventLoopWindowTarget, redraw_flag: SharedFlag, user_events_sender: mpsc::Sender, user_events_receiver: PeekableReceiver, //must wake looper whenever something gets sent loop_running: bool, // Dispatched `NewEvents` running: bool, pending_redraw: bool, cause: StartCause, ignore_volume_keys: bool, combining_accent: Option, } #[derive(Debug, Clone, PartialEq)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) android_app: Option, pub(crate) ignore_volume_keys: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { android_app: Default::default(), ignore_volume_keys: true, } } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { let (user_events_sender, user_events_receiver) = mpsc::channel(); let android_app = attributes.android_app.as_ref().expect("An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on Android"); let redraw_flag = SharedFlag::new(); Ok(Self { android_app: android_app.clone(), window_target: event_loop::EventLoopWindowTarget { p: EventLoopWindowTarget { app: android_app.clone(), control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(false), redraw_requester: RedrawRequester::new( &redraw_flag, android_app.create_waker(), ), _marker: std::marker::PhantomData, }, _marker: std::marker::PhantomData, }, redraw_flag, user_events_sender, user_events_receiver: PeekableReceiver::from_recv(user_events_receiver), loop_running: false, running: false, pending_redraw: false, cause: StartCause::Init, ignore_volume_keys: attributes.ignore_volume_keys, combining_accent: None, }) } fn single_iteration(&mut self, main_event: Option>, callback: &mut F) where F: FnMut(event::Event, &RootELW), { trace!("Mainloop iteration"); let cause = self.cause; let mut pending_redraw = self.pending_redraw; let mut resized = false; callback(event::Event::NewEvents(cause), self.window_target()); if let Some(event) = main_event { trace!("Handling main event {:?}", event); match event { MainEvent::InitWindow { .. } => { callback(event::Event::Resumed, self.window_target()); } MainEvent::TerminateWindow { .. } => { callback(event::Event::Suspended, self.window_target()); } MainEvent::WindowResized { .. } => resized = true, MainEvent::RedrawNeeded { .. } => pending_redraw = true, MainEvent::ContentRectChanged { .. } => { warn!("TODO: find a way to notify application of content rect change"); } MainEvent::GainedFocus => { *HAS_FOCUS.write().unwrap() = true; callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Focused(true), }, self.window_target(), ); } MainEvent::LostFocus => { *HAS_FOCUS.write().unwrap() = false; callback( event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Focused(false), }, self.window_target(), ); } MainEvent::ConfigChanged { .. } => { let monitor = MonitorHandle::new(self.android_app.clone()); let old_scale_factor = monitor.scale_factor(); let scale_factor = monitor.scale_factor(); if (scale_factor - old_scale_factor).abs() < f64::EPSILON { let new_inner_size = Arc::new(Mutex::new( MonitorHandle::new(self.android_app.clone()).size(), )); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::ScaleFactorChanged { inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &new_inner_size, )), scale_factor, }, }; callback(event, self.window_target()); } } MainEvent::LowMemory => { callback(event::Event::MemoryWarning, self.window_target()); } MainEvent::Start => { // XXX: how to forward this state to applications? warn!("TODO: forward onStart notification to application"); } MainEvent::Resume { .. } => { debug!("App Resumed - is running"); self.running = true; } MainEvent::SaveState { .. } => { // XXX: how to forward this state to applications? // XXX: also how do we expose state restoration to apps? warn!("TODO: forward saveState notification to application"); } MainEvent::Pause => { debug!("App Paused - stopped running"); self.running = false; } MainEvent::Stop => { // XXX: how to forward this state to applications? warn!("TODO: forward onStop notification to application"); } MainEvent::Destroy => { // XXX: maybe exit mainloop to drop things before being // killed by the OS? warn!("TODO: forward onDestroy notification to application"); } MainEvent::InsetsChanged { .. } => { // XXX: how to forward this state to applications? warn!("TODO: handle Android InsetsChanged notification"); } unknown => { trace!("Unknown MainEvent {unknown:?} (ignored)"); } } } else { trace!("No main event to handle"); } // temporarily decouple `android_app` from `self` so we aren't holding // a borrow of `self` while iterating let android_app = self.android_app.clone(); // Process input events match android_app.input_events_iter() { Ok(mut input_iter) => loop { let read_event = input_iter.next(|event| self.handle_input_event(&android_app, event, callback)); if !read_event { break; } }, Err(err) => { log::warn!("Failed to get input events iterator: {err:?}"); } } // Empty the user event buffer { while let Ok(event) = self.user_events_receiver.try_recv() { callback(crate::event::Event::UserEvent(event), self.window_target()); } } if self.running { if resized { let size = if let Some(native_window) = self.android_app.native_window().as_ref() { let width = native_window.width() as _; let height = native_window.height() as _; PhysicalSize::new(width, height) } else { PhysicalSize::new(0, 0) }; let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; callback(event, self.window_target()); } pending_redraw |= self.redraw_flag.get_and_reset(); if pending_redraw { pending_redraw = false; let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::RedrawRequested, }; callback(event, self.window_target()); } } // This is always the last event we dispatch before poll again callback(event::Event::AboutToWait, self.window_target()); self.pending_redraw = pending_redraw; } fn handle_input_event( &mut self, android_app: &AndroidApp, event: &InputEvent<'_>, callback: &mut F, ) -> InputStatus where F: FnMut(event::Event, &RootELW), { let mut input_status = InputStatus::Handled; match event { InputEvent::MotionEvent(motion_event) => { let window_id = window::WindowId(WindowId); let device_id = event::DeviceId(DeviceId(motion_event.device_id())); 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), _ => { 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 _, }; trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}"); let event = event::Event::WindowEvent { window_id, event: event::WindowEvent::Touch(event::Touch { device_id, phase, location, id: pointer.pointer_id() as u64, force: Some(Force::Normalized(pointer.pressure() as f64)), }), }; callback(event, self.window_target()); } } } InputEvent::KeyEvent(key) => { match key.key_code() { // Flag keys related to volume as unhandled. While winit does not have a way for applications // to configure what keys to flag as handled, this appears to be a good default until winit // can be configured. Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => { if self.ignore_volume_keys { input_status = InputStatus::Unhandled } } keycode => { let state = match key.action() { KeyAction::Down => event::ElementState::Pressed, KeyAction::Up => event::ElementState::Released, _ => event::ElementState::Released, }; let key_char = keycodes::character_map_and_combine_key( android_app, key, &mut self.combining_accent, ); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::KeyboardInput { device_id: event::DeviceId(DeviceId(key.device_id())), event: event::KeyEvent { state, physical_key: keycodes::to_physical_key(keycode), logical_key: keycodes::to_logical(key_char, keycode), location: keycodes::to_location(keycode), repeat: key.repeat_count() > 0, text: None, platform_specific: KeyEventExtra {}, }, is_synthetic: false, }, }; callback(event, self.window_target()); } } } _ => { warn!("Unknown android_activity input event {event:?}") } } input_status } pub fn run(mut self, event_handler: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::EventLoopWindowTarget), { self.run_on_demand(event_handler) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::EventLoopWindowTarget), { if self.loop_running { return Err(EventLoopError::AlreadyRunning); } loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); } _ => { continue; } } } } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(event::Event, &RootELW), { if !self.loop_running { self.loop_running = true; // Reset the internal state for the loop as we start running to // ensure consistent behaviour in case the loop runs and exits more // than once self.pending_redraw = false; self.cause = StartCause::Init; // run the initial loop iteration self.single_iteration(None, &mut callback); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if self.exiting() { self.loop_running = false; callback(event::Event::LoopExiting, self.window_target()); PumpStatus::Exit(0) } else { PumpStatus::Continue } } fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(event::Event, &RootELW), { let start = Instant::now(); self.pending_redraw |= self.redraw_flag.get_and_reset(); timeout = if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) { // If we already have work to do then we don't want to block on the next poll Some(Duration::ZERO) } else { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) } }; min_timeout(control_flow_timeout, timeout) }; let app = self.android_app.clone(); // Don't borrow self as part of poll expression app.poll_events(timeout, |poll_event| { let mut main_event = None; match poll_event { android_activity::PollEvent::Wake => { // In the X11 backend it's noted that too many false-positive wake ups // would cause the event loop to run continuously. They handle this by re-checking // for pending events (assuming they cover all valid reasons for a wake up). // // For now, user_events and redraw_requests are the only reasons to expect // a wake up here so we can ignore the wake up if there are no events/requests. // We also ignore wake ups while suspended. self.pending_redraw |= self.redraw_flag.get_and_reset(); if !self.running || (!self.pending_redraw && !self.user_events_receiver.has_incoming()) { return; } } android_activity::PollEvent::Timeout => {} android_activity::PollEvent::Main(event) => { main_event = Some(event); } unknown_event => { warn!("Unknown poll event {unknown_event:?} (ignored)"); } } self.cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None, }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline), } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline, } } } }; self.single_iteration(main_event, &mut callback); }); } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { &self.window_target } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), waker: self.android_app.create_waker(), } } fn control_flow(&self) -> ControlFlow { self.window_target.p.control_flow() } fn exiting(&self) -> bool { self.window_target.p.exiting() } } pub struct EventLoopProxy { user_events_sender: mpsc::Sender, waker: AndroidAppWaker, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), waker: self.waker.clone(), } } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { self.user_events_sender .send(event) .map_err(|err| event_loop::EventLoopClosed(err.0))?; self.waker.wake(); Ok(()) } } pub struct EventLoopWindowTarget { app: AndroidApp, control_flow: Cell, exit: Cell, redraw_requester: RedrawRequester, _marker: std::marker::PhantomData, } impl EventLoopWindowTarget { pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle::new(self.app.clone())); v } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[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(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(true) } pub(crate) fn clear_exit(&self) { self.exit.set(false) } pub(crate) fn exiting(&self) -> bool { self.exit.get() } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub(crate) struct WindowId; impl WindowId { pub const fn dummy() -> Self { WindowId } } impl From for u64 { fn from(_: WindowId) -> Self { 0 } } impl From for WindowId { fn from(_: u64) -> Self { Self } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId(i32); impl DeviceId { pub const fn dummy() -> Self { DeviceId(0) } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowBuilderAttributes; pub(crate) struct Window { app: AndroidApp, redraw_requester: RedrawRequester, } impl Window { pub(crate) fn new( el: &EventLoopWindowTarget, _window_attrs: window::WindowAttributes, _: PlatformSpecificWindowBuilderAttributes, ) -> Result { // FIXME this ignores requested window attributes Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone(), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } pub fn id(&self) -> WindowId { WindowId } pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle::new(self.app.clone())); v } pub fn current_monitor(&self) -> Option { Some(MonitorHandle::new(self.app.clone())) } pub fn scale_factor(&self) -> f64 { MonitorHandle::new(self.app.clone()).scale_factor() } pub fn request_redraw(&self) { self.redraw_requester.request_redraw() } pub fn pre_present_notify(&self) {} 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 request_inner_size(&self, _size: Size) -> Option> { Some(self.inner_size()) } pub fn outer_size(&self) -> PhysicalSize { MonitorHandle::new(self.app.clone()).size() } pub fn set_min_inner_size(&self, _: Option) {} pub fn set_max_inner_size(&self, _: Option) {} pub fn resize_increments(&self) -> Option> { None } pub fn set_resize_increments(&self, _increments: Option) {} pub fn set_title(&self, _title: &str) {} pub fn set_transparent(&self, _transparent: bool) {} pub fn set_blur(&self, _blur: bool) {} pub fn set_visible(&self, _visibility: bool) {} pub fn is_visible(&self) -> Option { None } pub fn set_resizable(&self, _resizeable: bool) {} pub fn is_resizable(&self) -> bool { false } pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } pub fn set_minimized(&self, _minimized: bool) {} pub fn is_minimized(&self) -> Option { None } pub fn set_maximized(&self, _maximized: bool) {} pub fn is_maximized(&self) -> bool { 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 is_decorated(&self) -> bool { true } pub fn set_window_level(&self, _level: WindowLevel) {} pub fn set_window_icon(&self, _window_icon: Option) {} pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} pub fn set_ime_allowed(&self, _allowed: bool) {} pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} pub fn focus_window(&self) {} 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, _: CursorGrabMode) -> 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(), )) } #[inline] pub fn show_window_menu(&self, _position: Position) {} pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), )) } #[cfg(feature = "rwh_04")] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { use rwh_04::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } 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."); } } #[cfg(feature = "rwh_05")] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { use rwh_05::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } 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."); } } #[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")] // Allow the usage of HasRawWindowHandle inside this function #[allow(deprecated)] pub fn raw_window_handle_rwh_06(&self) -> Result { use rwh_06::HasRawWindowHandle; if let Some(native_window) = self.app.native_window().as_ref() { native_window.raw_window_handle() } else { log::error!("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."); 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) -> ConfigurationRef { self.app.config() } pub fn content_rect(&self) -> Rect { self.app.content_rect() } pub fn set_theme(&self, _theme: Option) {} pub fn theme(&self) -> Option { None } pub fn set_content_protected(&self, _protected: bool) {} pub fn has_focus(&self) -> bool { *HAS_FOCUS.read().unwrap() } pub fn title(&self) -> String { String::new() } pub fn reset_dead_keys(&self) {} } #[derive(Default, Clone, Debug)] pub struct OsError; use std::fmt::{self, Display, Formatter}; impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(fmt, "Android OS Error") } } pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct MonitorHandle { app: AndroidApp, } 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 { std::cmp::Ordering::Equal } } impl MonitorHandle { pub(crate) fn new(app: AndroidApp) -> Self { Self { app } } pub fn name(&self) -> Option { Some("Android Device".to_owned()) } pub fn size(&self) -> PhysicalSize { if let Some(native_window) = self.app.native_window() { PhysicalSize::new(native_window.width() as _, native_window.height() as _) } else { PhysicalSize::new(0, 0) } } pub fn position(&self) -> PhysicalPosition { (0, 0).into() } pub fn scale_factor(&self) -> f64 { self.app .config() .density() .map(|dpi| dpi as f64 / 160.0) .unwrap_or(1.0) } pub fn refresh_rate_millihertz(&self) -> Option { // FIXME no way to get real refresh rate for now. None } pub fn video_modes(&self) -> impl Iterator { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) std::iter::once(VideoMode { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct VideoMode { size: (u32, u32), bit_depth: u16, refresh_rate_millihertz: u32, 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_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } winit-0.29.15/src/platform_impl/ios/app_state.rs000064400000000000000000001025351046102023000177160ustar 00000000000000#![deny(unused_results)] use std::{ cell::{RefCell, RefMut}, collections::HashSet, mem, os::raw::c_void, ptr, sync::{Arc, Mutex}, time::Instant, }; use core_foundation::base::CFRelease; use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, }; use icrate::Foundation::{ CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo, }; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{msg_send, sel}; use once_cell::sync::Lazy; use super::event_loop::{EventHandler, Never}; use super::uikit::UIView; use super::view::WinitUIWindow; use crate::{ dpi::PhysicalSize, event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event_loop::ControlFlow, window::WindowId as RootWindowId, }; macro_rules! bug { ($($msg:tt)*) => { panic!("winit iOS bug, file an issue: {}", format!($($msg)*)) }; } macro_rules! bug_assert { ($test:expr, $($msg:tt)*) => { assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*)) }; } #[derive(Debug)] pub enum EventWrapper { StaticEvent(Event), ScaleFactorChanged(ScaleFactorChanged), } #[derive(Debug)] pub struct ScaleFactorChanged { pub(super) window: Id, pub(super) suggested_size: PhysicalSize, pub(super) scale_factor: f64, } enum UserCallbackTransitionResult<'a> { Success { event_handler: Box, active_control_flow: ControlFlow, processing_redraws: bool, }, ReentrancyPrevented { queued_events: &'a mut Vec, }, } impl Event { fn is_redraw(&self) -> bool { matches!( self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } ) } } // 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, } pub(crate) struct AppState { // This should never be `None`, except for briefly during a state transition. app_state: Option, control_flow: ControlFlow, waker: EventLoopWaker, } impl AppState { pub(crate) fn get_mut(_mtm: MainThreadMarker) -> 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); let mut guard = unsafe { APP_STATE.borrow_mut() }; if guard.is_none() { #[inline(never)] #[cold] fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(unsafe { 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, }); } 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 has_terminated(&self) -> bool { matches!(self.state(), AppStateImpl::Terminated) } 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: self.control_flow, 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() || self.has_terminated() { 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) } 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<'_>, >(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() || self.has_terminated() { 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::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) } // Unlike on macOS, handle Poll to Poll transition here to call the waker (_, ControlFlow::Poll) => { self.set_state(AppStateImpl::PollFinished { waiting_event_handler, }); self.waker.start() } } } fn terminated_transition(&mut self) -> Box { match self.replace_state(AppStateImpl::Terminated) { AppStateImpl::ProcessingEvents { event_handler, .. } => event_handler, s => bug!("`LoopExiting` happened while not processing events {:?}", s), } } pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) { self.control_flow = control_flow; } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow } } pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id) { let mut this = AppState::get_mut(mtm); match this.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => return queued_windows.push(window.clone()), &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); window.makeKeyAndVisible(); } pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id) { let mut this = AppState::get_mut(mtm); 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, .. } => { let _ = 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") } } } pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box) { AppState::get_mut(mtm).will_launch_transition(queued_event_handler) } pub fn did_finish_launching(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), }; this.waker.start(); // have to drop RefMut because the window setup code below can trigger new events drop(this); for window in windows { // 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 initialzation // completed. This may result in incorrect visual appearance. // ``` let screen = window.screen(); let _: () = unsafe { msg_send![&window, setScreen: ptr::null::()] }; window.setScreen(&screen); let controller = window.rootViewController(); window.setRootViewController(None); window.setRootViewController(controller.as_deref()); window.makeKeyAndVisible(); } let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition(); let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( StartCause::Init, ))) .chain(events); handle_nonuser_events(mtm, 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 for window in windows { window.makeKeyAndVisible(); } } // AppState::did_finish_launching handles the special transition `Init` pub fn handle_wakeup_transition(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let wakeup_event = match this.wakeup_transition() { None => return, Some(wakeup_event) => wakeup_event, }; drop(this); handle_nonuser_event(mtm, wakeup_event) } pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) { handle_nonuser_events(mtm, std::iter::once(event)) } pub(crate) fn handle_nonuser_events>( mtm: MainThreadMarker, events: I, ) { let mut this = AppState::get_mut(mtm); if this.has_terminated() { return; } 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), }; drop(this); for wrapper in events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { log::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { log::warn!( "processing non `RedrawRequested` event after the main event loop: {:#?}", event ); } event_handler.handle_nonuser_event(event) } EventWrapper::ScaleFactorChanged(event) => { handle_hidpi_proxy(&mut event_handler, event) } } } loop { let mut this = AppState::get_mut(mtm); 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, } }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => { if !processing_redraws && event.is_redraw() { log::info!("processing `RedrawRequested` during the main event loop"); } else if processing_redraws && !event.is_redraw() { log::warn!( "processing non-`RedrawRequested` event after the main event loop: {:#?}", event ); } event_handler.handle_nonuser_event(event) } EventWrapper::ScaleFactorChanged(event) => { handle_hidpi_proxy(&mut event_handler, event) } } } } } fn handle_user_events(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); 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(); loop { let mut this = AppState::get_mut(mtm); 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, }); break; } drop(this); for wrapper in queued_events { match wrapper { EventWrapper::StaticEvent(event) => event_handler.handle_nonuser_event(event), EventWrapper::ScaleFactorChanged(event) => { handle_hidpi_proxy(&mut event_handler, event) } } } event_handler.handle_user_events(); } } pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); if !this.has_launched() || this.has_terminated() { return; } match this.state_mut() { AppStateImpl::ProcessingEvents { .. } => {} _ => bug!("`ProcessingRedraws` happened unexpectedly"), }; drop(this); handle_user_events(mtm); let mut this = AppState::get_mut(mtm); let redraw_events: Vec = this .main_events_cleared_transition() .into_iter() .map(|window| { EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, }) }) .collect(); drop(this); handle_nonuser_events(mtm, redraw_events); handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait)); } pub fn handle_events_cleared(mtm: MainThreadMarker) { AppState::get_mut(mtm).events_cleared_transition(); } pub fn terminated(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); let mut event_handler = this.terminated_transition(); drop(this); event_handler.handle_nonuser_event(Event::LoopExiting) } fn handle_hidpi_proxy(event_handler: &mut Box, event: ScaleFactorChanged) { let ScaleFactorChanged { suggested_size, scale_factor, window, } = event; let new_inner_size = Arc::new(Mutex::new(suggested_size)); let event = Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }; event_handler.handle_nonuser_event(event); let (view, screen_frame) = get_view_and_screen_frame(&window); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let logical_size = physical_size.to_logical(scale_factor); let size = CGSize::new(logical_size.width, logical_size.height); let new_frame: CGRect = CGRect::new(screen_frame.origin, size); view.setFrame(new_frame); } fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id, CGRect) { let view_controller = window.rootViewController().unwrap(); let view = view_controller.view().unwrap(); let bounds = window.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = window.convertRect_toCoordinateSpace(bounds, &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(), std::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, std::f64::MAX) } } fn start(&mut self) { unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::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 = meets_requirements(os_version, $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.majorVersion, self.os_version.minorVersion, self.os_version.patchVersion, extra_msg ) } )*} }; } os_capabilities! { /// #[allow(unused)] // error message unused safe_area_err_msg: "-[UIView safeAreaInsets]", safe_area: 11-0, /// home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", home_indicator_hidden: 11-0, /// defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", defer_system_gestures: 11-0, /// maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", maximum_frames_per_second: 10-3, /// #[allow(unused)] // error message unused force_touch_err_msg: "-[UITouch force]", force_touch: 9-0, } fn meets_requirements( version: NSOperatingSystemVersion, required_major: NSInteger, required_minor: NSInteger, ) -> bool { (version.majorVersion, version.minorVersion) >= (required_major, required_minor) } pub fn os_capabilities() -> OSCapabilities { static OS_CAPABILITIES: Lazy = Lazy::new(|| { let version: NSOperatingSystemVersion = unsafe { let process_info = NSProcessInfo::processInfo(); let atleast_ios_8: bool = msg_send![ &process_info, respondsToSelector: sel!(operatingSystemVersion) ]; // winit 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, "`winit` requires iOS version 8 or greater"); process_info.operatingSystemVersion() }; version.into() }); OS_CAPABILITIES.clone() } winit-0.29.15/src/platform_impl/ios/event_loop.rs000064400000000000000000000301571046102023000201100ustar 00000000000000use std::{ collections::VecDeque, ffi::c_void, fmt::{self, Debug}, marker::PhantomData, ptr, sync::mpsc::{self, Receiver, Sender}, }; use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use icrate::Foundation::{MainThreadMarker, NSString}; use objc2::ClassType; use crate::{ error::EventLoopError, event::Event, event_loop::{ ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget, }, platform::ios::Idiom, }; use super::{app_state, monitor, view, MonitorHandle}; use super::{ app_state::AppState, uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen}, }; #[derive(Debug)] pub struct EventLoopWindowTarget { pub(super) mtm: MainThreadMarker, p: PhantomData, } impl EventLoopWindowTarget { pub fn available_monitors(&self) -> VecDeque { monitor::uiscreens(self.mtm) } pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(UIScreen::main(self.mtm))) } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[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(crate) fn set_control_flow(&self, control_flow: ControlFlow) { AppState::get_mut(self.mtm).set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { AppState::get_mut(self.mtm).control_flow() } pub(crate) fn exit(&self) { // 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"); } pub(crate) fn exiting(&self) -> bool { false } } pub struct EventLoop { mtm: MainThreadMarker, sender: Sender, receiver: Receiver, window_target: RootEventLoopWindowTarget, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { pub(crate) fn new( _: &PlatformSpecificEventLoopAttributes, ) -> Result, EventLoopError> { let mtm = MainThreadMarker::new() .expect("On iOS, `EventLoop` must be created on the main thread"); static mut SINGLETON_INIT: bool = false; unsafe { assert!( !SINGLETON_INIT, "Only one `EventLoop` is supported on iOS. \ `EventLoopProxy` might be helpful" ); SINGLETON_INIT = true; } let (sender, receiver) = mpsc::channel(); // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); Ok(EventLoop { mtm, sender, receiver, window_target: RootEventLoopWindowTarget { p: EventLoopWindowTarget { mtm, p: PhantomData, }, _marker: PhantomData, }, }) } pub fn run(self, event_handler: F) -> ! where F: FnMut(Event, &RootEventLoopWindowTarget), { unsafe { let application = UIApplication::shared(self.mtm); assert!( application.is_none(), "\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ Note: `EventLoop::run` calls `UIApplicationMain` on iOS", ); let event_handler = std::mem::transmute::< Box, &RootEventLoopWindowTarget)>, Box>, >(Box::new(event_handler)); let handler = EventLoopHandler { f: event_handler, receiver: self.receiver, event_loop: self.window_target, }; app_state::will_launch(self.mtm, Box::new(handler)); // Ensure application delegate is initialized view::WinitApplicationDelegate::class(); UIApplicationMain( 0, ptr::null(), None, Some(&NSString::from_str("WinitApplicationDelegate")), ); unreachable!() } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.sender.clone()) } pub fn window_target(&self) -> &RootEventLoopWindowTarget { &self.window_target } } // EventLoopExtIOS impl EventLoop { pub fn idiom(&self) -> Idiom { UIDevice::current(self.mtm).userInterfaceIdiom().into() } } pub struct EventLoopProxy { sender: Sender, source: CFRunLoopSourceRef, } unsafe impl Send 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(_: *const 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 { version: 0, info: ptr::null_mut(), retain: None, release: None, copyDescription: None, equal: None, hash: None, schedule: None, cancel: None, perform: event_loop_proxy_handler, }; let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 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(|::std::sync::mpsc::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, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm), _ => 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 AboutToWait 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 `AboutToWait`. // // 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, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), kCFRunLoopExit => {} // may happen when running on macOS _ => 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, ) { let mtm = MainThreadMarker::new().unwrap(); #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), kCFRunLoopExit => {} // may happen when running on macOS _ => unreachable!(), } } let main_loop = CFRunLoopGetMain(); let begin_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopAfterWaiting, 1, // repeat = true CFIndex::min_value(), 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_value(), control_flow_end_handler, ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode); } } #[derive(Debug)] pub enum Never {} type EventHandlerCallback = dyn FnMut(Event, &RootEventLoopWindowTarget) + 'static; pub trait EventHandler: Debug { fn handle_nonuser_event(&mut self, event: Event); fn handle_user_events(&mut self); } struct EventLoopHandler { f: Box>, receiver: Receiver, 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 { fn handle_nonuser_event(&mut self, event: Event) { (self.f)(event.map_nonuser_event().unwrap(), &self.event_loop); } fn handle_user_events(&mut self) { for event in self.receiver.try_iter() { (self.f)(Event::UserEvent(event), &self.event_loop); } } } winit-0.29.15/src/platform_impl/ios/ffi.rs000064400000000000000000000046711046102023000165040ustar 00000000000000#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] use icrate::Foundation::{NSInteger, NSUInteger}; use objc2::encode::{Encode, Encoding}; use crate::platform::ios::{Idiom, ScreenEdge}; #[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 From for Idiom { fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom { match ui_idiom { 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, PartialEq, Eq)] pub struct UIRectEdge(NSUInteger); impl UIRectEdge { pub(crate) const NONE: Self = Self(0); } 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 From for ScreenEdge { fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge { let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`"); ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`") } } winit-0.29.15/src/platform_impl/ios/mod.rs000064400000000000000000000051451046102023000165140ustar 00000000000000//! 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_winit_app() { //! start_inner() //! } //! //! fn start_inner() { //! ... //! } //! ``` //! //! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode. //! //! ```ignore //! void start_winit_app(); //! ``` //! //! Use start_winit_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 winit: //! //! - applicationDidBecomeActive is Resumed //! - applicationWillResignActive is Suspended //! - applicationWillTerminate is LoopExiting //! //! Keep in mind that after LoopExiting event is received every attempt to draw with //! opengl will result in segfault. //! //! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. #![cfg(ios_platform)] #![allow(clippy::let_unit_value)] mod app_state; mod event_loop; mod ffi; mod monitor; mod uikit; mod view; mod window; use std::fmt; pub(crate) use self::{ event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }, monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, }; use self::uikit::UIScreen; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId { uiscreen: *const UIScreen, } impl DeviceId { pub const unsafe fn dummy() -> Self { DeviceId { uiscreen: std::ptr::null(), } } } unsafe impl Send for DeviceId {} unsafe impl Sync for DeviceId {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyEventExtra {} #[derive(Debug)] pub enum OsError {} impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "os error") } } winit-0.29.15/src/platform_impl/ios/monitor.rs000064400000000000000000000177621046102023000174340ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::{ collections::{BTreeSet, VecDeque}, fmt, hash, ptr, }; use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger}; use objc2::mutability::IsRetainable; use objc2::rc::Id; use super::uikit::{UIScreen, UIScreenMode}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::VideoMode as RootVideoMode, platform_impl::platform::app_state, }; // Workaround for `MainThreadBound` implementing almost no traits #[derive(Debug)] struct MainThreadBoundDelegateImpls(MainThreadBound>); impl Clone for MainThreadBoundDelegateImpls { fn clone(&self) -> Self { Self( self.0 .get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)), ) } } impl hash::Hash for MainThreadBoundDelegateImpls { fn hash(&self, state: &mut H) { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; Id::as_ptr(self.0.get(mtm)).hash(state); } } impl PartialEq for MainThreadBoundDelegateImpls { fn eq(&self, other: &Self) -> bool { // SAFETY: Marker only used to get the pointer let mtm = unsafe { MainThreadMarker::new_unchecked() }; Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm)) } } impl Eq for MainThreadBoundDelegateImpls {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, screen_mode: MainThreadBoundDelegateImpls, pub(crate) monitor: MonitorHandle, } impl VideoMode { fn new( uiscreen: Id, screen_mode: Id, mtm: MainThreadMarker, ) -> VideoMode { let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let size = screen_mode.size(); VideoMode { size: (size.width as u32, size.height as u32), bit_depth: 32, refresh_rate_millihertz, screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)), monitor: MonitorHandle::new(uiscreen), } } pub fn size(&self) -> PhysicalSize { self.size.into() } pub fn bit_depth(&self) -> u16 { self.bit_depth } pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id { self.screen_mode.0.get(mtm) } } pub struct MonitorHandle { ui_screen: MainThreadBound>, } impl Clone for MonitorHandle { fn clone(&self) -> Self { Self { ui_screen: self .ui_screen .get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)), } } } impl hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { (self as *const Self).hash(state); } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { ptr::eq(self, other) } } 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 { // TODO: Make a better ordering (self as *const Self).cmp(&(other as *const Self)) } } 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)] 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(crate) fn new(ui_screen: Id) -> Self { // Holding `Id` implies we're on the main thread. let mtm = MainThreadMarker::new().unwrap(); Self { ui_screen: MainThreadBound::new(ui_screen, mtm), } } pub fn name(&self) -> Option { self.ui_screen.get_on_main(|ui_screen, mtm| { let main = UIScreen::main(mtm); if *ui_screen == main { Some("Primary".to_string()) } else if *ui_screen == main.mirroredScreen() { Some("Mirrored".to_string()) } else { UIScreen::screens(mtm) .iter() .position(|rhs| rhs == &**ui_screen) .map(|idx| idx.to_string()) } }) } pub fn size(&self) -> PhysicalSize { let bounds = self .ui_screen .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } pub fn position(&self) -> PhysicalPosition { let bounds = self .ui_screen .get_on_main(|ui_screen, _| ui_screen.nativeBounds()); (bounds.origin.x as f64, bounds.origin.y as f64).into() } pub fn scale_factor(&self) -> f64 { self.ui_screen .get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64 } pub fn refresh_rate_millihertz(&self) -> Option { Some( self.ui_screen .get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)), ) } pub fn video_modes(&self) -> impl Iterator { self.ui_screen.get_on_main(|ui_screen, mtm| { // Use Ord impl of RootVideoMode let modes: BTreeSet<_> = ui_screen .availableModes() .into_iter() .map(|mode| RootVideoMode { video_mode: VideoMode::new(ui_screen.clone(), mode, mtm), }) .collect(); modes.into_iter().map(|mode| mode.video_mode) }) } pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id { self.ui_screen.get(mtm) } pub fn preferred_video_mode(&self) -> VideoMode { self.ui_screen.get_on_main(|ui_screen, mtm| { VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm) }) } } fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 { let refresh_rate_millihertz: NSInteger = { let os_capabilities = app_state::os_capabilities(); if os_capabilities.maximum_frames_per_second { 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 } }; refresh_rate_millihertz as u32 * 1000 } pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque { UIScreen::screens(mtm) .into_iter() .map(MonitorHandle::new) .collect() } winit-0.29.15/src/platform_impl/ios/uikit/application.rs000064400000000000000000000015651046102023000213670ustar 00000000000000use icrate::Foundation::{CGRect, MainThreadMarker, NSArray, NSObject}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{UIResponder, UIWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIApplication; unsafe impl ClassType for UIApplication { #[inherits(NSObject)] type Super = UIResponder; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIApplication { pub fn shared(_mtm: MainThreadMarker) -> Option> { unsafe { msg_send_id![Self::class(), sharedApplication] } } pub fn windows(&self) -> Id> { unsafe { msg_send_id![self, windows] } } #[method(statusBarFrame)] pub fn statusBarFrame(&self) -> CGRect; } ); winit-0.29.15/src/platform_impl/ios/uikit/coordinate_space.rs000064400000000000000000000005111046102023000223540ustar 00000000000000use icrate::Foundation::NSObject; use objc2::{extern_class, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UICoordinateSpace; unsafe impl ClassType for UICoordinateSpace { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); winit-0.29.15/src/platform_impl/ios/uikit/device.rs000064400000000000000000000013211046102023000203110ustar 00000000000000use icrate::Foundation::{MainThreadMarker, NSObject}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::super::ffi::UIUserInterfaceIdiom; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIDevice; unsafe impl ClassType for UIDevice { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIDevice { pub fn current(_mtm: MainThreadMarker) -> Id { unsafe { msg_send_id![Self::class(), currentDevice] } } #[method(userInterfaceIdiom)] pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom; } ); winit-0.29.15/src/platform_impl/ios/uikit/event.rs000064400000000000000000000004651046102023000202030ustar 00000000000000use icrate::Foundation::NSObject; use objc2::{extern_class, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIEvent; unsafe impl ClassType for UIEvent { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); winit-0.29.15/src/platform_impl/ios/uikit/mod.rs000064400000000000000000000024751046102023000176440ustar 00000000000000#![allow(non_snake_case)] #![allow(non_upper_case_globals)] use std::os::raw::{c_char, c_int}; use icrate::Foundation::NSString; mod application; mod coordinate_space; mod device; mod event; mod responder; mod screen; mod screen_mode; mod status_bar_style; mod touch; mod trait_collection; mod view; mod view_controller; mod window; pub(crate) use self::application::UIApplication; pub(crate) use self::coordinate_space::UICoordinateSpace; pub(crate) use self::device::UIDevice; pub(crate) use self::event::UIEvent; pub(crate) use self::responder::UIResponder; pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation}; pub(crate) use self::screen_mode::UIScreenMode; pub(crate) use self::status_bar_style::UIStatusBarStyle; pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType}; pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection}; #[allow(unused_imports)] pub(crate) use self::view::{UIEdgeInsets, UIView}; pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController}; pub(crate) use self::window::UIWindow; #[link(name = "UIKit", kind = "framework")] extern "C" { pub fn UIApplicationMain( argc: c_int, argv: *const c_char, principalClassName: Option<&NSString>, delegateClassName: Option<&NSString>, ) -> c_int; } winit-0.29.15/src/platform_impl/ios/uikit/responder.rs000064400000000000000000000004751046102023000210640ustar 00000000000000use icrate::Foundation::NSObject; use objc2::{extern_class, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIResponder; unsafe impl ClassType for UIResponder { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); winit-0.29.15/src/platform_impl/ios/uikit/screen.rs000064400000000000000000000045451046102023000203440ustar 00000000000000use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject}; use objc2::encode::{Encode, Encoding}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{UICoordinateSpace, UIScreenMode}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIScreen; unsafe impl ClassType for UIScreen { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIScreen { pub fn main(_mtm: MainThreadMarker) -> Id { unsafe { msg_send_id![Self::class(), mainScreen] } } pub fn screens(_mtm: MainThreadMarker) -> Id> { unsafe { msg_send_id![Self::class(), screens] } } #[method(bounds)] pub fn bounds(&self) -> CGRect; #[method(scale)] pub fn scale(&self) -> CGFloat; #[method(nativeBounds)] pub fn nativeBounds(&self) -> CGRect; #[method(nativeScale)] pub fn nativeScale(&self) -> CGFloat; #[method(maximumFramesPerSecond)] pub fn maximumFramesPerSecond(&self) -> NSInteger; pub fn mirroredScreen(&self) -> Id { unsafe { msg_send_id![Self::class(), mirroredScreen] } } pub fn preferredMode(&self) -> Option> { unsafe { msg_send_id![self, preferredMode] } } #[method(setCurrentMode:)] pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>); pub fn availableModes(&self) -> Id> { unsafe { msg_send_id![self, availableModes] } } #[method(setOverscanCompensation:)] pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation); pub fn coordinateSpace(&self) -> Id { unsafe { msg_send_id![self, coordinateSpace] } } } ); #[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: Self = Self(0); pub const InsetBounds: Self = Self(1); pub const None: Self = Self(2); } winit-0.29.15/src/platform_impl/ios/uikit/screen_mode.rs000064400000000000000000000007211046102023000213400ustar 00000000000000use icrate::Foundation::{CGSize, NSObject}; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIScreenMode; unsafe impl ClassType for UIScreenMode { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIScreenMode { #[method(size)] pub fn size(&self) -> CGSize; } ); winit-0.29.15/src/platform_impl/ios/uikit/status_bar_style.rs000064400000000000000000000013231046102023000224430ustar 00000000000000use crate::platform::ios::StatusBarStyle; use icrate::Foundation::NSInteger; use objc2::encode::{Encode, Encoding}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[allow(dead_code)] #[repr(isize)] pub enum UIStatusBarStyle { #[default] Default = 0, LightContent = 1, DarkContent = 3, } impl From for UIStatusBarStyle { fn from(value: StatusBarStyle) -> Self { match value { StatusBarStyle::Default => Self::Default, StatusBarStyle::LightContent => Self::LightContent, StatusBarStyle::DarkContent => Self::DarkContent, } } } unsafe impl Encode for UIStatusBarStyle { const ENCODING: Encoding = NSInteger::ENCODING; } winit-0.29.15/src/platform_impl/ios/uikit/touch.rs000064400000000000000000000026221046102023000202010ustar 00000000000000use icrate::Foundation::{CGFloat, CGPoint, NSInteger, NSObject}; use objc2::encode::{Encode, Encoding}; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::UIView; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UITouch; unsafe impl ClassType for UITouch { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UITouch { #[method(locationInView:)] pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint; #[method(type)] pub fn type_(&self) -> UITouchType; #[method(force)] pub fn force(&self) -> CGFloat; #[method(maximumPossibleForce)] pub fn maximumPossibleForce(&self) -> CGFloat; #[method(altitudeAngle)] pub fn altitudeAngle(&self) -> CGFloat; #[method(phase)] pub fn phase(&self) -> UITouchPhase; } ); #[derive(Debug, PartialEq, Eq)] #[allow(dead_code)] #[repr(isize)] pub enum UITouchType { Direct = 0, Indirect, Pencil, } unsafe impl Encode for UITouchType { const ENCODING: Encoding = NSInteger::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 = NSInteger::ENCODING; } winit-0.29.15/src/platform_impl/ios/uikit/trait_collection.rs000064400000000000000000000014721046102023000224170ustar 00000000000000use icrate::Foundation::{NSInteger, NSObject}; use objc2::encode::{Encode, Encoding}; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UITraitCollection; unsafe impl ClassType for UITraitCollection { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UITraitCollection { #[method(forceTouchCapability)] pub fn forceTouchCapability(&self) -> UIForceTouchCapability; } ); #[derive(Debug, PartialEq, Eq)] #[allow(dead_code)] #[repr(isize)] pub enum UIForceTouchCapability { Unknown = 0, Unavailable, Available, } unsafe impl Encode for UIForceTouchCapability { const ENCODING: Encoding = NSInteger::ENCODING; } winit-0.29.15/src/platform_impl/ios/uikit/view.rs000064400000000000000000000046161046102023000200360ustar 00000000000000use icrate::Foundation::{CGFloat, CGRect, NSObject}; use objc2::encode::{Encode, Encoding}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{UICoordinateSpace, UIResponder, UIViewController}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIView; unsafe impl ClassType for UIView { #[inherits(NSObject)] type Super = UIResponder; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIView { #[method(bounds)] pub fn bounds(&self) -> CGRect; #[method(setBounds:)] pub fn setBounds(&self, value: CGRect); #[method(frame)] pub fn frame(&self) -> CGRect; #[method(setFrame:)] pub fn setFrame(&self, value: CGRect); #[method(contentScaleFactor)] pub fn contentScaleFactor(&self) -> CGFloat; #[method(setContentScaleFactor:)] pub fn setContentScaleFactor(&self, val: CGFloat); #[method(setMultipleTouchEnabled:)] pub fn setMultipleTouchEnabled(&self, val: bool); pub fn rootViewController(&self) -> Option> { unsafe { msg_send_id![self, rootViewController] } } #[method(setRootViewController:)] pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>); #[method(convertRect:toCoordinateSpace:)] pub fn convertRect_toCoordinateSpace( &self, rect: CGRect, coordinateSpace: &UICoordinateSpace, ) -> CGRect; #[method(convertRect:fromCoordinateSpace:)] pub fn convertRect_fromCoordinateSpace( &self, rect: CGRect, coordinateSpace: &UICoordinateSpace, ) -> CGRect; #[method(safeAreaInsets)] pub fn safeAreaInsets(&self) -> UIEdgeInsets; #[method(setNeedsDisplay)] pub fn setNeedsDisplay(&self); } ); #[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, ], ); } winit-0.29.15/src/platform_impl/ios/uikit/view_controller.rs000064400000000000000000000034741046102023000223020ustar 00000000000000use icrate::Foundation::{NSObject, NSUInteger}; use objc2::encode::{Encode, Encoding}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{UIResponder, UIView}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIViewController; unsafe impl ClassType for UIViewController { #[inherits(NSObject)] type Super = UIResponder; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIViewController { #[method(attemptRotationToDeviceOrientation)] pub fn attemptRotationToDeviceOrientation(); #[method(setNeedsStatusBarAppearanceUpdate)] pub fn setNeedsStatusBarAppearanceUpdate(&self); #[method(setNeedsUpdateOfHomeIndicatorAutoHidden)] pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self); #[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)] pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self); pub fn view(&self) -> Option> { unsafe { msg_send_id![self, view] } } #[method(setView:)] pub fn setView(&self, view: Option<&UIView>); } ); bitflags! { #[derive(Clone, Copy)] pub struct UIInterfaceOrientationMask: NSUInteger { const Portrait = 1 << 1; const PortraitUpsideDown = 1 << 2; const LandscapeRight = 1 << 3; const LandscapeLeft = 1 << 4; const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits(); const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits(); const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits(); } } unsafe impl Encode for UIInterfaceOrientationMask { const ENCODING: Encoding = NSUInteger::ENCODING; } winit-0.29.15/src/platform_impl/ios/uikit/window.rs000064400000000000000000000016271046102023000203720ustar 00000000000000use icrate::Foundation::NSObject; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{UIResponder, UIScreen, UIView}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct UIWindow; unsafe impl ClassType for UIWindow { #[inherits(UIResponder, NSObject)] type Super = UIView; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl UIWindow { pub fn screen(&self) -> Id { unsafe { msg_send_id![self, screen] } } #[method(setScreen:)] pub fn setScreen(&self, screen: &UIScreen); #[method(setHidden:)] pub fn setHidden(&self, flag: bool); #[method(makeKeyAndVisible)] pub fn makeKeyAndVisible(&self); #[method(isKeyWindow)] pub fn isKeyWindow(&self) -> bool; } ); winit-0.29.15/src/platform_impl/ios/view.rs000064400000000000000000000553161046102023000167140ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::cell::Cell; use std::ptr::NonNull; use icrate::Foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSObjectProtocol, NSSet}; use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::Id; use objc2::runtime::AnyClass; use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType}; use super::app_state::{self, EventWrapper}; use super::uikit::{ UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask, UIResponder, UIStatusBarStyle, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController, UIWindow, }; use super::window::WindowId; use crate::{ dpi::PhysicalPosition, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::ValidOrientations, platform_impl::platform::{ ffi::{UIRectEdge, UIUserInterfaceIdiom}, window::PlatformSpecificWindowBuilderAttributes, DeviceId, Fullscreen, }, window::{WindowAttributes, WindowId as RootWindowId}, }; declare_class!( pub(crate) struct WinitView; unsafe impl ClassType for WinitView { #[inherits(UIResponder, NSObject)] type Super = UIView; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitUIView"; } unsafe impl WinitView { #[method(drawRect:)] fn draw_rect(&self, rect: CGRect) { let mtm = MainThreadMarker::new().unwrap(); let window = self.window().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::RedrawRequested, }), ); let _: () = unsafe { msg_send![super(self), drawRect: rect] }; } #[method(layoutSubviews)] fn layout_subviews(&self) { let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let window = self.window().unwrap(); let window_bounds = window.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space); let scale_factor = 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 as f64); // 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 = self.frame(); if view_frame != window_bounds { self.setFrame(window_bounds); } app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Resized(size), }), ); } #[method(setContentScaleFactor:)] fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { let mtm = MainThreadMarker::new().unwrap(); let _: () = unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] }; // `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 let window = match self.window() { Some(window) => window, None => 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 = self.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 = scale_factor as f64; let bounds = self.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space); let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); app_state::handle_nonuser_events( mtm, std::iter::once(EventWrapper::ScaleFactorChanged( app_state::ScaleFactorChanged { window, scale_factor, suggested_size: size.to_physical(scale_factor), }, )) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id, event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); } #[method(touchesBegan:withEvent:)] fn touches_began(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesMoved:withEvent:)] fn touches_moved(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesEnded:withEvent:)] fn touches_ended(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[method(touchesCancelled:withEvent:)] fn touches_cancelled(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } } ); extern_methods!( #[allow(non_snake_case)] unsafe impl WinitView { fn window(&self) -> Option> { unsafe { msg_send_id![self, window] } } unsafe fn traitCollection(&self) -> Id { msg_send_id![self, traitCollection] } // TODO: Allow the user to customize this #[method(layerClass)] pub(crate) fn layerClass() -> &'static AnyClass; } ); impl WinitView { pub(crate) fn new( _mtm: MainThreadMarker, _window_attributes: &WindowAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, ) -> Id { let this: Id = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] }; this.setMultipleTouchEnabled(true); if let Some(scale_factor) = platform_attributes.scale_factor { this.setContentScaleFactor(scale_factor as _); } this } fn handle_touches(&self, touches: &NSSet) { let window = self.window().unwrap(); let uiscreen = window.screen(); let mut touch_events = Vec::new(); let os_supports_force = app_state::os_capabilities().force_touch; for touch in touches { let logical_location = touch.locationInView(None); let touch_type = touch.type_(); let force = if os_supports_force { let trait_collection = unsafe { self.traitCollection() }; let touch_capability = trait_collection.forceTouchCapability(); // Both the OS _and_ the device need to be checked for force touch support. if touch_capability == UIForceTouchCapability::Available || touch_type == UITouchType::Pencil { let force = touch.force(); let max_possible_force = touch.maximumPossibleForce(); let altitude_angle: Option = if touch_type == UITouchType::Pencil { let angle = 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 *const UITouch as u64; let phase = 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 = self.contentScaleFactor(); PhysicalPosition::from_logical::<(f64, f64), f64>( (logical_location.x as _, logical_location.y as _), scale_factor as f64, ) }; touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Touch(Touch { device_id: RootDeviceId(DeviceId { uiscreen: Id::as_ptr(&uiscreen), }), id: touch_id, location: physical_location, force, phase, }), })); } let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events(mtm, touch_events); } } pub struct ViewControllerState { prefers_status_bar_hidden: Cell, preferred_status_bar_style: Cell, prefers_home_indicator_auto_hidden: Cell, supported_orientations: Cell, preferred_screen_edges_deferring_system_gestures: Cell, } declare_class!( pub(crate) struct WinitViewController { state: IvarDrop, "_state">, } mod view_controller_ivars; unsafe impl ClassType for WinitViewController { #[inherits(UIResponder, NSObject)] type Super = UIViewController; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitUIViewController"; } unsafe impl WinitViewController { #[method(init)] unsafe fn init(this: *mut Self) -> Option> { let this: Option<&mut Self> = msg_send![super(this), init]; this.map(|this| { // These are set in WinitViewController::new, it's just to set them // to _something_. Ivar::write( &mut this.state, Box::new(ViewControllerState { prefers_status_bar_hidden: Cell::new(false), preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default), prefers_home_indicator_auto_hidden: Cell::new(false), supported_orientations: Cell::new(UIInterfaceOrientationMask::All), preferred_screen_edges_deferring_system_gestures: Cell::new( UIRectEdge::NONE, ), }), ); NonNull::from(this) }) } } unsafe impl WinitViewController { #[method(shouldAutorotate)] fn should_autorotate(&self) -> bool { true } #[method(prefersStatusBarHidden)] fn prefers_status_bar_hidden(&self) -> bool { self.state.prefers_status_bar_hidden.get() } #[method(preferredStatusBarStyle)] fn preferred_status_bar_style(&self) -> UIStatusBarStyle { self.state.preferred_status_bar_style.get() } #[method(prefersHomeIndicatorAutoHidden)] fn prefers_home_indicator_auto_hidden(&self) -> bool { self.state.prefers_home_indicator_auto_hidden.get() } #[method(supportedInterfaceOrientations)] fn supported_orientations(&self) -> UIInterfaceOrientationMask { self.state.supported_orientations.get() } #[method(preferredScreenEdgesDeferringSystemGestures)] fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { self.state .preferred_screen_edges_deferring_system_gestures .get() } } ); impl WinitViewController { pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) { self.state.prefers_status_bar_hidden.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_preferred_status_bar_style(&self, val: UIStatusBarStyle) { self.state.preferred_status_bar_style.set(val); self.setNeedsStatusBarAppearanceUpdate(); } pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) { self.state.prefers_home_indicator_auto_hidden.set(val); let os_capabilities = app_state::os_capabilities(); if os_capabilities.home_indicator_hidden { self.setNeedsUpdateOfHomeIndicatorAutoHidden(); } else { os_capabilities.home_indicator_hidden_err_msg("ignoring") } } pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) { self.state .preferred_screen_edges_deferring_system_gestures .set(val); let os_capabilities = app_state::os_capabilities(); if os_capabilities.defer_system_gestures { self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures(); } else { os_capabilities.defer_system_gestures_err_msg("ignoring") } } pub(crate) fn set_supported_interface_orientations( &self, mtm: MainThreadMarker, valid_orientations: ValidOrientations, ) { let mask = match ( valid_orientations, UIDevice::current(mtm).userInterfaceIdiom(), ) { (ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => { UIInterfaceOrientationMask::AllButUpsideDown } (ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All, (ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape, (ValidOrientations::Portrait, UIUserInterfaceIdiom::Phone) => { UIInterfaceOrientationMask::Portrait } (ValidOrientations::Portrait, _) => { UIInterfaceOrientationMask::Portrait | UIInterfaceOrientationMask::PortraitUpsideDown } }; self.state.supported_orientations.set(mask); UIViewController::attemptRotationToDeviceOrientation(); } pub(crate) fn new( mtm: MainThreadMarker, _window_attributes: &WindowAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes, view: &UIView, ) -> Id { let this: Id = unsafe { msg_send_id![Self::alloc(), init] }; this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden); this.set_preferred_status_bar_style(platform_attributes.preferred_status_bar_style.into()); this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations); this.set_prefers_home_indicator_auto_hidden( platform_attributes.prefers_home_indicator_hidden, ); this.set_preferred_screen_edges_deferring_system_gestures( platform_attributes .preferred_screen_edges_deferring_system_gestures .into(), ); this.setView(Some(view)); this } } declare_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct WinitUIWindow; unsafe impl ClassType for WinitUIWindow { #[inherits(UIResponder, NSObject)] type Super = UIWindow; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitUIWindow"; } unsafe impl WinitUIWindow { #[method(becomeKeyWindow)] fn become_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(true), }), ); let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } #[method(resignKeyWindow)] fn resign_key_window(&self) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event( mtm, EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(self.id()), event: WindowEvent::Focused(false), }), ); let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; } } ); impl WinitUIWindow { pub(crate) fn new( mtm: MainThreadMarker, window_attributes: &WindowAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, view_controller: &UIViewController, ) -> Id { let this: Id = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] }; this.setRootViewController(Some(view_controller)); match window_attributes.fullscreen.0.clone().map(Into::into) { Some(Fullscreen::Exclusive(ref video_mode)) => { let monitor = video_mode.monitor(); let screen = monitor.ui_screen(mtm); screen.setCurrentMode(Some(video_mode.screen_mode(mtm))); this.setScreen(screen); } Some(Fullscreen::Borderless(Some(ref monitor))) => { let screen = monitor.ui_screen(mtm); this.setScreen(screen); } _ => (), } this } pub(crate) fn id(&self) -> WindowId { (self as *const Self as usize as u64).into() } } declare_class!( pub struct WinitApplicationDelegate; unsafe impl ClassType for WinitApplicationDelegate { type Super = NSObject; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitApplicationDelegate"; } // UIApplicationDelegate protocol unsafe impl WinitApplicationDelegate { #[method(application:didFinishLaunchingWithOptions:)] fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { app_state::did_finish_launching(MainThreadMarker::new().unwrap()); true } #[method(applicationDidBecomeActive:)] fn did_become_active(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) } #[method(applicationWillResignActive:)] fn will_resign_active(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) } #[method(applicationWillEnterForeground:)] fn will_enter_foreground(&self, application: &UIApplication) { self.send_occluded_event_for_all_windows(application, false); } #[method(applicationDidEnterBackground:)] fn did_enter_background(&self, application: &UIApplication) { self.send_occluded_event_for_all_windows(application, true); } #[method(applicationWillTerminate:)] fn will_terminate(&self, application: &UIApplication) { let mut events = Vec::new(); for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Destroyed, })); } } let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events(mtm, events); app_state::terminated(mtm); } #[method(applicationDidReceiveMemoryWarning:)] fn did_receive_memory_warning(&self, _application: &UIApplication) { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning)) } } ); impl WinitApplicationDelegate { fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) { let mut events = Vec::new(); for window in application.windows().iter() { if window.is_kind_of::() { // SAFETY: We just checked that the window is a `winit` window let window = unsafe { let ptr: *const UIWindow = window; let ptr: *const WinitUIWindow = ptr.cast(); &*ptr }; events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.id()), event: WindowEvent::Occluded(occluded), })); } } let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events(mtm, events); } } winit-0.29.15/src/platform_impl/ios/window.rs000064400000000000000000000546721046102023000172550ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; use icrate::Foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{class, msg_send}; use super::app_state::EventWrapper; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::view::{WinitUIWindow, WinitView, WinitViewController}; use crate::{ dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::{Event, WindowEvent}, icon::Icon, platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations}, platform_impl::platform::{ app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; pub struct Inner { window: Id, view_controller: Id, view: Id, gl_or_metal_backed: bool, } impl Inner { pub fn set_title(&self, _title: &str) { debug!("`Window::set_title` is ignored on iOS") } pub fn set_transparent(&self, _transparent: bool) { debug!("`Window::set_transparent` is ignored on iOS") } pub fn set_blur(&self, _blur: bool) { debug!("`Window::set_blur` is ignored on iOS") } pub fn set_visible(&self, visible: bool) { self.window.setHidden(!visible) } pub fn is_visible(&self) -> Option { warn!("`Window::is_visible` is ignored on iOS"); None } pub fn request_redraw(&self) { if self.gl_or_metal_backed { let mtm = MainThreadMarker::new().unwrap(); // `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(mtm, self.window.clone()); } else { self.view.setNeedsDisplay(); } } pub fn pre_present_notify(&self) {} pub fn inner_position(&self) -> Result, NotSupportedError> { 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> { 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) { 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.rect_from_screen_space(new_screen_frame); self.window.setBounds(bounds); } pub fn inner_size(&self) -> PhysicalSize { 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 { 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 request_inner_size(&self, _size: Size) -> Option> { Some(self.inner_size()) } pub fn set_min_inner_size(&self, _dimensions: Option) { warn!("`Window::set_min_inner_size` is ignored on iOS") } pub fn set_max_inner_size(&self, _dimensions: Option) { warn!("`Window::set_max_inner_size` is ignored on iOS") } pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { warn!("`Window::set_resize_increments` is ignored on iOS") } pub fn set_resizable(&self, _resizable: bool) { warn!("`Window::set_resizable` is ignored on iOS") } pub fn is_resizable(&self) -> bool { warn!("`Window::is_resizable` is ignored on iOS"); false } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { warn!("`Window::set_enabled_buttons` is ignored on iOS"); } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { warn!("`Window::enabled_buttons` is ignored on iOS"); WindowButtons::all() } pub fn scale_factor(&self) -> f64 { self.view.contentScaleFactor() 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, _: CursorGrabMode) -> 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 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())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} pub fn set_cursor_hittest(&self, _hittest: 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 is_minimized(&self) -> Option { warn!("`Window::is_minimized` is ignored on iOS"); None } 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(crate) fn set_fullscreen(&self, monitor: Option) { let mtm = MainThreadMarker::new().unwrap(); let uiscreen = match &monitor { Some(Fullscreen::Exclusive(video_mode)) => { let uiscreen = video_mode.monitor.ui_screen(mtm); uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm))); uiscreen.clone() } Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(), Some(Fullscreen::Borderless(None)) => { self.current_monitor_inner().ui_screen(mtm).clone() } 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 = self.window.screen(); if uiscreen != current { self.window.setScreen(&uiscreen); } let bounds = uiscreen.bounds(); 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 uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None); } pub(crate) fn fullscreen(&self) -> Option { let mtm = MainThreadMarker::new().unwrap(); let monitor = self.current_monitor_inner(); let uiscreen = monitor.ui_screen(mtm); let screen_space_bounds = self.screen_frame(); let screen_bounds = 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) {} pub fn is_decorated(&self) -> bool { true } pub fn set_window_level(&self, _level: WindowLevel) { warn!("`Window::set_window_level` 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_cursor_area(&self, _position: Position, _size: Size) { warn!("`Window::set_ime_cursor_area` is ignored on iOS") } pub fn set_ime_allowed(&self, _allowed: bool) { warn!("`Window::set_ime_allowed` is ignored on iOS") } pub fn set_ime_purpose(&self, _purpose: ImePurpose) { warn!("`Window::set_ime_allowed` is ignored on iOS") } pub fn focus_window(&self) { warn!("`Window::set_focus` is ignored on iOS") } pub fn request_user_attention(&self, _request_type: Option) { warn!("`Window::request_user_attention` is ignored on iOS") } // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> MonitorHandle { MonitorHandle::new(self.window.screen()) } pub fn current_monitor(&self) -> Option { Some(self.current_monitor_inner()) } pub fn available_monitors(&self) -> VecDeque { monitor::uiscreens(MainThreadMarker::new().unwrap()) } pub fn primary_monitor(&self) -> Option { Some(MonitorHandle::new(UIScreen::main( MainThreadMarker::new().unwrap(), ))) } pub fn id(&self) -> WindowId { self.window.id() } #[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 = Id::as_ptr(&self.window) as _; window_handle.ui_view = Id::as_ptr(&self.view) as _; window_handle.ui_view_controller = Id::as_ptr(&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 = Id::as_ptr(&self.window) as _; window_handle.ui_view = Id::as_ptr(&self.view) as _; window_handle.ui_view_controller = Id::as_ptr(&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({ let ui_view = Id::as_ptr(&self.view) as _; std::ptr::NonNull::new(ui_view).expect("Id should never be null") }); window_handle.ui_view_controller = std::ptr::NonNull::new(Id::as_ptr(&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) -> Option { warn!("`Window::theme` is ignored on iOS"); None } pub fn set_content_protected(&self, _protected: bool) {} pub fn has_focus(&self) -> bool { self.window.isKeyWindow() } #[inline] pub fn set_theme(&self, _theme: Option) { warn!("`Window::set_theme` is ignored on iOS"); } pub fn title(&self) -> String { warn!("`Window::title` is ignored on iOS"); String::new() } pub fn reset_dead_keys(&self) { // Noop } } pub struct Window { inner: MainThreadBound, } impl Window { pub(crate) fn new( event_loop: &EventLoopWindowTarget, window_attributes: WindowAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { let mtm = event_loop.mtm; if window_attributes.min_inner_size.is_some() { warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); } if window_attributes.max_inner_size.is_some() { warn!("`WindowAttributes::max_inner_size` is ignored on iOS"); } // TODO: transparency, visible let main_screen = UIScreen::main(mtm); let fullscreen = window_attributes.fullscreen.0.clone().map(Into::into); let screen = match fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm), Some(Fullscreen::Borderless(None)) | None => &main_screen, }; let screen_bounds = screen.bounds(); let frame = match window_attributes.inner_size { Some(dim) => { let scale_factor = screen.scale(); let size = dim.to_logical::(scale_factor as f64); CGRect { origin: screen_bounds.origin, size: CGSize { width: size.width as _, height: size.height as _, }, } } None => screen_bounds, }; let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame); let gl_or_metal_backed = unsafe { let layer_class = WinitView::layerClass(); let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; is_metal || is_gl }; let view_controller = WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view); let window = WinitUIWindow::new( mtm, &window_attributes, &platform_attributes, frame, &view_controller, ); app_state::set_key_window(mtm, &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 = view.contentScaleFactor(); let scale_factor = scale_factor as f64; if scale_factor != 1.0 { let bounds = view.bounds(); let screen = window.screen(); let screen_space = screen.coordinateSpace(); let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space); let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; let window_id = RootWindowId(window.id()); app_state::handle_nonuser_events( mtm, std::iter::once(EventWrapper::ScaleFactorChanged( app_state::ScaleFactorChanged { window: window.clone(), scale_factor, suggested_size: size.to_physical(scale_factor), }, )) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id, event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); } let inner = Inner { window, view_controller, view, gl_or_metal_backed, }; Ok(Window { inner: MainThreadBound::new(inner, mtm), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { // For now, don't actually do queuing, since it may be less predictable self.maybe_wait_on_main(f) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { self.inner.get_on_main(|inner, _mtm| f(inner)) } } // WindowExtIOS impl Inner { pub fn set_scale_factor(&self, scale_factor: f64) { assert!( dpi::validate_scale_factor(scale_factor), "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" ); let scale_factor = scale_factor as CGFloat; self.view.setContentScaleFactor(scale_factor); } pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { self.view_controller.set_supported_interface_orientations( MainThreadMarker::new().unwrap(), valid_orientations, ); } pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { self.view_controller .set_prefers_home_indicator_auto_hidden(hidden); } pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { self.view_controller .set_preferred_screen_edges_deferring_system_gestures(edges.into()); } pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { self.view_controller.set_prefers_status_bar_hidden(hidden); } pub fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { self.view_controller .set_preferred_status_bar_style(status_bar_style.into()); } } impl Inner { fn screen_frame(&self) -> CGRect { self.rect_to_screen_space(self.window.bounds()) } fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window .convertRect_toCoordinateSpace(rect, &screen_space) } fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { let screen_space = self.window.screen().coordinateSpace(); self.window .convertRect_fromCoordinateSpace(rect, &screen_space) } fn safe_area_screen_space(&self) -> CGRect { let bounds = self.window.bounds(); if app_state::os_capabilities().safe_area { let safe_area = 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.rect_to_screen_space(safe_bounds) } else { let screen_frame = self.rect_to_screen_space(bounds); let status_bar_frame = { let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect( "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS", ); 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: *mut WinitUIWindow, } impl WindowId { pub const unsafe fn dummy() -> Self { WindowId { window: std::ptr::null_mut(), } } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.window as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self { window: raw_id as _, } } } 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 _, } } } #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { pub scale_factor: Option, pub valid_orientations: ValidOrientations, pub prefers_home_indicator_hidden: bool, pub prefers_status_bar_hidden: bool, pub preferred_status_bar_style: StatusBarStyle, pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } winit-0.29.15/src/platform_impl/linux/common/mod.rs000064400000000000000000000000151046102023000203400ustar 00000000000000pub mod xkb; winit-0.29.15/src/platform_impl/linux/common/xkb/compose.rs000064400000000000000000000065501046102023000220240ustar 00000000000000//! XKB compose handling. use std::env; use std::ffi::CString; use std::ops::Deref; use std::os::unix::ffi::OsStringExt; use std::ptr::NonNull; use super::XkbContext; use super::XKBCH; use smol_str::SmolStr; use xkbcommon_dl::{ xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags, xkb_compose_status, xkb_compose_table, xkb_keysym_t, }; #[derive(Debug)] pub struct XkbComposeTable { table: NonNull, } impl XkbComposeTable { pub fn new(context: &XkbContext) -> Option { let locale = env::var_os("LC_ALL") .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LC_CTYPE")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LANG")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .unwrap_or_else(|| "C".into()); let locale = CString::new(locale.into_vec()).unwrap(); let table = unsafe { (XKBCH.xkb_compose_table_new_from_locale)( context.as_ptr(), locale.as_ptr(), xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, ) }; let table = NonNull::new(table)?; Some(Self { table }) } /// Create new state with the given compose table. pub fn new_state(&self) -> Option { let state = unsafe { (XKBCH.xkb_compose_state_new)( self.table.as_ptr(), xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, ) }; let state = NonNull::new(state)?; Some(XkbComposeState { state }) } } impl Deref for XkbComposeTable { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.table } } impl Drop for XkbComposeTable { fn drop(&mut self) { unsafe { (XKBCH.xkb_compose_table_unref)(self.table.as_ptr()); } } } #[derive(Debug)] pub struct XkbComposeState { state: NonNull, } impl XkbComposeState { pub fn get_string(&mut self, scratch_buffer: &mut Vec) -> Option { super::make_string_with(scratch_buffer, |ptr, len| unsafe { (XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len) }) } #[inline] pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus { let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) }; match feed_result { xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored, xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => { ComposeStatus::Accepted(self.status()) } } } #[inline] pub fn reset(&mut self) { unsafe { (XKBCH.xkb_compose_state_reset)(self.state.as_ptr()); } } #[inline] pub fn status(&mut self) -> xkb_compose_status { unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) } } } impl Drop for XkbComposeState { fn drop(&mut self) { unsafe { (XKBCH.xkb_compose_state_unref)(self.state.as_ptr()); }; } } #[derive(Copy, Clone, Debug)] pub enum ComposeStatus { Accepted(xkb_compose_status), Ignored, None, } winit-0.29.15/src/platform_impl/linux/common/xkb/keymap.rs000064400000000000000000001165101046102023000216430ustar 00000000000000//! XKB keymap. use std::ffi::c_char; use std::ops::Deref; use std::ptr::{self, NonNull}; #[cfg(x11_platform)] use x11_dl::xlib_xcb::xcb_connection_t; #[cfg(wayland_platform)] use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; use xkb::XKB_MOD_INVALID; use xkbcommon_dl::{ self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t, xkb_layout_index_t, xkb_mod_index_t, }; use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; #[cfg(x11_platform)] use crate::platform_impl::common::xkb::XKBXH; use crate::platform_impl::common::xkb::{XkbContext, XKBH}; /// Map the raw X11-style keycode to the `KeyCode` enum. /// /// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. pub fn raw_keycode_to_physicalkey(keycode: u32) -> PhysicalKey { scancode_to_keycode(keycode.saturating_sub(8)) } /// Map the linux scancode to Keycode. /// /// Both X11 and Wayland use keys with `+ 8` offset to linux scancode. pub fn scancode_to_keycode(scancode: u32) -> PhysicalKey { // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as // libxkbcommon's documentation seems to suggest that the keycode values we're interested in // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, // I can only hope they agree on what the keycodes mean. // // Some of the keycodes are likely superfluous for our purposes, and some are ones which are // difficult to test the correctness of, or discover the purpose of. Because of this, they've // either been commented out here, or not included at all. PhysicalKey::Code(match scancode { 0 => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(0)), 1 => KeyCode::Escape, 2 => KeyCode::Digit1, 3 => KeyCode::Digit2, 4 => KeyCode::Digit3, 5 => KeyCode::Digit4, 6 => KeyCode::Digit5, 7 => KeyCode::Digit6, 8 => KeyCode::Digit7, 9 => KeyCode::Digit8, 10 => KeyCode::Digit9, 11 => KeyCode::Digit0, 12 => KeyCode::Minus, 13 => KeyCode::Equal, 14 => KeyCode::Backspace, 15 => KeyCode::Tab, 16 => KeyCode::KeyQ, 17 => KeyCode::KeyW, 18 => KeyCode::KeyE, 19 => KeyCode::KeyR, 20 => KeyCode::KeyT, 21 => KeyCode::KeyY, 22 => KeyCode::KeyU, 23 => KeyCode::KeyI, 24 => KeyCode::KeyO, 25 => KeyCode::KeyP, 26 => KeyCode::BracketLeft, 27 => KeyCode::BracketRight, 28 => KeyCode::Enter, 29 => KeyCode::ControlLeft, 30 => KeyCode::KeyA, 31 => KeyCode::KeyS, 32 => KeyCode::KeyD, 33 => KeyCode::KeyF, 34 => KeyCode::KeyG, 35 => KeyCode::KeyH, 36 => KeyCode::KeyJ, 37 => KeyCode::KeyK, 38 => KeyCode::KeyL, 39 => KeyCode::Semicolon, 40 => KeyCode::Quote, 41 => KeyCode::Backquote, 42 => KeyCode::ShiftLeft, 43 => KeyCode::Backslash, 44 => KeyCode::KeyZ, 45 => KeyCode::KeyX, 46 => KeyCode::KeyC, 47 => KeyCode::KeyV, 48 => KeyCode::KeyB, 49 => KeyCode::KeyN, 50 => KeyCode::KeyM, 51 => KeyCode::Comma, 52 => KeyCode::Period, 53 => KeyCode::Slash, 54 => KeyCode::ShiftRight, 55 => KeyCode::NumpadMultiply, 56 => KeyCode::AltLeft, 57 => KeyCode::Space, 58 => KeyCode::CapsLock, 59 => KeyCode::F1, 60 => KeyCode::F2, 61 => KeyCode::F3, 62 => KeyCode::F4, 63 => KeyCode::F5, 64 => KeyCode::F6, 65 => KeyCode::F7, 66 => KeyCode::F8, 67 => KeyCode::F9, 68 => KeyCode::F10, 69 => KeyCode::NumLock, 70 => KeyCode::ScrollLock, 71 => KeyCode::Numpad7, 72 => KeyCode::Numpad8, 73 => KeyCode::Numpad9, 74 => KeyCode::NumpadSubtract, 75 => KeyCode::Numpad4, 76 => KeyCode::Numpad5, 77 => KeyCode::Numpad6, 78 => KeyCode::NumpadAdd, 79 => KeyCode::Numpad1, 80 => KeyCode::Numpad2, 81 => KeyCode::Numpad3, 82 => KeyCode::Numpad0, 83 => KeyCode::NumpadDecimal, 85 => KeyCode::Lang5, 86 => KeyCode::IntlBackslash, 87 => KeyCode::F11, 88 => KeyCode::F12, 89 => KeyCode::IntlRo, 90 => KeyCode::Lang3, 91 => KeyCode::Lang4, 92 => KeyCode::Convert, 93 => KeyCode::KanaMode, 94 => KeyCode::NonConvert, // 95 => KeyCode::KPJPCOMMA, 96 => KeyCode::NumpadEnter, 97 => KeyCode::ControlRight, 98 => KeyCode::NumpadDivide, 99 => KeyCode::PrintScreen, 100 => KeyCode::AltRight, // 101 => KeyCode::LINEFEED, 102 => KeyCode::Home, 103 => KeyCode::ArrowUp, 104 => KeyCode::PageUp, 105 => KeyCode::ArrowLeft, 106 => KeyCode::ArrowRight, 107 => KeyCode::End, 108 => KeyCode::ArrowDown, 109 => KeyCode::PageDown, 110 => KeyCode::Insert, 111 => KeyCode::Delete, // 112 => KeyCode::MACRO, 113 => KeyCode::AudioVolumeMute, 114 => KeyCode::AudioVolumeDown, 115 => KeyCode::AudioVolumeUp, // 116 => KeyCode::POWER, 117 => KeyCode::NumpadEqual, // 118 => KeyCode::KPPLUSMINUS, 119 => KeyCode::Pause, // 120 => KeyCode::SCALE, 121 => KeyCode::NumpadComma, 122 => KeyCode::Lang1, 123 => KeyCode::Lang2, 124 => KeyCode::IntlYen, 125 => KeyCode::SuperLeft, 126 => KeyCode::SuperRight, 127 => KeyCode::ContextMenu, // 128 => KeyCode::STOP, // 129 => KeyCode::AGAIN, // 130 => KeyCode::PROPS, // 131 => KeyCode::UNDO, // 132 => KeyCode::FRONT, // 133 => KeyCode::COPY, // 134 => KeyCode::OPEN, // 135 => KeyCode::PASTE, // 136 => KeyCode::FIND, // 137 => KeyCode::CUT, // 138 => KeyCode::HELP, // 139 => KeyCode::MENU, // 140 => KeyCode::CALC, // 141 => KeyCode::SETUP, // 142 => KeyCode::SLEEP, // 143 => KeyCode::WAKEUP, // 144 => KeyCode::FILE, // 145 => KeyCode::SENDFILE, // 146 => KeyCode::DELETEFILE, // 147 => KeyCode::XFER, // 148 => KeyCode::PROG1, // 149 => KeyCode::PROG2, // 150 => KeyCode::WWW, // 151 => KeyCode::MSDOS, // 152 => KeyCode::COFFEE, // 153 => KeyCode::ROTATE_DISPLAY, // 154 => KeyCode::CYCLEWINDOWS, // 155 => KeyCode::MAIL, // 156 => KeyCode::BOOKMARKS, // 157 => KeyCode::COMPUTER, // 158 => KeyCode::BACK, // 159 => KeyCode::FORWARD, // 160 => KeyCode::CLOSECD, // 161 => KeyCode::EJECTCD, // 162 => KeyCode::EJECTCLOSECD, 163 => KeyCode::MediaTrackNext, 164 => KeyCode::MediaPlayPause, 165 => KeyCode::MediaTrackPrevious, 166 => KeyCode::MediaStop, // 167 => KeyCode::RECORD, // 168 => KeyCode::REWIND, // 169 => KeyCode::PHONE, // 170 => KeyCode::ISO, // 171 => KeyCode::CONFIG, // 172 => KeyCode::HOMEPAGE, // 173 => KeyCode::REFRESH, // 174 => KeyCode::EXIT, // 175 => KeyCode::MOVE, // 176 => KeyCode::EDIT, // 177 => KeyCode::SCROLLUP, // 178 => KeyCode::SCROLLDOWN, // 179 => KeyCode::KPLEFTPAREN, // 180 => KeyCode::KPRIGHTPAREN, // 181 => KeyCode::NEW, // 182 => KeyCode::REDO, 183 => KeyCode::F13, 184 => KeyCode::F14, 185 => KeyCode::F15, 186 => KeyCode::F16, 187 => KeyCode::F17, 188 => KeyCode::F18, 189 => KeyCode::F19, 190 => KeyCode::F20, 191 => KeyCode::F21, 192 => KeyCode::F22, 193 => KeyCode::F23, 194 => KeyCode::F24, // 200 => KeyCode::PLAYCD, // 201 => KeyCode::PAUSECD, // 202 => KeyCode::PROG3, // 203 => KeyCode::PROG4, // 204 => KeyCode::DASHBOARD, // 205 => KeyCode::SUSPEND, // 206 => KeyCode::CLOSE, // 207 => KeyCode::PLAY, // 208 => KeyCode::FASTFORWARD, // 209 => KeyCode::BASSBOOST, // 210 => KeyCode::PRINT, // 211 => KeyCode::HP, // 212 => KeyCode::CAMERA, // 213 => KeyCode::SOUND, // 214 => KeyCode::QUESTION, // 215 => KeyCode::EMAIL, // 216 => KeyCode::CHAT, // 217 => KeyCode::SEARCH, // 218 => KeyCode::CONNECT, // 219 => KeyCode::FINANCE, // 220 => KeyCode::SPORT, // 221 => KeyCode::SHOP, // 222 => KeyCode::ALTERASE, // 223 => KeyCode::CANCEL, // 224 => KeyCode::BRIGHTNESSDOW, // 225 => KeyCode::BRIGHTNESSU, // 226 => KeyCode::MEDIA, // 227 => KeyCode::SWITCHVIDEOMODE, // 228 => KeyCode::KBDILLUMTOGGLE, // 229 => KeyCode::KBDILLUMDOWN, // 230 => KeyCode::KBDILLUMUP, // 231 => KeyCode::SEND, // 232 => KeyCode::REPLY, // 233 => KeyCode::FORWARDMAIL, // 234 => KeyCode::SAVE, // 235 => KeyCode::DOCUMENTS, // 236 => KeyCode::BATTERY, // 237 => KeyCode::BLUETOOTH, // 238 => KeyCode::WLAN, // 239 => KeyCode::UWB, 240 => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified), // 241 => KeyCode::VIDEO_NEXT, // 242 => KeyCode::VIDEO_PREV, // 243 => KeyCode::BRIGHTNESS_CYCLE, // 244 => KeyCode::BRIGHTNESS_AUTO, // 245 => KeyCode::DISPLAY_OFF, // 246 => KeyCode::WWAN, // 247 => KeyCode::RFKILL, // 248 => KeyCode::KEY_MICMUTE, _ => return PhysicalKey::Unidentified(NativeKeyCode::Xkb(scancode)), }) } pub fn physicalkey_to_scancode(key: PhysicalKey) -> Option { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => { return match code { NativeKeyCode::Unidentified => Some(240), NativeKeyCode::Xkb(raw) => Some(raw), _ => None, }; } }; match code { KeyCode::Escape => Some(1), KeyCode::Digit1 => Some(2), KeyCode::Digit2 => Some(3), KeyCode::Digit3 => Some(4), KeyCode::Digit4 => Some(5), KeyCode::Digit5 => Some(6), KeyCode::Digit6 => Some(7), KeyCode::Digit7 => Some(8), KeyCode::Digit8 => Some(9), KeyCode::Digit9 => Some(10), KeyCode::Digit0 => Some(11), KeyCode::Minus => Some(12), KeyCode::Equal => Some(13), KeyCode::Backspace => Some(14), KeyCode::Tab => Some(15), KeyCode::KeyQ => Some(16), KeyCode::KeyW => Some(17), KeyCode::KeyE => Some(18), KeyCode::KeyR => Some(19), KeyCode::KeyT => Some(20), KeyCode::KeyY => Some(21), KeyCode::KeyU => Some(22), KeyCode::KeyI => Some(23), KeyCode::KeyO => Some(24), KeyCode::KeyP => Some(25), KeyCode::BracketLeft => Some(26), KeyCode::BracketRight => Some(27), KeyCode::Enter => Some(28), KeyCode::ControlLeft => Some(29), KeyCode::KeyA => Some(30), KeyCode::KeyS => Some(31), KeyCode::KeyD => Some(32), KeyCode::KeyF => Some(33), KeyCode::KeyG => Some(34), KeyCode::KeyH => Some(35), KeyCode::KeyJ => Some(36), KeyCode::KeyK => Some(37), KeyCode::KeyL => Some(38), KeyCode::Semicolon => Some(39), KeyCode::Quote => Some(40), KeyCode::Backquote => Some(41), KeyCode::ShiftLeft => Some(42), KeyCode::Backslash => Some(43), KeyCode::KeyZ => Some(44), KeyCode::KeyX => Some(45), KeyCode::KeyC => Some(46), KeyCode::KeyV => Some(47), KeyCode::KeyB => Some(48), KeyCode::KeyN => Some(49), KeyCode::KeyM => Some(50), KeyCode::Comma => Some(51), KeyCode::Period => Some(52), KeyCode::Slash => Some(53), KeyCode::ShiftRight => Some(54), KeyCode::NumpadMultiply => Some(55), KeyCode::AltLeft => Some(56), KeyCode::Space => Some(57), KeyCode::CapsLock => Some(58), KeyCode::F1 => Some(59), KeyCode::F2 => Some(60), KeyCode::F3 => Some(61), KeyCode::F4 => Some(62), KeyCode::F5 => Some(63), KeyCode::F6 => Some(64), KeyCode::F7 => Some(65), KeyCode::F8 => Some(66), KeyCode::F9 => Some(67), KeyCode::F10 => Some(68), KeyCode::NumLock => Some(69), KeyCode::ScrollLock => Some(70), KeyCode::Numpad7 => Some(71), KeyCode::Numpad8 => Some(72), KeyCode::Numpad9 => Some(73), KeyCode::NumpadSubtract => Some(74), KeyCode::Numpad4 => Some(75), KeyCode::Numpad5 => Some(76), KeyCode::Numpad6 => Some(77), KeyCode::NumpadAdd => Some(78), KeyCode::Numpad1 => Some(79), KeyCode::Numpad2 => Some(80), KeyCode::Numpad3 => Some(81), KeyCode::Numpad0 => Some(82), KeyCode::NumpadDecimal => Some(83), KeyCode::Lang5 => Some(85), KeyCode::IntlBackslash => Some(86), KeyCode::F11 => Some(87), KeyCode::F12 => Some(88), KeyCode::IntlRo => Some(89), KeyCode::Lang3 => Some(90), KeyCode::Lang4 => Some(91), KeyCode::Convert => Some(92), KeyCode::KanaMode => Some(93), KeyCode::NonConvert => Some(94), KeyCode::NumpadEnter => Some(96), KeyCode::ControlRight => Some(97), KeyCode::NumpadDivide => Some(98), KeyCode::PrintScreen => Some(99), KeyCode::AltRight => Some(100), KeyCode::Home => Some(102), KeyCode::ArrowUp => Some(103), KeyCode::PageUp => Some(104), KeyCode::ArrowLeft => Some(105), KeyCode::ArrowRight => Some(106), KeyCode::End => Some(107), KeyCode::ArrowDown => Some(108), KeyCode::PageDown => Some(109), KeyCode::Insert => Some(110), KeyCode::Delete => Some(111), KeyCode::AudioVolumeMute => Some(113), KeyCode::AudioVolumeDown => Some(114), KeyCode::AudioVolumeUp => Some(115), KeyCode::NumpadEqual => Some(117), KeyCode::Pause => Some(119), KeyCode::NumpadComma => Some(121), KeyCode::Lang1 => Some(122), KeyCode::Lang2 => Some(123), KeyCode::IntlYen => Some(124), KeyCode::SuperLeft => Some(125), KeyCode::SuperRight => Some(126), KeyCode::ContextMenu => Some(127), KeyCode::MediaTrackNext => Some(163), KeyCode::MediaPlayPause => Some(164), KeyCode::MediaTrackPrevious => Some(165), KeyCode::MediaStop => Some(166), KeyCode::F13 => Some(183), KeyCode::F14 => Some(184), KeyCode::F15 => Some(185), KeyCode::F16 => Some(186), KeyCode::F17 => Some(187), KeyCode::F18 => Some(188), KeyCode::F19 => Some(189), KeyCode::F20 => Some(190), KeyCode::F21 => Some(191), KeyCode::F22 => Some(192), KeyCode::F23 => Some(193), KeyCode::F24 => Some(194), _ => None, } } pub fn keysym_to_key(keysym: u32) -> Key { use xkbcommon_dl::keysyms; Key::Named(match keysym { // TTY function keys keysyms::BackSpace => NamedKey::Backspace, keysyms::Tab => NamedKey::Tab, // keysyms::Linefeed => NamedKey::Linefeed, keysyms::Clear => NamedKey::Clear, keysyms::Return => NamedKey::Enter, keysyms::Pause => NamedKey::Pause, keysyms::Scroll_Lock => NamedKey::ScrollLock, keysyms::Sys_Req => NamedKey::PrintScreen, keysyms::Escape => NamedKey::Escape, keysyms::Delete => NamedKey::Delete, // IME keys keysyms::Multi_key => NamedKey::Compose, keysyms::Codeinput => NamedKey::CodeInput, keysyms::SingleCandidate => NamedKey::SingleCandidate, keysyms::MultipleCandidate => NamedKey::AllCandidates, keysyms::PreviousCandidate => NamedKey::PreviousCandidate, // Japanese keys keysyms::Kanji => NamedKey::KanjiMode, keysyms::Muhenkan => NamedKey::NonConvert, keysyms::Henkan_Mode => NamedKey::Convert, keysyms::Romaji => NamedKey::Romaji, keysyms::Hiragana => NamedKey::Hiragana, keysyms::Hiragana_Katakana => NamedKey::HiraganaKatakana, keysyms::Zenkaku => NamedKey::Zenkaku, keysyms::Hankaku => NamedKey::Hankaku, keysyms::Zenkaku_Hankaku => NamedKey::ZenkakuHankaku, // keysyms::Touroku => NamedKey::Touroku, // keysyms::Massyo => NamedKey::Massyo, keysyms::Kana_Lock => NamedKey::KanaMode, keysyms::Kana_Shift => NamedKey::KanaMode, keysyms::Eisu_Shift => NamedKey::Alphanumeric, keysyms::Eisu_toggle => NamedKey::Alphanumeric, // NOTE: The next three items are aliases for values we've already mapped. // keysyms::Kanji_Bangou => NamedKey::CodeInput, // keysyms::Zen_Koho => NamedKey::AllCandidates, // keysyms::Mae_Koho => NamedKey::PreviousCandidate, // Cursor control & motion keysyms::Home => NamedKey::Home, keysyms::Left => NamedKey::ArrowLeft, keysyms::Up => NamedKey::ArrowUp, keysyms::Right => NamedKey::ArrowRight, keysyms::Down => NamedKey::ArrowDown, // keysyms::Prior => NamedKey::PageUp, keysyms::Page_Up => NamedKey::PageUp, // keysyms::Next => NamedKey::PageDown, keysyms::Page_Down => NamedKey::PageDown, keysyms::End => NamedKey::End, // keysyms::Begin => NamedKey::Begin, // Misc. functions keysyms::Select => NamedKey::Select, keysyms::Print => NamedKey::PrintScreen, keysyms::Execute => NamedKey::Execute, keysyms::Insert => NamedKey::Insert, keysyms::Undo => NamedKey::Undo, keysyms::Redo => NamedKey::Redo, keysyms::Menu => NamedKey::ContextMenu, keysyms::Find => NamedKey::Find, keysyms::Cancel => NamedKey::Cancel, keysyms::Help => NamedKey::Help, keysyms::Break => NamedKey::Pause, keysyms::Mode_switch => NamedKey::ModeChange, // keysyms::script_switch => NamedKey::ModeChange, keysyms::Num_Lock => NamedKey::NumLock, // Keypad keys // keysyms::KP_Space => return Key::Character(" "), keysyms::KP_Tab => NamedKey::Tab, keysyms::KP_Enter => NamedKey::Enter, keysyms::KP_F1 => NamedKey::F1, keysyms::KP_F2 => NamedKey::F2, keysyms::KP_F3 => NamedKey::F3, keysyms::KP_F4 => NamedKey::F4, keysyms::KP_Home => NamedKey::Home, keysyms::KP_Left => NamedKey::ArrowLeft, keysyms::KP_Up => NamedKey::ArrowUp, keysyms::KP_Right => NamedKey::ArrowRight, keysyms::KP_Down => NamedKey::ArrowDown, // keysyms::KP_Prior => NamedKey::PageUp, keysyms::KP_Page_Up => NamedKey::PageUp, // keysyms::KP_Next => NamedKey::PageDown, keysyms::KP_Page_Down => NamedKey::PageDown, keysyms::KP_End => NamedKey::End, // This is the key labeled "5" on the numpad when NumLock is off. // keysyms::KP_Begin => NamedKey::Begin, keysyms::KP_Insert => NamedKey::Insert, keysyms::KP_Delete => NamedKey::Delete, // keysyms::KP_Equal => NamedKey::Equal, // keysyms::KP_Multiply => NamedKey::Multiply, // keysyms::KP_Add => NamedKey::Add, // keysyms::KP_Separator => NamedKey::Separator, // keysyms::KP_Subtract => NamedKey::Subtract, // keysyms::KP_Decimal => NamedKey::Decimal, // keysyms::KP_Divide => NamedKey::Divide, // keysyms::KP_0 => return Key::Character("0"), // keysyms::KP_1 => return Key::Character("1"), // keysyms::KP_2 => return Key::Character("2"), // keysyms::KP_3 => return Key::Character("3"), // keysyms::KP_4 => return Key::Character("4"), // keysyms::KP_5 => return Key::Character("5"), // keysyms::KP_6 => return Key::Character("6"), // keysyms::KP_7 => return Key::Character("7"), // keysyms::KP_8 => return Key::Character("8"), // keysyms::KP_9 => return Key::Character("9"), // Function keys keysyms::F1 => NamedKey::F1, keysyms::F2 => NamedKey::F2, keysyms::F3 => NamedKey::F3, keysyms::F4 => NamedKey::F4, keysyms::F5 => NamedKey::F5, keysyms::F6 => NamedKey::F6, keysyms::F7 => NamedKey::F7, keysyms::F8 => NamedKey::F8, keysyms::F9 => NamedKey::F9, keysyms::F10 => NamedKey::F10, keysyms::F11 => NamedKey::F11, keysyms::F12 => NamedKey::F12, keysyms::F13 => NamedKey::F13, keysyms::F14 => NamedKey::F14, keysyms::F15 => NamedKey::F15, keysyms::F16 => NamedKey::F16, keysyms::F17 => NamedKey::F17, keysyms::F18 => NamedKey::F18, keysyms::F19 => NamedKey::F19, keysyms::F20 => NamedKey::F20, keysyms::F21 => NamedKey::F21, keysyms::F22 => NamedKey::F22, keysyms::F23 => NamedKey::F23, keysyms::F24 => NamedKey::F24, keysyms::F25 => NamedKey::F25, keysyms::F26 => NamedKey::F26, keysyms::F27 => NamedKey::F27, keysyms::F28 => NamedKey::F28, keysyms::F29 => NamedKey::F29, keysyms::F30 => NamedKey::F30, keysyms::F31 => NamedKey::F31, keysyms::F32 => NamedKey::F32, keysyms::F33 => NamedKey::F33, keysyms::F34 => NamedKey::F34, keysyms::F35 => NamedKey::F35, // Modifiers keysyms::Shift_L => NamedKey::Shift, keysyms::Shift_R => NamedKey::Shift, keysyms::Control_L => NamedKey::Control, keysyms::Control_R => NamedKey::Control, keysyms::Caps_Lock => NamedKey::CapsLock, // keysyms::Shift_Lock => NamedKey::ShiftLock, // keysyms::Meta_L => NamedKey::Meta, // keysyms::Meta_R => NamedKey::Meta, keysyms::Alt_L => NamedKey::Alt, keysyms::Alt_R => NamedKey::Alt, keysyms::Super_L => NamedKey::Super, keysyms::Super_R => NamedKey::Super, keysyms::Hyper_L => NamedKey::Hyper, keysyms::Hyper_R => NamedKey::Hyper, // XKB function and modifier keys // keysyms::ISO_Lock => NamedKey::IsoLock, // keysyms::ISO_Level2_Latch => NamedKey::IsoLevel2Latch, keysyms::ISO_Level3_Shift => NamedKey::AltGraph, keysyms::ISO_Level3_Latch => NamedKey::AltGraph, keysyms::ISO_Level3_Lock => NamedKey::AltGraph, // keysyms::ISO_Level5_Shift => NamedKey::IsoLevel5Shift, // keysyms::ISO_Level5_Latch => NamedKey::IsoLevel5Latch, // keysyms::ISO_Level5_Lock => NamedKey::IsoLevel5Lock, // keysyms::ISO_Group_Shift => NamedKey::IsoGroupShift, // keysyms::ISO_Group_Latch => NamedKey::IsoGroupLatch, // keysyms::ISO_Group_Lock => NamedKey::IsoGroupLock, keysyms::ISO_Next_Group => NamedKey::GroupNext, // keysyms::ISO_Next_Group_Lock => NamedKey::GroupNextLock, keysyms::ISO_Prev_Group => NamedKey::GroupPrevious, // keysyms::ISO_Prev_Group_Lock => NamedKey::GroupPreviousLock, keysyms::ISO_First_Group => NamedKey::GroupFirst, // keysyms::ISO_First_Group_Lock => NamedKey::GroupFirstLock, keysyms::ISO_Last_Group => NamedKey::GroupLast, // keysyms::ISO_Last_Group_Lock => NamedKey::GroupLastLock, // keysyms::ISO_Left_Tab => NamedKey::Tab, // keysyms::ISO_Move_Line_Up => NamedKey::IsoMoveLineUp, // keysyms::ISO_Move_Line_Down => NamedKey::IsoMoveLineDown, // keysyms::ISO_Partial_Line_Up => NamedKey::IsoPartialLineUp, // keysyms::ISO_Partial_Line_Down => NamedKey::IsoPartialLineDown, // keysyms::ISO_Partial_Space_Left => NamedKey::IsoPartialSpaceLeft, // keysyms::ISO_Partial_Space_Right => NamedKey::IsoPartialSpaceRight, // keysyms::ISO_Set_Margin_Left => NamedKey::IsoSetMarginLeft, // keysyms::ISO_Set_Margin_Right => NamedKey::IsoSetMarginRight, // keysyms::ISO_Release_Margin_Left => NamedKey::IsoReleaseMarginLeft, // keysyms::ISO_Release_Margin_Right => NamedKey::IsoReleaseMarginRight, // keysyms::ISO_Release_Both_Margins => NamedKey::IsoReleaseBothMargins, // keysyms::ISO_Fast_Cursor_Left => NamedKey::IsoFastCursorLeft, // keysyms::ISO_Fast_Cursor_Right => NamedKey::IsoFastCursorRight, // keysyms::ISO_Fast_Cursor_Up => NamedKey::IsoFastCursorUp, // keysyms::ISO_Fast_Cursor_Down => NamedKey::IsoFastCursorDown, // keysyms::ISO_Continuous_Underline => NamedKey::IsoContinuousUnderline, // keysyms::ISO_Discontinuous_Underline => NamedKey::IsoDiscontinuousUnderline, // keysyms::ISO_Emphasize => NamedKey::IsoEmphasize, // keysyms::ISO_Center_Object => NamedKey::IsoCenterObject, keysyms::ISO_Enter => NamedKey::Enter, // dead_grave..dead_currency // dead_lowline..dead_longsolidusoverlay // dead_a..dead_capital_schwa // dead_greek // First_Virtual_Screen..Terminate_Server // AccessX_Enable..AudibleBell_Enable // Pointer_Left..Pointer_Drag5 // Pointer_EnableKeys..Pointer_DfltBtnPrev // ch..C_H // 3270 terminal keys // keysyms::3270_Duplicate => NamedKey::Duplicate, // keysyms::3270_FieldMark => NamedKey::FieldMark, // keysyms::3270_Right2 => NamedKey::Right2, // keysyms::3270_Left2 => NamedKey::Left2, // keysyms::3270_BackTab => NamedKey::BackTab, keysyms::_3270_EraseEOF => NamedKey::EraseEof, // keysyms::3270_EraseInput => NamedKey::EraseInput, // keysyms::3270_Reset => NamedKey::Reset, // keysyms::3270_Quit => NamedKey::Quit, // keysyms::3270_PA1 => NamedKey::Pa1, // keysyms::3270_PA2 => NamedKey::Pa2, // keysyms::3270_PA3 => NamedKey::Pa3, // keysyms::3270_Test => NamedKey::Test, keysyms::_3270_Attn => NamedKey::Attn, // keysyms::3270_CursorBlink => NamedKey::CursorBlink, // keysyms::3270_AltCursor => NamedKey::AltCursor, // keysyms::3270_KeyClick => NamedKey::KeyClick, // keysyms::3270_Jump => NamedKey::Jump, // keysyms::3270_Ident => NamedKey::Ident, // keysyms::3270_Rule => NamedKey::Rule, // keysyms::3270_Copy => NamedKey::Copy, keysyms::_3270_Play => NamedKey::Play, // keysyms::3270_Setup => NamedKey::Setup, // keysyms::3270_Record => NamedKey::Record, // keysyms::3270_ChangeScreen => NamedKey::ChangeScreen, // keysyms::3270_DeleteWord => NamedKey::DeleteWord, keysyms::_3270_ExSelect => NamedKey::ExSel, keysyms::_3270_CursorSelect => NamedKey::CrSel, keysyms::_3270_PrintScreen => NamedKey::PrintScreen, keysyms::_3270_Enter => NamedKey::Enter, keysyms::space => NamedKey::Space, // exclam..Sinh_kunddaliya // XFree86 // keysyms::XF86_ModeLock => NamedKey::ModeLock, // XFree86 - Backlight controls keysyms::XF86_MonBrightnessUp => NamedKey::BrightnessUp, keysyms::XF86_MonBrightnessDown => NamedKey::BrightnessDown, // keysyms::XF86_KbdLightOnOff => NamedKey::LightOnOff, // keysyms::XF86_KbdBrightnessUp => NamedKey::KeyboardBrightnessUp, // keysyms::XF86_KbdBrightnessDown => NamedKey::KeyboardBrightnessDown, // XFree86 - "Internet" keysyms::XF86_Standby => NamedKey::Standby, keysyms::XF86_AudioLowerVolume => NamedKey::AudioVolumeDown, keysyms::XF86_AudioRaiseVolume => NamedKey::AudioVolumeUp, keysyms::XF86_AudioPlay => NamedKey::MediaPlay, keysyms::XF86_AudioStop => NamedKey::MediaStop, keysyms::XF86_AudioPrev => NamedKey::MediaTrackPrevious, keysyms::XF86_AudioNext => NamedKey::MediaTrackNext, keysyms::XF86_HomePage => NamedKey::BrowserHome, keysyms::XF86_Mail => NamedKey::LaunchMail, // keysyms::XF86_Start => NamedKey::Start, keysyms::XF86_Search => NamedKey::BrowserSearch, keysyms::XF86_AudioRecord => NamedKey::MediaRecord, // XFree86 - PDA keysyms::XF86_Calculator => NamedKey::LaunchApplication2, // keysyms::XF86_Memo => NamedKey::Memo, // keysyms::XF86_ToDoList => NamedKey::ToDoList, keysyms::XF86_Calendar => NamedKey::LaunchCalendar, keysyms::XF86_PowerDown => NamedKey::Power, // keysyms::XF86_ContrastAdjust => NamedKey::AdjustContrast, // keysyms::XF86_RockerUp => NamedKey::RockerUp, // keysyms::XF86_RockerDown => NamedKey::RockerDown, // keysyms::XF86_RockerEnter => NamedKey::RockerEnter, // XFree86 - More "Internet" keysyms::XF86_Back => NamedKey::BrowserBack, keysyms::XF86_Forward => NamedKey::BrowserForward, // keysyms::XF86_Stop => NamedKey::Stop, keysyms::XF86_Refresh => NamedKey::BrowserRefresh, keysyms::XF86_PowerOff => NamedKey::Power, keysyms::XF86_WakeUp => NamedKey::WakeUp, keysyms::XF86_Eject => NamedKey::Eject, keysyms::XF86_ScreenSaver => NamedKey::LaunchScreenSaver, keysyms::XF86_WWW => NamedKey::LaunchWebBrowser, keysyms::XF86_Sleep => NamedKey::Standby, keysyms::XF86_Favorites => NamedKey::BrowserFavorites, keysyms::XF86_AudioPause => NamedKey::MediaPause, // keysyms::XF86_AudioMedia => NamedKey::AudioMedia, keysyms::XF86_MyComputer => NamedKey::LaunchApplication1, // keysyms::XF86_VendorHome => NamedKey::VendorHome, // keysyms::XF86_LightBulb => NamedKey::LightBulb, // keysyms::XF86_Shop => NamedKey::BrowserShop, // keysyms::XF86_History => NamedKey::BrowserHistory, // keysyms::XF86_OpenURL => NamedKey::OpenUrl, // keysyms::XF86_AddFavorite => NamedKey::AddFavorite, // keysyms::XF86_HotLinks => NamedKey::HotLinks, // keysyms::XF86_BrightnessAdjust => NamedKey::BrightnessAdjust, // keysyms::XF86_Finance => NamedKey::BrowserFinance, // keysyms::XF86_Community => NamedKey::BrowserCommunity, keysyms::XF86_AudioRewind => NamedKey::MediaRewind, // keysyms::XF86_BackForward => Key::???, // XF86_Launch0..XF86_LaunchF // XF86_ApplicationLeft..XF86_CD keysyms::XF86_Calculater => NamedKey::LaunchApplication2, // Nice typo, libxkbcommon :) // XF86_Clear keysyms::XF86_Close => NamedKey::Close, keysyms::XF86_Copy => NamedKey::Copy, keysyms::XF86_Cut => NamedKey::Cut, // XF86_Display..XF86_Documents keysyms::XF86_Excel => NamedKey::LaunchSpreadsheet, // XF86_Explorer..XF86iTouch keysyms::XF86_LogOff => NamedKey::LogOff, // XF86_Market..XF86_MenuPB keysyms::XF86_MySites => NamedKey::BrowserFavorites, keysyms::XF86_New => NamedKey::New, // XF86_News..XF86_OfficeHome keysyms::XF86_Open => NamedKey::Open, // XF86_Option keysyms::XF86_Paste => NamedKey::Paste, keysyms::XF86_Phone => NamedKey::LaunchPhone, // XF86_Q keysyms::XF86_Reply => NamedKey::MailReply, keysyms::XF86_Reload => NamedKey::BrowserRefresh, // XF86_RotateWindows..XF86_RotationKB keysyms::XF86_Save => NamedKey::Save, // XF86_ScrollUp..XF86_ScrollClick keysyms::XF86_Send => NamedKey::MailSend, keysyms::XF86_Spell => NamedKey::SpellCheck, keysyms::XF86_SplitScreen => NamedKey::SplitScreenToggle, // XF86_Support..XF86_User2KB keysyms::XF86_Video => NamedKey::LaunchMediaPlayer, // XF86_WheelButton keysyms::XF86_Word => NamedKey::LaunchWordProcessor, // XF86_Xfer keysyms::XF86_ZoomIn => NamedKey::ZoomIn, keysyms::XF86_ZoomOut => NamedKey::ZoomOut, // XF86_Away..XF86_Messenger keysyms::XF86_WebCam => NamedKey::LaunchWebCam, keysyms::XF86_MailForward => NamedKey::MailForward, // XF86_Pictures keysyms::XF86_Music => NamedKey::LaunchMusicPlayer, // XF86_Battery..XF86_UWB // keysyms::XF86_AudioForward => NamedKey::MediaFastForward, // XF86_AudioRepeat keysyms::XF86_AudioRandomPlay => NamedKey::RandomToggle, keysyms::XF86_Subtitle => NamedKey::Subtitle, keysyms::XF86_AudioCycleTrack => NamedKey::MediaAudioTrack, // XF86_CycleAngle..XF86_Blue // keysyms::XF86_Suspend => NamedKey::Standby, keysyms::XF86_Hibernate => NamedKey::Hibernate, // XF86_TouchpadToggle..XF86_TouchpadOff // keysyms::XF86_AudioMute => NamedKey::AudioVolumeMute, // XF86_Switch_VT_1..XF86_Switch_VT_12 // XF86_Ungrab..XF86_ClearGrab keysyms::XF86_Next_VMode => NamedKey::VideoModeNext, // keysyms::XF86_Prev_VMode => NamedKey::VideoModePrevious, // XF86_LogWindowTree..XF86_LogGrabInfo // SunFA_Grave..SunFA_Cedilla // keysyms::SunF36 => NamedKey::F36 | NamedKey::F11, // keysyms::SunF37 => NamedKey::F37 | NamedKey::F12, // keysyms::SunSys_Req => NamedKey::PrintScreen, // The next couple of xkb (until SunStop) are already handled. // SunPrint_Screen..SunPageDown // SunUndo..SunFront keysyms::SUN_Copy => NamedKey::Copy, keysyms::SUN_Open => NamedKey::Open, keysyms::SUN_Paste => NamedKey::Paste, keysyms::SUN_Cut => NamedKey::Cut, // SunPowerSwitch keysyms::SUN_AudioLowerVolume => NamedKey::AudioVolumeDown, keysyms::SUN_AudioMute => NamedKey::AudioVolumeMute, keysyms::SUN_AudioRaiseVolume => NamedKey::AudioVolumeUp, // SUN_VideoDegauss keysyms::SUN_VideoLowerBrightness => NamedKey::BrightnessDown, keysyms::SUN_VideoRaiseBrightness => NamedKey::BrightnessUp, // SunPowerSwitchShift // 0 => return Key::Unidentified(NativeKey::Unidentified), _ => return Key::Unidentified(NativeKey::Xkb(keysym)), }) } pub fn keysym_location(keysym: u32) -> KeyLocation { use xkbcommon_dl::keysyms; match keysym { keysyms::Shift_L | keysyms::Control_L | keysyms::Meta_L | keysyms::Alt_L | keysyms::Super_L | keysyms::Hyper_L => KeyLocation::Left, keysyms::Shift_R | keysyms::Control_R | keysyms::Meta_R | keysyms::Alt_R | keysyms::Super_R | keysyms::Hyper_R => KeyLocation::Right, keysyms::KP_0 | keysyms::KP_1 | keysyms::KP_2 | keysyms::KP_3 | keysyms::KP_4 | keysyms::KP_5 | keysyms::KP_6 | keysyms::KP_7 | keysyms::KP_8 | keysyms::KP_9 | keysyms::KP_Space | keysyms::KP_Tab | keysyms::KP_Enter | keysyms::KP_F1 | keysyms::KP_F2 | keysyms::KP_F3 | keysyms::KP_F4 | keysyms::KP_Home | keysyms::KP_Left | keysyms::KP_Up | keysyms::KP_Right | keysyms::KP_Down | keysyms::KP_Page_Up | keysyms::KP_Page_Down | keysyms::KP_End | keysyms::KP_Begin | keysyms::KP_Insert | keysyms::KP_Delete | keysyms::KP_Equal | keysyms::KP_Multiply | keysyms::KP_Add | keysyms::KP_Separator | keysyms::KP_Subtract | keysyms::KP_Decimal | keysyms::KP_Divide => KeyLocation::Numpad, _ => KeyLocation::Standard, } } #[derive(Debug)] pub struct XkbKeymap { keymap: NonNull, _mods_indices: ModsIndices, pub _core_keyboard_id: i32, } impl XkbKeymap { #[cfg(wayland_platform)] pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option { let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? }; let keymap = unsafe { let keymap = (XKBH.xkb_keymap_new_from_string)( (*context).as_ptr(), map.as_ptr() as *const _, xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ); NonNull::new(keymap)? }; Some(Self::new_inner(keymap, 0)) } #[cfg(x11_platform)] pub fn from_x11_keymap( context: &XkbContext, xcb: *mut xcb_connection_t, core_keyboard_id: i32, ) -> Option { let keymap = unsafe { (XKBXH.xkb_x11_keymap_new_from_device)( context.as_ptr(), xcb, core_keyboard_id, xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ) }; let keymap = NonNull::new(keymap)?; Some(Self::new_inner(keymap, core_keyboard_id)) } fn new_inner(keymap: NonNull, _core_keyboard_id: i32) -> Self { let mods_indices = ModsIndices { shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT), caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS), ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL), alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT), num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM), mod3: mod_index_for_name(keymap, b"Mod3\0"), logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO), mod5: mod_index_for_name(keymap, b"Mod5\0"), }; Self { keymap, _mods_indices: mods_indices, _core_keyboard_id, } } #[cfg(x11_platform)] pub fn mods_indices(&self) -> ModsIndices { self._mods_indices } pub fn first_keysym_by_level( &mut self, layout: xkb_layout_index_t, keycode: xkb_keycode_t, ) -> xkb_keysym_t { unsafe { let mut keysyms = ptr::null(); let count = (XKBH.xkb_keymap_key_get_syms_by_level)( self.keymap.as_ptr(), keycode, layout, // NOTE: The level should be zero to ignore modifiers. 0, &mut keysyms, ); if count == 1 { *keysyms } else { 0 } } } /// Check whether the given key repeats. pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool { unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 } } } impl Drop for XkbKeymap { fn drop(&mut self) { unsafe { (XKBH.xkb_keymap_unref)(self.keymap.as_ptr()); }; } } impl Deref for XkbKeymap { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.keymap } } /// Modifier index in the keymap. #[derive(Default, Debug, Clone, Copy)] pub struct ModsIndices { pub shift: Option, pub caps: Option, pub ctrl: Option, pub alt: Option, pub num: Option, pub mod3: Option, pub logo: Option, pub mod5: Option, } fn mod_index_for_name(keymap: NonNull, name: &[u8]) -> Option { unsafe { let mod_index = (XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char); if mod_index == XKB_MOD_INVALID { None } else { Some(mod_index) } } } winit-0.29.15/src/platform_impl/linux/common/xkb/mod.rs000064400000000000000000000351201046102023000211310ustar 00000000000000use std::ops::Deref; use std::os::raw::c_char; use std::ptr::{self, NonNull}; use std::sync::atomic::{AtomicBool, Ordering}; use log::warn; use once_cell::sync::Lazy; use smol_str::SmolStr; #[cfg(wayland_platform)] use std::os::unix::io::OwnedFd; use xkbcommon_dl::{ self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon, XkbCommonCompose, }; #[cfg(x11_platform)] use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; use crate::event::ElementState; use crate::event::KeyEvent; use crate::keyboard::{Key, KeyLocation}; use crate::platform_impl::KeyEventExtra; mod compose; mod keymap; mod state; use compose::{ComposeStatus, XkbComposeState, XkbComposeTable}; use keymap::XkbKeymap; #[cfg(x11_platform)] pub use keymap::raw_keycode_to_physicalkey; pub use keymap::{physicalkey_to_scancode, scancode_to_keycode}; pub use state::XkbState; // TODO: Wire this up without using a static `AtomicBool`. static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); #[cfg(feature = "x11")] static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); #[inline(always)] pub fn reset_dead_keys() { RESET_DEAD_KEYS.store(true, Ordering::SeqCst); } #[derive(Debug)] pub enum Error { /// libxkbcommon is not available XKBNotFound, } #[derive(Debug)] pub struct Context { // NOTE: field order matters. #[cfg(x11_platform)] pub core_keyboard_id: i32, state: Option, keymap: Option, compose_state1: Option, compose_state2: Option, _compose_table: Option, context: XkbContext, scratch_buffer: Vec, } impl Context { pub fn new() -> Result { if xkb::xkbcommon_option().is_none() { return Err(Error::XKBNotFound); } let context = XkbContext::new()?; let mut compose_table = XkbComposeTable::new(&context); let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state()); let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state()); // Disable compose if anything compose related failed to initialize. if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() { compose_state2 = None; compose_state1 = None; compose_table = None; } Ok(Self { state: None, keymap: None, compose_state1, compose_state2, #[cfg(x11_platform)] core_keyboard_id: 0, _compose_table: compose_table, context, scratch_buffer: Vec::with_capacity(8), }) } #[cfg(feature = "x11")] pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result { let result = unsafe { (XKBXH.xkb_x11_setup_xkb_extension)( xcb, 1, 2, xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ) }; if result != 1 { return Err(Error::XKBNotFound); } let mut this = Self::new()?; this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) }; this.set_keymap_from_x11(xcb); Ok(this) } pub fn state_mut(&mut self) -> Option<&mut XkbState> { self.state.as_mut() } pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> { self.keymap.as_mut() } #[cfg(wayland_platform)] pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) { let keymap = XkbKeymap::from_fd(&self.context, fd, size); let state = keymap.as_ref().and_then(XkbState::new_wayland); if keymap.is_none() || state.is_none() { warn!("failed to update xkb keymap"); } self.state = state; self.keymap = keymap; } #[cfg(x11_platform)] pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) { let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id); let state = keymap .as_ref() .and_then(|keymap| XkbState::new_x11(xcb, keymap)); if keymap.is_none() || state.is_none() { warn!("failed to update xkb keymap"); } self.state = state; self.keymap = keymap; } /// Key builder context with the user provided xkb state. pub fn key_context(&mut self) -> Option> { let state = self.state.as_mut()?; let keymap = self.keymap.as_mut()?; let compose_state1 = self.compose_state1.as_mut(); let compose_state2 = self.compose_state2.as_mut(); let scratch_buffer = &mut self.scratch_buffer; Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer, }) } /// Key builder context with the user provided xkb state. /// /// Should be used when the original context must not be altered. #[cfg(x11_platform)] pub fn key_context_with_state<'a>( &'a mut self, state: &'a mut XkbState, ) -> Option> { let keymap = self.keymap.as_mut()?; let compose_state1 = self.compose_state1.as_mut(); let compose_state2 = self.compose_state2.as_mut(); let scratch_buffer = &mut self.scratch_buffer; Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer, }) } } pub struct KeyContext<'a> { pub state: &'a mut XkbState, pub keymap: &'a mut XkbKeymap, compose_state1: Option<&'a mut XkbComposeState>, compose_state2: Option<&'a mut XkbComposeState>, scratch_buffer: &'a mut Vec, } impl<'a> KeyContext<'a> { pub fn process_key_event( &mut self, keycode: u32, state: ElementState, repeat: bool, ) -> KeyEvent { let mut event = KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed); let physical_key = keymap::raw_keycode_to_physicalkey(keycode); let (logical_key, location) = event.key(); let text = event.text(); let (key_without_modifiers, _) = event.key_without_modifiers(); let text_with_all_modifiers = event.text_with_all_modifiers(); let platform_specific = KeyEventExtra { text_with_all_modifiers, key_without_modifiers, }; KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific, } } fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option { self.scratch_buffer.clear(); self.scratch_buffer.reserve(8); loop { let bytes_written = unsafe { (XKBH.xkb_keysym_to_utf8)( keysym, self.scratch_buffer.as_mut_ptr().cast(), self.scratch_buffer.capacity(), ) }; if bytes_written == 0 { return None; } else if bytes_written == -1 { self.scratch_buffer.reserve(8); } else { unsafe { self.scratch_buffer .set_len(bytes_written.try_into().unwrap()) }; break; } } // Remove the null-terminator self.scratch_buffer.pop(); byte_slice_to_smol_str(self.scratch_buffer) } } struct KeyEventResults<'a, 'b> { context: &'a mut KeyContext<'b>, keycode: u32, keysym: u32, compose: ComposeStatus, } impl<'a, 'b> KeyEventResults<'a, 'b> { fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self { let keysym = context.state.get_one_sym_raw(keycode); let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) { if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { state.reset(); context.compose_state2.as_mut().unwrap().reset(); } state.feed(keysym) } else { ComposeStatus::None }; KeyEventResults { context, keycode, keysym, compose, } } pub fn key(&mut self) -> (Key, KeyLocation) { let (key, location) = match self.keysym_to_key(self.keysym) { Ok(known) => return known, Err(undefined) => undefined, }; if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose { let compose_state = self.context.compose_state2.as_mut().unwrap(); // When pressing a dead key twice, the non-combining variant of that character will // be produced. Since this function only concerns itself with a single keypress, we // simulate this double press here by feeding the keysym to the compose state // twice. compose_state.feed(self.keysym); if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) { // Extracting only a single `char` here *should* be fine, assuming that no // dead key's non-combining variant ever occupies more than one `char`. let text = compose_state.get_string(self.context.scratch_buffer); let key = Key::Dead(text.and_then(|s| s.chars().next())); (key, location) } else { (key, location) } } else { let key = self .composed_text() .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) .map(Key::Character) .unwrap_or(key); (key, location) } } pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) { // This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it. let layout = self.context.state.layout(self.keycode); let keysym = self .context .keymap .first_keysym_by_level(layout, self.keycode); match self.keysym_to_key(keysym) { Ok((key, location)) => (key, location), Err((key, location)) => { let key = self .context .keysym_to_utf8_raw(keysym) .map(Key::Character) .unwrap_or(key); (key, location) } } } fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { let location = keymap::keysym_location(keysym); let key = keymap::keysym_to_key(keysym); if matches!(key, Key::Unidentified(_)) { Err((key, location)) } else { Ok((key, location)) } } pub fn text(&mut self) -> Option { self.composed_text() .unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym)) } // The current behaviour makes it so composing a character overrides attempts to input a // control character with the `Ctrl` key. We can potentially add a configuration option // if someone specifically wants the oppsite behaviour. pub fn text_with_all_modifiers(&mut self) -> Option { match self.composed_text() { Ok(text) => text, Err(_) => self .context .state .get_utf8_raw(self.keycode, self.context.scratch_buffer), } } fn composed_text(&mut self) -> Result, ()> { match self.compose { ComposeStatus::Accepted(status) => match status { xkb_compose_status::XKB_COMPOSE_COMPOSED => { let state = self.context.compose_state1.as_mut().unwrap(); Ok(state.get_string(self.context.scratch_buffer)) } xkb_compose_status::XKB_COMPOSE_COMPOSING | xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None), xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()), }, _ => Err(()), } } } #[derive(Debug)] pub struct XkbContext { context: NonNull, } impl XkbContext { pub fn new() -> Result { let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; let context = match NonNull::new(context) { Some(context) => context, None => return Err(Error::XKBNotFound), }; Ok(Self { context }) } } impl Drop for XkbContext { fn drop(&mut self) { unsafe { (XKBH.xkb_context_unref)(self.context.as_ptr()); } } } impl Deref for XkbContext { type Target = NonNull; fn deref(&self) -> &Self::Target { &self.context } } /// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and /// `xkb_state_key_get_utf8`. fn make_string_with(scratch_buffer: &mut Vec, mut f: F) -> Option where F: FnMut(*mut c_char, usize) -> i32, { let size = f(ptr::null_mut(), 0); if size == 0 { return None; } let size = usize::try_from(size).unwrap(); scratch_buffer.clear(); // The allocated buffer must include space for the null-terminator. scratch_buffer.reserve(size + 1); unsafe { let written = f( scratch_buffer.as_mut_ptr().cast(), scratch_buffer.capacity(), ); if usize::try_from(written).unwrap() != size { // This will likely never happen. return None; } scratch_buffer.set_len(size); }; byte_slice_to_smol_str(scratch_buffer) } // NOTE: This is track_caller so we can have more informative line numbers when logging #[track_caller] fn byte_slice_to_smol_str(bytes: &[u8]) -> Option { std::str::from_utf8(bytes) .map(SmolStr::new) .map_err(|e| { log::warn!( "UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes ) }) .ok() } winit-0.29.15/src/platform_impl/linux/common/xkb/state.rs000064400000000000000000000134451046102023000215000ustar 00000000000000//! XKB state. use std::os::raw::c_char; use std::ptr::NonNull; use smol_str::SmolStr; #[cfg(x11_platform)] use x11_dl::xlib_xcb::xcb_connection_t; use xkbcommon_dl::{ self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component, }; use crate::platform_impl::common::xkb::keymap::XkbKeymap; #[cfg(x11_platform)] use crate::platform_impl::common::xkb::XKBXH; use crate::platform_impl::common::xkb::{make_string_with, XKBH}; #[derive(Debug)] pub struct XkbState { state: NonNull, modifiers: ModifiersState, } impl XkbState { #[cfg(wayland_platform)] pub fn new_wayland(keymap: &XkbKeymap) -> Option { let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?; Some(Self::new_inner(state)) } #[cfg(x11_platform)] pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option { let state = unsafe { (XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id) }; let state = NonNull::new(state)?; Some(Self::new_inner(state)) } fn new_inner(state: NonNull) -> Self { let modifiers = ModifiersState::default(); let mut this = Self { state, modifiers }; this.reload_modifiers(); this } pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t { unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) } } pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t { unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) } } #[cfg(x11_platform)] pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_DEPRESSED, ) } } #[cfg(x11_platform)] pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_LATCHED, ) } } #[cfg(x11_platform)] pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t { unsafe { (XKBH.xkb_state_serialize_mods)( self.state.as_ptr(), xkb_state_component::XKB_STATE_MODS_LOCKED, ) } } pub fn get_utf8_raw( &mut self, keycode: xkb_keycode_t, scratch_buffer: &mut Vec, ) -> Option { make_string_with(scratch_buffer, |ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len) }) } pub fn modifiers(&self) -> ModifiersState { self.modifiers } pub fn update_modifiers( &mut self, mods_depressed: u32, mods_latched: u32, mods_locked: u32, depressed_group: u32, latched_group: u32, locked_group: u32, ) { let mask = unsafe { (XKBH.xkb_state_update_mask)( self.state.as_ptr(), mods_depressed, mods_latched, mods_locked, depressed_group, latched_group, locked_group, ) }; if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { // Effective value of mods have changed, we need to update our state. self.reload_modifiers(); } } /// Reload the modifiers. fn reload_modifiers(&mut self) { self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL); self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT); self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT); self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS); self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO); self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM); } /// Check if the modifier is active within xkb. fn mod_name_is_active(&mut self, name: &[u8]) -> bool { unsafe { (XKBH.xkb_state_mod_name_is_active)( self.state.as_ptr(), name.as_ptr() as *const c_char, xkb_state_component::XKB_STATE_MODS_EFFECTIVE, ) > 0 } } } impl Drop for XkbState { fn drop(&mut self) { unsafe { (XKBH.xkb_state_unref)(self.state.as_ptr()); } } } /// Represents the current state of the keyboard modifiers /// /// Each field of this struct represents a modifier and is `true` if this modifier is active. /// /// For some modifiers, this means that the key is currently pressed, others are toggled /// (like caps lock). #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct ModifiersState { /// The "control" key pub ctrl: bool, /// The "alt" key pub alt: bool, /// The "shift" key pub shift: bool, /// The "Caps lock" key pub caps_lock: bool, /// The "logo" key /// /// Also known as the "windows" key on most keyboards pub logo: bool, /// The "Num lock" key pub num_lock: bool, } impl From for crate::keyboard::ModifiersState { fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { let mut to_mods = crate::keyboard::ModifiersState::empty(); to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo); to_mods } } winit-0.29.15/src/platform_impl/linux/common/xkb_state.rs000064400000000000000000000550471046102023000215640ustar 00000000000000use std::convert::TryInto; use std::env; use std::ffi::CString; use std::os::raw::c_char; use std::os::unix::ffi::OsStringExt; use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use once_cell::sync::Lazy; use smol_str::SmolStr; use xkbcommon_dl::{ self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon, XkbCommonCompose, }; #[cfg(feature = "wayland")] use {memmap2::MmapOptions, std::os::unix::io::OwnedFd}; #[cfg(feature = "x11")] use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle}; use crate::event::KeyEvent; use crate::platform_impl::common::keymap; use crate::platform_impl::KeyEventExtra; use crate::{ event::ElementState, keyboard::{Key, KeyLocation, PhysicalKey}, }; // TODO: Wire this up without using a static `AtomicBool`. static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); #[inline(always)] pub fn reset_dead_keys() { RESET_DEAD_KEYS.store(true, Ordering::SeqCst); } static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle); static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle); #[cfg(feature = "x11")] static XKBXH: Lazy<&'static ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle); #[derive(Debug)] pub struct KbdState { #[cfg(feature = "x11")] xcb_connection: *mut xcb_connection_t, xkb_context: *mut ffi::xkb_context, xkb_keymap: *mut ffi::xkb_keymap, xkb_state: *mut ffi::xkb_state, xkb_compose_table: *mut ffi::xkb_compose_table, xkb_compose_state: *mut ffi::xkb_compose_state, xkb_compose_state_2: *mut ffi::xkb_compose_state, mods_state: ModifiersState, #[cfg(feature = "x11")] pub core_keyboard_id: i32, scratch_buffer: Vec, } impl KbdState { pub fn update_modifiers( &mut self, mods_depressed: u32, mods_latched: u32, mods_locked: u32, depressed_group: u32, latched_group: u32, locked_group: u32, ) { if !self.ready() { return; } let mask = unsafe { (XKBH.xkb_state_update_mask)( self.xkb_state, mods_depressed, mods_latched, mods_locked, depressed_group, latched_group, locked_group, ) }; if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { // effective value of mods have changed, we need to update our state self.mods_state.update_with(self.xkb_state); } } pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { if !self.ready() { return 0; } unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) } } pub fn get_utf8_raw(&mut self, keycode: u32) -> Option { if !self.ready() { return None; } let xkb_state = self.xkb_state; self.make_string_with({ |ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) } }) } fn compose_feed_normal(&mut self, keysym: u32) -> Option { self.compose_feed(self.xkb_compose_state, keysym) } fn compose_feed_2(&mut self, keysym: u32) -> Option { self.compose_feed(self.xkb_compose_state_2, keysym) } fn compose_feed( &mut self, xkb_compose_state: *mut ffi::xkb_compose_state, keysym: u32, ) -> Option { if !self.ready() || self.xkb_compose_state.is_null() { return None; } if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { unsafe { self.init_compose() }; } Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) }) } fn compose_status_normal(&mut self) -> Option { self.compose_status(self.xkb_compose_state) } fn compose_status( &mut self, xkb_compose_state: *mut ffi::xkb_compose_state, ) -> Option { if !self.ready() || xkb_compose_state.is_null() { return None; } Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) }) } fn compose_get_utf8_normal(&mut self) -> Option { self.compose_get_utf8(self.xkb_compose_state) } fn compose_get_utf8_2(&mut self) -> Option { self.compose_get_utf8(self.xkb_compose_state_2) } fn compose_get_utf8( &mut self, xkb_compose_state: *mut ffi::xkb_compose_state, ) -> Option { if !self.ready() || xkb_compose_state.is_null() { return None; } self.make_string_with(|ptr, len| unsafe { (XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len) }) } /// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and /// `xkb_state_key_get_utf8`. fn make_string_with(&mut self, mut f: F) -> Option where F: FnMut(*mut c_char, usize) -> i32, { let size = f(ptr::null_mut(), 0); if size == 0 { return None; } let size = usize::try_from(size).unwrap(); self.scratch_buffer.clear(); // The allocated buffer must include space for the null-terminator self.scratch_buffer.reserve(size + 1); unsafe { let written = f( self.scratch_buffer.as_mut_ptr().cast(), self.scratch_buffer.capacity(), ); if usize::try_from(written).unwrap() != size { // This will likely never happen return None; } self.scratch_buffer.set_len(size); }; byte_slice_to_smol_str(&self.scratch_buffer) } pub fn new() -> Result { if ffi::xkbcommon_option().is_none() { return Err(Error::XKBNotFound); } let context = unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; if context.is_null() { return Err(Error::XKBNotFound); } let mut me = Self { #[cfg(feature = "x11")] xcb_connection: ptr::null_mut(), xkb_context: context, xkb_keymap: ptr::null_mut(), xkb_state: ptr::null_mut(), xkb_compose_table: ptr::null_mut(), xkb_compose_state: ptr::null_mut(), xkb_compose_state_2: ptr::null_mut(), mods_state: ModifiersState::new(), #[cfg(feature = "x11")] core_keyboard_id: 0, scratch_buffer: Vec::new(), }; unsafe { me.init_compose() }; Ok(me) } #[cfg(feature = "x11")] pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result { let mut me = Self::new()?; me.xcb_connection = connection; let result = unsafe { (XKBXH.xkb_x11_setup_xkb_extension)( connection, 1, 2, xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ) }; assert_eq!(result, 1, "Failed to initialize libxkbcommon"); unsafe { me.init_with_x11_keymap() }; Ok(me) } unsafe fn init_compose(&mut self) { let locale = env::var_os("LC_ALL") .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LC_CTYPE")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .or_else(|| env::var_os("LANG")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .unwrap_or_else(|| "C".into()); let locale = CString::new(locale.into_vec()).unwrap(); let compose_table = unsafe { (XKBCH.xkb_compose_table_new_from_locale)( self.xkb_context, locale.as_ptr(), ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, ) }; if compose_table.is_null() { // init of compose table failed, continue without compose return; } let compose_state = unsafe { (XKBCH.xkb_compose_state_new)( compose_table, ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, ) }; if compose_state.is_null() { // init of compose state failed, continue without compose unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) }; return; } let compose_state_2 = unsafe { (XKBCH.xkb_compose_state_new)( compose_table, ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, ) }; if compose_state_2.is_null() { // init of compose state failed, continue without compose unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) }; unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) }; return; } self.xkb_compose_table = compose_table; self.xkb_compose_state = compose_state; self.xkb_compose_state_2 = compose_state_2; } unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) { self.xkb_keymap = keymap; self.xkb_state = state; self.mods_state.update_with(state); } unsafe fn de_init(&mut self) { unsafe { (XKBH.xkb_state_unref)(self.xkb_state) }; self.xkb_state = ptr::null_mut(); unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) }; self.xkb_keymap = ptr::null_mut(); } #[cfg(feature = "x11")] pub unsafe fn init_with_x11_keymap(&mut self) { if !self.xkb_keymap.is_null() { unsafe { self.de_init() }; } // TODO: Support keyboards other than the "virtual core keyboard device". self.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) }; let keymap = unsafe { (XKBXH.xkb_x11_keymap_new_from_device)( self.xkb_context, self.xcb_connection, self.core_keyboard_id, xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ) }; if keymap.is_null() { panic!("Failed to get keymap from X11 server."); } let state = unsafe { (XKBXH.xkb_x11_state_new_from_device)( keymap, self.xcb_connection, self.core_keyboard_id, ) }; unsafe { self.post_init(state, keymap) }; } #[cfg(feature = "wayland")] pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) { if !self.xkb_keymap.is_null() { unsafe { self.de_init() }; } let map = unsafe { MmapOptions::new() .len(size) .map_copy_read_only(&fd) .unwrap() }; let keymap = unsafe { (XKBH.xkb_keymap_new_from_string)( self.xkb_context, map.as_ptr() as *const _, ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, ) }; if keymap.is_null() { panic!("Received invalid keymap from compositor."); } let state = unsafe { (XKBH.xkb_state_new)(keymap) }; unsafe { self.post_init(state, keymap) }; } pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 } } #[inline] pub fn ready(&self) -> bool { !self.xkb_state.is_null() } #[inline] pub fn mods_state(&self) -> ModifiersState { self.mods_state } pub fn process_key_event( &mut self, keycode: u32, state: ElementState, repeat: bool, ) -> KeyEvent { let mut event = KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed); let physical_key = event.physical_key(); let (logical_key, location) = event.key(); let text = event.text(); let (key_without_modifiers, _) = event.key_without_modifiers(); let text_with_all_modifiers = event.text_with_all_modifiers(); let platform_specific = KeyEventExtra { key_without_modifiers, text_with_all_modifiers, }; KeyEvent { physical_key, logical_key, text, location, state, repeat, platform_specific, } } fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option { self.scratch_buffer.clear(); self.scratch_buffer.reserve(8); loop { let bytes_written = unsafe { (XKBH.xkb_keysym_to_utf8)( keysym, self.scratch_buffer.as_mut_ptr().cast(), self.scratch_buffer.capacity(), ) }; if bytes_written == 0 { return None; } else if bytes_written == -1 { self.scratch_buffer.reserve(8); } else { unsafe { self.scratch_buffer .set_len(bytes_written.try_into().unwrap()) }; break; } } // Remove the null-terminator self.scratch_buffer.pop(); byte_slice_to_smol_str(&self.scratch_buffer) } } impl Drop for KbdState { fn drop(&mut self) { unsafe { if !self.xkb_compose_state.is_null() { (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state); } if !self.xkb_compose_state_2.is_null() { (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2); } if !self.xkb_compose_table.is_null() { (XKBCH.xkb_compose_table_unref)(self.xkb_compose_table); } if !self.xkb_state.is_null() { (XKBH.xkb_state_unref)(self.xkb_state); } if !self.xkb_keymap.is_null() { (XKBH.xkb_keymap_unref)(self.xkb_keymap); } (XKBH.xkb_context_unref)(self.xkb_context); } } } struct KeyEventResults<'a> { state: &'a mut KbdState, keycode: u32, keysym: u32, compose: Option, } impl<'a> KeyEventResults<'a> { fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self { let keysym = state.get_one_sym_raw(keycode); let compose = if compose { Some(match state.compose_feed_normal(keysym) { Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { // Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized. XkbCompose::Accepted(state.compose_status_normal().unwrap()) } Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored, None => XkbCompose::Uninitialized, }) } else { None }; KeyEventResults { state, keycode, keysym, compose, } } fn physical_key(&self) -> PhysicalKey { keymap::raw_keycode_to_physicalkey(self.keycode) } pub fn key(&mut self) -> (Key, KeyLocation) { self.keysym_to_key(self.keysym) .unwrap_or_else(|(key, location)| match self.compose { Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => { // When pressing a dead key twice, the non-combining variant of that character will be // produced. Since this function only concerns itself with a single keypress, we simulate // this double press here by feeding the keysym to the compose state twice. self.state.compose_feed_2(self.keysym); match self.state.compose_feed_2(self.keysym) { Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => ( // Extracting only a single `char` here *should* be fine, assuming that no dead // key's non-combining variant ever occupies more than one `char`. Key::Dead( self.state .compose_get_utf8_2() .map(|s| s.chars().next().unwrap()), ), location, ), _ => (key, location), } } _ => ( self.composed_text() .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) .map(Key::Character) .unwrap_or(key), location, ), }) } pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) { // This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it. let mut keysyms = ptr::null(); let keysym_count = unsafe { let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode); (XKBH.xkb_keymap_key_get_syms_by_level)( self.state.xkb_keymap, self.keycode, layout, // NOTE: The level should be zero to ignore modifiers. 0, &mut keysyms, ) }; let keysym = if keysym_count == 1 { unsafe { *keysyms } } else { 0 }; self.keysym_to_key(keysym) .unwrap_or_else(|(key, location)| { ( self.state .keysym_to_utf8_raw(keysym) .map(Key::Character) .unwrap_or(key), location, ) }) } fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> { let location = super::keymap::keysym_location(keysym); let key = super::keymap::keysym_to_key(keysym); if matches!(key, Key::Unidentified(_)) { Err((key, location)) } else { Ok((key, location)) } } pub fn text(&mut self) -> Option { self.composed_text() .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) } pub fn text_with_all_modifiers(&mut self) -> Option { // The current behaviour makes it so composing a character overrides attempts to input a // control character with the `Ctrl` key. We can potentially add a configuration option // if someone specifically wants the oppsite behaviour. self.composed_text() .unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode)) } fn composed_text(&mut self) -> Result, ()> { if let Some(compose) = &self.compose { match compose { XkbCompose::Accepted(status) => match status { ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => { Ok(self.state.compose_get_utf8_normal()) } ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()), _ => Ok(None), }, XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()), } } else { Err(()) } } } /// Represents the current state of the keyboard modifiers /// /// Each field of this struct represents a modifier and is `true` if this modifier is active. /// /// For some modifiers, this means that the key is currently pressed, others are toggled /// (like caps lock). #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct ModifiersState { /// The "control" key pub ctrl: bool, /// The "alt" key pub alt: bool, /// The "shift" key pub shift: bool, /// The "Caps lock" key pub caps_lock: bool, /// The "logo" key /// /// Also known as the "windows" key on most keyboards pub logo: bool, /// The "Num lock" key pub num_lock: bool, } impl ModifiersState { fn new() -> Self { Self::default() } fn update_with(&mut self, state: *mut ffi::xkb_state) { let mod_name_is_active = |mod_name: &[u8]| unsafe { (XKBH.xkb_state_mod_name_is_active)( state, mod_name.as_ptr() as *const c_char, xkb_state_component::XKB_STATE_MODS_EFFECTIVE, ) > 0 }; self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL); self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT); self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT); self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS); self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO); self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM); } } impl From for crate::keyboard::ModifiersState { fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { let mut to_mods = crate::keyboard::ModifiersState::empty(); to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo); to_mods } } #[derive(Debug)] pub enum Error { /// libxkbcommon is not available XKBNotFound, } #[derive(Copy, Clone, Debug)] enum XkbCompose { Accepted(ffi::xkb_compose_status), Ignored, Uninitialized, } // Note: This is track_caller so we can have more informative line numbers when logging #[track_caller] fn byte_slice_to_smol_str(bytes: &[u8]) -> Option { std::str::from_utf8(bytes) .map(SmolStr::new) .map_err(|e| { warn!( "UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes ) }) .ok() } winit-0.29.15/src/platform_impl/linux/mod.rs000064400000000000000000000731531046102023000170650ustar 00000000000000#![cfg(free_unix)] #[cfg(all(not(x11_platform), not(wayland_platform)))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::Arc; use std::time::Duration; use std::{collections::VecDeque, env, fmt}; #[cfg(x11_platform)] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex}; #[cfg(x11_platform)] use once_cell::sync::Lazy; use smol_str::SmolStr; #[cfg(x11_platform)] use crate::platform::x11::XlibErrorHook; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError}, event::KeyEvent, event_loop::{ AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW, }, icon::Icon, keyboard::{Key, PhysicalKey}, platform::{ modifier_supplement::KeyEventExtModifierSupplement, pump_events::PumpStatus, scancode::PhysicalKeyExtScancode, }, window::{ ActivationToken, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }, }; #[cfg(x11_platform)] pub use x11::XNotSupported; #[cfg(x11_platform)] use x11::{util::WindowType as XWindowType, X11Error, XConnection, XError}; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; pub mod common; #[cfg(wayland_platform)] pub mod wayland; #[cfg(x11_platform)] pub mod x11; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum Backend { #[cfg(x11_platform)] X, #[cfg(wayland_platform)] Wayland, } #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) forced_backend: Option, pub(crate) any_thread: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ApplicationName { pub general: String, pub instance: String, } impl ApplicationName { pub fn new(general: String, instance: String) -> Self { Self { general, instance } } } #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub name: Option, pub activation_token: Option, #[cfg(x11_platform)] pub x11: X11WindowBuilderAttributes, } #[derive(Clone)] #[cfg(x11_platform)] pub struct X11WindowBuilderAttributes { pub visual_id: Option, pub screen_id: Option, pub base_size: Option, pub override_redirect: bool, pub x11_window_types: Vec, /// The parent window to embed this window into. pub embed_window: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { name: None, activation_token: None, #[cfg(x11_platform)] x11: X11WindowBuilderAttributes { visual_id: None, screen_id: None, base_size: None, override_redirect: false, x11_window_types: vec![XWindowType::Normal], embed_window: None, }, } } } #[cfg(x11_platform)] pub(crate) static X11_BACKEND: Lazy, XNotSupported>>> = Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new))); #[derive(Debug, Clone)] pub enum OsError { Misc(&'static str), #[cfg(x11_platform)] XError(Arc), #[cfg(wayland_platform)] WaylandError(Arc), } impl fmt::Display for OsError { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match *self { OsError::Misc(e) => _f.pad(e), #[cfg(x11_platform)] OsError::XError(ref e) => fmt::Display::fmt(e, _f), #[cfg(wayland_platform)] OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f), } } } pub(crate) enum Window { #[cfg(x11_platform)] X(x11::Window), #[cfg(wayland_platform)] Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(u64); impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id) } } impl WindowId { pub const unsafe fn dummy() -> Self { Self(0) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { #[cfg(x11_platform)] X(x11::DeviceId), #[cfg(wayland_platform)] Wayland(wayland::DeviceId), } impl DeviceId { pub const unsafe fn dummy() -> Self { #[cfg(wayland_platform)] return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() }); #[cfg(all(not(wayland_platform), x11_platform))] return DeviceId::X(unsafe { x11::DeviceId::dummy() }); } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { #[cfg(x11_platform)] X(x11::MonitorHandle), #[cfg(wayland_platform)] Wayland(wayland::MonitorHandle), } /// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` /// expands to the equivalent of /// ```ignore /// match self { /// Enum::X(foo) => foo.something(), /// Enum::Wayland(foo) => foo.something(), /// } /// ``` /// The result can be converted to another enum by adding `; as AnotherEnum` macro_rules! x11_or_wayland { (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => { match $what { #[cfg(x11_platform)] $enum::X($($c1)*) => $enum2::X($x), #[cfg(wayland_platform)] $enum::Wayland($($c1)*) => $enum2::Wayland($x), } }; (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => { match $what { #[cfg(x11_platform)] $enum::X($($c1)*) => $x, #[cfg(wayland_platform)] $enum::Wayland($($c1)*) => $x, } }; } impl MonitorHandle { #[inline] pub fn name(&self) -> Option { x11_or_wayland!(match self; MonitorHandle(m) => m.name()) } #[inline] pub fn native_identifier(&self) -> u32 { x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier()) } #[inline] pub fn size(&self) -> PhysicalSize { x11_or_wayland!(match self; MonitorHandle(m) => m.size()) } #[inline] pub fn position(&self) -> PhysicalPosition { x11_or_wayland!(match self; MonitorHandle(m) => m.position()) } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz()) } #[inline] pub fn scale_factor(&self) -> f64 { x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _) } #[inline] pub fn video_modes(&self) -> Box> { x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum VideoMode { #[cfg(x11_platform)] X(x11::VideoMode), #[cfg(wayland_platform)] Wayland(wayland::VideoMode), } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { x11_or_wayland!(match self; VideoMode(m) => m.size()) } #[inline] pub fn bit_depth(&self) -> u16 { x11_or_wayland!(match self; VideoMode(m) => m.bit_depth()) } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz()) } #[inline] pub fn monitor(&self) -> MonitorHandle { x11_or_wayland!(match self; VideoMode(m) => m.monitor(); as MonitorHandle) } } impl Window { #[inline] pub(crate) fn new( window_target: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { match *window_target { #[cfg(wayland_platform)] EventLoopWindowTarget::Wayland(ref window_target) => { wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland) } #[cfg(x11_platform)] EventLoopWindowTarget::X(ref window_target) => { x11::Window::new(window_target, attribs, pl_attribs).map(Window::X) } } } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } #[inline] pub fn id(&self) -> WindowId { x11_or_wayland!(match self; Window(w) => w.id()) } #[inline] pub fn set_title(&self, title: &str) { x11_or_wayland!(match self; Window(w) => w.set_title(title)); } #[inline] pub fn set_transparent(&self, transparent: bool) { x11_or_wayland!(match self; Window(w) => w.set_transparent(transparent)); } #[inline] pub fn set_blur(&self, blur: bool) { x11_or_wayland!(match self; Window(w) => w.set_blur(blur)); } #[inline] pub fn set_visible(&self, visible: bool) { x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) } #[inline] pub fn is_visible(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.is_visible()) } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { x11_or_wayland!(match self; Window(w) => w.outer_position()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { x11_or_wayland!(match self; Window(w) => w.inner_position()) } #[inline] pub fn set_outer_position(&self, position: Position) { x11_or_wayland!(match self; Window(w) => w.set_outer_position(position)) } #[inline] pub fn inner_size(&self) -> PhysicalSize { x11_or_wayland!(match self; Window(w) => w.inner_size()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { x11_or_wayland!(match self; Window(w) => w.outer_size()) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { x11_or_wayland!(match self; Window(w) => w.request_inner_size(size)) } #[inline] pub(crate) fn request_activation_token(&self) -> Result { x11_or_wayland!(match self; Window(w) => w.request_activation_token()) } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions)) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) } #[inline] pub fn resize_increments(&self) -> Option> { x11_or_wayland!(match self; Window(w) => w.resize_increments()) } #[inline] pub fn set_resize_increments(&self, increments: Option) { x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments)) } #[inline] pub fn set_resizable(&self, resizable: bool) { x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) } #[inline] pub fn is_resizable(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_resizable()) } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { x11_or_wayland!(match self; Window(w) => w.set_enabled_buttons(buttons)) } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { x11_or_wayland!(match self; Window(w) => w.enabled_buttons()) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode)) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.drag_window()) } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction)) } #[inline] pub fn show_window_menu(&self, position: Position) { x11_or_wayland!(match self; Window(w) => w.show_window_menu(position)) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) } #[inline] pub fn scale_factor(&self) -> f64 { x11_or_wayland!(match self; Window(w) => w.scale_factor()) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position)) } #[inline] pub fn set_maximized(&self, maximized: bool) { x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) } #[inline] pub fn is_maximized(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_maximized()) } #[inline] pub fn set_minimized(&self, minimized: bool) { x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) } #[inline] pub fn is_minimized(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.is_minimized()) } #[inline] pub(crate) fn fullscreen(&self) -> Option { x11_or_wayland!(match self; Window(w) => w.fullscreen()) } #[inline] pub(crate) fn set_fullscreen(&self, monitor: Option) { x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor)) } #[inline] pub fn set_decorations(&self, decorations: bool) { x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations)) } #[inline] pub fn is_decorated(&self) -> bool { x11_or_wayland!(match self; Window(w) => w.is_decorated()) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { x11_or_wayland!(match self; Window(w) => w.set_window_level(level)) } #[inline] pub fn set_window_icon(&self, window_icon: Option) { x11_or_wayland!(match self; Window(w) => w.set_window_icon(window_icon.map(|icon| icon.inner))) } #[inline] pub fn set_ime_cursor_area(&self, position: Position, size: Size) { x11_or_wayland!(match self; Window(w) => w.set_ime_cursor_area(position, size)) } #[inline] pub fn reset_dead_keys(&self) { common::xkb::reset_dead_keys() } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed)) } #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose)) } #[inline] pub fn focus_window(&self) { x11_or_wayland!(match self; Window(w) => w.focus_window()) } pub fn request_user_attention(&self, request_type: Option) { x11_or_wayland!(match self; Window(w) => w.request_user_attention(request_type)) } #[inline] pub fn request_redraw(&self) { x11_or_wayland!(match self; Window(w) => w.request_redraw()) } #[inline] pub fn pre_present_notify(&self) { x11_or_wayland!(match self; Window(w) => w.pre_present_notify()) } #[inline] pub fn current_monitor(&self) -> Option { Some(x11_or_wayland!(match self; Window(w) => w.current_monitor()?; as MonitorHandle)) } #[inline] pub fn available_monitors(&self) -> VecDeque { match self { #[cfg(x11_platform)] Window::X(ref window) => window .available_monitors() .into_iter() .map(MonitorHandle::X) .collect(), #[cfg(wayland_platform)] Window::Wayland(ref window) => window .available_monitors() .into_iter() .map(MonitorHandle::Wayland) .collect(), } } #[inline] pub fn primary_monitor(&self) -> Option { Some(x11_or_wayland!(match self; Window(w) => w.primary_monitor()?; as MonitorHandle)) } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_04()) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_05()) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_05()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { x11_or_wayland!(match self; Window(window) => window.raw_window_handle_rwh_06()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { x11_or_wayland!(match self; Window(window) => window.raw_display_handle_rwh_06()) } #[inline] pub fn set_theme(&self, theme: Option) { x11_or_wayland!(match self; Window(window) => window.set_theme(theme)) } #[inline] pub fn theme(&self) -> Option { x11_or_wayland!(match self; Window(window) => window.theme()) } pub fn set_content_protected(&self, protected: bool) { x11_or_wayland!(match self; Window(window) => window.set_content_protected(protected)) } #[inline] pub fn has_focus(&self) -> bool { x11_or_wayland!(match self; Window(window) => window.has_focus()) } pub fn title(&self) -> String { x11_or_wayland!(match self; Window(window) => window.title()) } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub key_without_modifiers: Key, pub text_with_all_modifiers: Option, } impl KeyEventExtModifierSupplement for KeyEvent { #[inline] fn text_with_all_modifiers(&self) -> Option<&str> { self.platform_specific .text_with_all_modifiers .as_ref() .map(|s| s.as_str()) } #[inline] fn key_without_modifiers(&self) -> Key { self.platform_specific.key_without_modifiers.clone() } } impl PhysicalKeyExtScancode for PhysicalKey { fn from_scancode(scancode: u32) -> PhysicalKey { common::xkb::scancode_to_keycode(scancode) } fn to_scancode(self) -> Option { common::xkb::physicalkey_to_scancode(self) } } /// Hooks for X11 errors. #[cfg(x11_platform)] pub(crate) static mut XLIB_ERROR_HOOKS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); #[cfg(x11_platform)] unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, ) -> c_int { let xconn_lock = X11_BACKEND.lock().unwrap(); if let Ok(ref xconn) = *xconn_lock { // Call all the hooks. let mut error_handled = false; for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() { error_handled |= hook(display as *mut _, event as *mut _); } // `assume_init` is safe here because the array consists of `MaybeUninit` values, // which do not require initialization. let mut buf: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; unsafe { (xconn.xlib.XGetErrorText)( display, (*event).error_code as c_int, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int, ) }; let description = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy(); let error = unsafe { XError { description: description.into_owned(), error_code: (*event).error_code, request_code: (*event).request_code, minor_code: (*event).minor_code, } }; // Don't log error. if !error_handled { error!("X11 error: {:#?}", error); // XXX only update the error, if it wasn't handled by any of the hooks. *xconn.latest_error.lock().unwrap() = Some(error); } } // Fun fact: this return value is completely ignored. 0 } pub enum EventLoop { #[cfg(wayland_platform)] Wayland(Box>), #[cfg(x11_platform)] X(x11::EventLoop), } pub enum EventLoopProxy { #[cfg(x11_platform)] X(x11::EventLoopProxy), #[cfg(wayland_platform)] Wayland(wayland::EventLoopProxy), } impl Clone for EventLoopProxy { fn clone(&self) -> Self { x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { if !attributes.any_thread && !is_main_thread() { 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 \ `EventLoopBuilderExtUnix::any_thread` function." ); } // NOTE: Wayland first because of X11 could be present under Wayland as well. Empty // variables are also treated as not set. let backend = match ( attributes.forced_backend, env::var("WAYLAND_DISPLAY") .ok() .filter(|var| !var.is_empty()) .or_else(|| env::var("WAYLAND_SOCKET").ok()) .filter(|var| !var.is_empty()) .is_some(), env::var("DISPLAY") .map(|var| !var.is_empty()) .unwrap_or(false), ) { // User is forcing a backend. (Some(backend), _, _) => backend, // Wayland is present. #[cfg(wayland_platform)] (None, true, _) => Backend::Wayland, // X11 is present. #[cfg(x11_platform)] (None, _, true) => Backend::X, // No backend is present. (_, wayland_display, x11_display) => { let msg = if wayland_display && !cfg!(wayland_platform) { "DISPLAY is not set; note: enable the `winit/wayland` feature to support Wayland" } else if x11_display && !cfg!(x11_platform) { "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the `winit/x11` feature to support X11" } else { "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set." }; return Err(EventLoopError::Os(os_error!(OsError::Misc(msg)))); } }; // Create the display based on the backend. match backend { #[cfg(wayland_platform)] Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), #[cfg(x11_platform)] Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into), } } #[cfg(wayland_platform)] fn new_wayland_any_thread() -> Result, EventLoopError> { wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp))) } #[cfg(x11_platform)] fn new_x11_any_thread() -> Result, EventLoopError> { let xconn = match X11_BACKEND.lock().unwrap().as_ref() { Ok(xconn) => xconn.clone(), Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())), }; Ok(EventLoop::X(x11::EventLoop::new(xconn))) } pub fn create_proxy(&self) -> EventLoopProxy { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } pub fn run(mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(crate::event::Event, &RootELW), { self.run_on_demand(callback) } pub fn run_on_demand(&mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(crate::event::Event, &RootELW), { x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback)) } pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(crate::event::Event, &RootELW), { x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback)) } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target()) } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd()) } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd()) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) } } pub enum EventLoopWindowTarget { #[cfg(wayland_platform)] Wayland(wayland::EventLoopWindowTarget), #[cfg(x11_platform)] X(x11::EventLoopWindowTarget), } impl EventLoopWindowTarget { #[inline] pub fn is_wayland(&self) -> bool { match *self { #[cfg(wayland_platform)] EventLoopWindowTarget::Wayland(_) => true, #[cfg(x11_platform)] _ => false, } } #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { #[cfg(wayland_platform)] EventLoopWindowTarget::Wayland(ref evlp) => evlp .available_monitors() .map(MonitorHandle::Wayland) .collect(), #[cfg(x11_platform)] EventLoopWindowTarget::X(ref evlp) => { evlp.available_monitors().map(MonitorHandle::X).collect() } } } #[inline] pub fn primary_monitor(&self) -> Option { Some( x11_or_wayland!(match self; EventLoopWindowTarget(evlp) => evlp.primary_monitor()?; as MonitorHandle), ) } #[inline] pub fn listen_device_events(&self, allowed: DeviceEvents) { x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed)) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_06()) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { x11_or_wayland!(match self; Self(evlp) => evlp.set_control_flow(control_flow)) } pub(crate) fn control_flow(&self) -> ControlFlow { x11_or_wayland!(match self; Self(evlp) => evlp.control_flow()) } pub(crate) fn clear_exit(&self) { x11_or_wayland!(match self; Self(evlp) => evlp.clear_exit()) } pub(crate) fn exit(&self) { x11_or_wayland!(match self; Self(evlp) => evlp.exit()) } pub(crate) fn exiting(&self) -> bool { x11_or_wayland!(match self; Self(evlp) => evlp.exiting()) } #[allow(dead_code)] fn set_exit_code(&self, code: i32) { x11_or_wayland!(match self; Self(evlp) => evlp.set_exit_code(code)) } #[allow(dead_code)] fn exit_code(&self) -> Option { x11_or_wayland!(match self; Self(evlp) => evlp.exit_code()) } } /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| { b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) }) } #[cfg(target_os = "linux")] fn is_main_thread() -> bool { rustix::thread::gettid() == rustix::process::getpid() } #[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") } winit-0.29.15/src/platform_impl/linux/wayland/event_loop/mod.rs000064400000000000000000000626471046102023000227040ustar 00000000000000//! The event-loop routines. use std::cell::{Cell, RefCell}; use std::io::Result as IOResult; use std::marker::PhantomData; use std::mem; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use sctk::reexports::calloop::Error as CalloopError; use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::globals; use sctk::reexports::client::{Connection, QueueHandle}; use crate::dpi::LogicalSize; use crate::error::{EventLoopError, OsError as RootOsError}; use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent}; use crate::event_loop::{ ControlFlow, DeviceEvents, EventLoopWindowTarget as RootEventLoopWindowTarget, }; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::min_timeout; use crate::platform_impl::{EventLoopWindowTarget as PlatformEventLoopWindowTarget, OsError}; mod proxy; pub mod sink; pub use proxy::EventLoopProxy; use sink::EventSink; use super::state::{WindowCompositorUpdate, WinitState}; use super::window::state::FrameCallbackState; use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId}; type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>; /// The Wayland event loop. pub struct EventLoop { /// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop loop_running: bool, buffer_sink: EventSink, compositor_updates: Vec, window_ids: Vec, /// Sender of user events. user_events_sender: calloop::channel::Sender, // XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which // we don't really want, since it'll break public API by a lot. /// Pending events from the user. pending_user_events: Rc>>, /// The Wayland dispatcher to has raw access to the queue when needed, such as /// when creating a new window. wayland_dispatcher: WaylandDispatcher, /// Connection to the wayland server. connection: Connection, /// Event loop window target. window_target: RootEventLoopWindowTarget, // XXX drop after everything else, just to be safe. /// Calloop's event loop. event_loop: calloop::EventLoop<'static, WinitState>, } impl EventLoop { pub fn new() -> Result, EventLoopError> { macro_rules! map_err { ($e:expr, $err:expr) => { $e.map_err(|error| os_error!($err(error).into())) }; } let connection = map_err!(Connection::connect_to_env(), WaylandError::Connection)?; let (globals, mut event_queue) = map_err!( globals::registry_queue_init(&connection), WaylandError::Global )?; let queue_handle = event_queue.handle(); let event_loop = map_err!( calloop::EventLoop::::try_new(), WaylandError::Calloop )?; let mut winit_state = WinitState::new(&globals, &queue_handle, event_loop.handle()) .map_err(|error| os_error!(error))?; // NOTE: do a roundtrip after binding the globals to prevent potential // races with the server. map_err!( event_queue.roundtrip(&mut winit_state), WaylandError::Dispatch )?; // Register Wayland source. let wayland_source = WaylandSource::new(connection.clone(), event_queue); let wayland_dispatcher = calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| { let result = queue.dispatch_pending(winit_state); if result.is_ok() && (!winit_state.events_sink.is_empty() || !winit_state.window_compositor_updates.is_empty()) { winit_state.dispatched_events = true; } result }); map_err!( event_loop .handle() .register_dispatcher(wayland_dispatcher.clone()), WaylandError::Calloop )?; // Setup the user proxy. let pending_user_events = Rc::new(RefCell::new(Vec::new())); let pending_user_events_clone = pending_user_events.clone(); let (user_events_sender, user_events_channel) = calloop::channel::channel(); let result = event_loop .handle() .insert_source( user_events_channel, move |event, _, winit_state: &mut WinitState| { if let calloop::channel::Event::Msg(msg) = event { winit_state.dispatched_events = true; pending_user_events_clone.borrow_mut().push(msg); } }, ) .map_err(|error| error.error); map_err!(result, WaylandError::Calloop)?; // An event's loop awakener to wake up for window events from winit's windows. let (event_loop_awakener, event_loop_awakener_source) = map_err!( calloop::ping::make_ping() .map_err(|error| CalloopError::OtherError(Box::new(error).into())), WaylandError::Calloop )?; let result = event_loop .handle() .insert_source( event_loop_awakener_source, move |_, _, winit_state: &mut WinitState| { // Mark that we have something to dispatch. winit_state.dispatched_events = true; }, ) .map_err(|error| error.error); map_err!(result, WaylandError::Calloop)?; let window_target = EventLoopWindowTarget { connection: connection.clone(), wayland_dispatcher: wayland_dispatcher.clone(), event_loop_awakener, queue_handle, control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), state: RefCell::new(winit_state), _marker: PhantomData, }; let event_loop = Self { loop_running: false, compositor_updates: Vec::new(), buffer_sink: EventSink::default(), window_ids: Vec::new(), connection, wayland_dispatcher, user_events_sender, pending_user_events, event_loop, window_target: RootEventLoopWindowTarget { p: PlatformEventLoopWindowTarget::Wayland(window_target), _marker: PhantomData, }, }; Ok(event_loop) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootEventLoopWindowTarget), { if self.loop_running { return Err(EventLoopError::AlreadyRunning); } let exit = loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); } _ => { continue; } } }; // Applications aren't allowed to carry windows between separate // `run_on_demand` calls but if they have only just dropped their // windows we need to make sure those last requests are sent to the // compositor. let _ = self.roundtrip().map_err(EventLoopError::Os); exit } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event, &RootEventLoopWindowTarget), { if !self.loop_running { self.loop_running = true; // Run the initial loop iteration. self.single_iteration(&mut callback, StartCause::Init); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit. if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if let Some(code) = self.exit_code() { self.loop_running = false; callback(Event::LoopExiting, self.window_target()); PumpStatus::Exit(code) } else { PumpStatus::Continue } } pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(Event, &RootEventLoopWindowTarget), { let cause = loop { let start = Instant::now(); timeout = { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) } }; min_timeout(control_flow_timeout, timeout) }; // NOTE Ideally we should flush as the last thing we do before polling // to wait for events, and this should be done by the calloop // WaylandSource but we currently need to flush writes manually. // // Checking for flush error is essential to perform an exit with error, since // once we have a protocol error, we could get stuck retrying... if self.connection.flush().is_err() { self.set_exit_code(1); return; } if let Err(error) = self.loop_dispatch(timeout) { // NOTE We exit on errors from dispatches, since if we've got protocol error // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not // really an option. Instead we inform that the event loop got destroyed. We may // communicate an error that something was terminated, but winit doesn't provide us // with an API to do that via some event. // Still, we set the exit code to the error's OS error code, or to 1 if not possible. let exit_code = error.raw_os_error().unwrap_or(1); self.set_exit_code(exit_code); return; } // NB: `StartCause::Init` is handled as a special case and doesn't need // to be considered here let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None, }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline), } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline, } } } }; // Reduce spurious wake-ups. let dispatched_events = self.with_state(|state| state.dispatched_events); if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events { continue; } break cause; }; self.single_iteration(&mut callback, cause); } fn single_iteration(&mut self, callback: &mut F, cause: StartCause) where F: FnMut(Event, &RootEventLoopWindowTarget), { // NOTE currently just indented to simplify the diff // We retain these grow-only scratch buffers as part of the EventLoop // for the sake of avoiding lots of reallocs. We take them here to avoid // trying to mutably borrow `self` more than once and we swap them back // when finished. let mut compositor_updates = std::mem::take(&mut self.compositor_updates); let mut buffer_sink = std::mem::take(&mut self.buffer_sink); let mut window_ids = std::mem::take(&mut self.window_ids); callback(Event::NewEvents(cause), &self.window_target); // NB: For consistency all platforms must emit a 'resumed' event even though Wayland // applications don't themselves have a formal suspend/resume lifecycle. if cause == StartCause::Init { callback(Event::Resumed, &self.window_target); } // Handle pending user events. We don't need back buffer, since we can't dispatch // user events indirectly via callback to the user. for user_event in self.pending_user_events.borrow_mut().drain(..) { callback(Event::UserEvent(user_event), &self.window_target); } // Drain the pending compositor updates. self.with_state(|state| compositor_updates.append(&mut state.window_compositor_updates)); for mut compositor_update in compositor_updates.drain(..) { let window_id = compositor_update.window_id; if compositor_update.scale_changed { let (physical_size, scale_factor) = self.with_state(|state| { let windows = state.windows.get_mut(); let window = windows.get(&window_id).unwrap().lock().unwrap(); let scale_factor = window.scale_factor(); let size = logical_to_physical_rounded(window.inner_size(), scale_factor); (size, scale_factor) }); // Stash the old window size. let old_physical_size = physical_size; let new_inner_size = Arc::new(Mutex::new(physical_size)); callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &new_inner_size, )), }, }, &self.window_target, ); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); // Resize the window when user altered the size. if old_physical_size != physical_size { self.with_state(|state| { let windows = state.windows.get_mut(); let mut window = windows.get(&window_id).unwrap().lock().unwrap(); let new_logical_size: LogicalSize = physical_size.to_logical(scale_factor); window.request_inner_size(new_logical_size.into()); }); // Make it queue resize. compositor_update.resized = true; } } // NOTE: Rescale changed the physical size which winit operates in, thus we should // resize. if compositor_update.resized || compositor_update.scale_changed { let physical_size = self.with_state(|state| { let windows = state.windows.get_mut(); let window = windows.get(&window_id).unwrap().lock().unwrap(); let scale_factor = window.scale_factor(); let size = logical_to_physical_rounded(window.inner_size(), scale_factor); // Mark the window as needed a redraw. state .window_requests .get_mut() .get_mut(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); size }); callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::Resized(physical_size), }, &self.window_target, ); } if compositor_update.close_window { callback( Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::CloseRequested, }, &self.window_target, ); } } // Push the events directly from the window. self.with_state(|state| { buffer_sink.append(&mut state.window_events_sink.lock().unwrap()); }); for event in buffer_sink.drain() { let event = event.map_nonuser_event().unwrap(); callback(event, &self.window_target); } // Handle non-synthetic events. self.with_state(|state| { buffer_sink.append(&mut state.events_sink); }); for event in buffer_sink.drain() { let event = event.map_nonuser_event().unwrap(); callback(event, &self.window_target); } // Collect the window ids self.with_state(|state| { window_ids.extend(state.window_requests.get_mut().keys()); }); for window_id in window_ids.iter() { let event = self.with_state(|state| { let window_requests = state.window_requests.get_mut(); if window_requests.get(window_id).unwrap().take_closed() { mem::drop(window_requests.remove(window_id)); mem::drop(state.windows.get_mut().remove(window_id)); return Some(WindowEvent::Destroyed); } let mut window = state .windows .get_mut() .get_mut(window_id) .unwrap() .lock() .unwrap(); if window.frame_callback_state() == FrameCallbackState::Requested { return None; } // Reset the frame callbacks state. window.frame_callback_reset(); let mut redraw_requested = window_requests .get(window_id) .unwrap() .take_redraw_requested(); // Redraw the frame while at it. redraw_requested |= window.refresh_frame(); redraw_requested.then_some(WindowEvent::RedrawRequested) }); if let Some(event) = event { callback( Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event, }, &self.window_target, ); } } // Reset the hint that we've dispatched events. self.with_state(|state| { state.dispatched_events = false; }); // This is always the last event we dispatch before poll again callback(Event::AboutToWait, &self.window_target); // Update the window frames and schedule redraws. let mut wake_up = false; for window_id in window_ids.drain(..) { wake_up |= self.with_state(|state| match state.windows.get_mut().get_mut(&window_id) { Some(window) => { let refresh = window.lock().unwrap().refresh_frame(); if refresh { state .window_requests .get_mut() .get_mut(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); } refresh } None => false, }); } // Wakeup event loop if needed. // // If the user draws from the `AboutToWait` this is likely not required, however // we can't do much about it. if wake_up { match &self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => { window_target.event_loop_awakener.ping(); } #[cfg(x11_platform)] PlatformEventLoopWindowTarget::X(_) => unreachable!(), } } std::mem::swap(&mut self.compositor_updates, &mut compositor_updates); std::mem::swap(&mut self.buffer_sink, &mut buffer_sink); std::mem::swap(&mut self.window_ids, &mut window_ids); } #[inline] pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.user_events_sender.clone()) } #[inline] pub fn window_target(&self) -> &RootEventLoopWindowTarget { &self.window_target } fn with_state<'a, U: 'a, F: FnOnce(&'a mut WinitState) -> U>(&'a mut self, callback: F) -> U { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), #[cfg(x11_platform)] _ => unreachable!(), }; callback(state) } fn loop_dispatch>>(&mut self, timeout: D) -> IOResult<()> { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), #[cfg(feature = "x11")] _ => unreachable!(), }; self.event_loop.dispatch(timeout, state).map_err(|error| { error!("Error dispatching event loop: {}", error); error.into() }) } fn roundtrip(&mut self) -> Result { let state = match &mut self.window_target.p { PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(), #[cfg(feature = "x11")] _ => unreachable!(), }; let mut wayland_source = self.wayland_dispatcher.as_source_mut(); let event_queue = wayland_source.queue(); event_queue.roundtrip(state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch( error )))) }) } fn control_flow(&self) -> ControlFlow { self.window_target.p.control_flow() } fn exiting(&self) -> bool { self.window_target.p.exiting() } fn set_exit_code(&self, code: i32) { self.window_target.p.set_exit_code(code) } fn exit_code(&self) -> Option { self.window_target.p.exit_code() } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } pub struct EventLoopWindowTarget { /// The event loop wakeup source. pub event_loop_awakener: calloop::ping::Ping, /// The main queue used by the event loop. pub queue_handle: QueueHandle, /// The application's latest control_flow state pub(crate) control_flow: Cell, /// The application's exit state. pub(crate) exit: Cell>, // TODO remove that RefCell once we can pass `&mut` in `Window::new`. /// Winit state. pub state: RefCell, /// Dispatcher of Wayland events. pub wayland_dispatcher: WaylandDispatcher, /// Connection to the wayland server. pub connection: Connection, _marker: std::marker::PhantomData, } impl EventLoopWindowTarget { pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(Some(0)) } pub(crate) fn clear_exit(&self) { self.exit.set(None) } pub(crate) fn exiting(&self) -> bool { self.exit.get().is_some() } pub(crate) fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub(crate) fn exit_code(&self) -> Option { self.exit.get() } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { use sctk::reexports::client::Proxy; let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); display_handle.display = self.connection.display().id().as_ptr() as *mut _; rwh_05::RawDisplayHandle::Wayland(display_handle) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { use sctk::reexports::client::Proxy; Ok(rwh_06::WaylandDisplayHandle::new({ let ptr = self.connection.display().id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_display should never be null") }) .into()) } } winit-0.29.15/src/platform_impl/linux/wayland/event_loop/proxy.rs000064400000000000000000000015061046102023000232710ustar 00000000000000//! An event loop proxy. use std::sync::mpsc::SendError; use sctk::reexports::calloop::channel::Sender; use crate::event_loop::EventLoopClosed; /// A handle that can be sent across the threads and used to wake up the `EventLoop`. pub struct EventLoopProxy { user_events_sender: Sender, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), } } } impl EventLoopProxy { pub fn new(user_events_sender: Sender) -> Self { Self { user_events_sender } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_events_sender .send(event) .map_err(|SendError(error)| EventLoopClosed(error)) } } winit-0.29.15/src/platform_impl/linux/wayland/event_loop/sink.rs000064400000000000000000000030271046102023000230540ustar 00000000000000//! An event loop's sink to deliver events from the Wayland event callbacks. use std::vec::Drain; use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; use crate::platform_impl::platform::DeviceId as PlatformDeviceId; use crate::window::WindowId as RootWindowId; use super::{DeviceId, WindowId}; /// An event loop's sink to deliver events from the Wayland event callbacks /// to the winit's user. #[derive(Default)] pub struct EventSink { pub window_events: Vec>, } impl EventSink { pub fn new() -> Self { Default::default() } /// Return `true` if there're pending events. #[inline] pub fn is_empty(&self) -> bool { self.window_events.is_empty() } /// Add new device event to a queue. #[inline] pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { self.window_events.push(Event::DeviceEvent { event, device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), }); } /// Add new window event to a queue. #[inline] pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) { self.window_events.push(Event::WindowEvent { event, window_id: RootWindowId(window_id), }); } #[inline] pub fn append(&mut self, other: &mut Self) { self.window_events.append(&mut other.window_events); } #[inline] pub fn drain(&mut self) -> Drain<'_, Event<()>> { self.window_events.drain(..) } } winit-0.29.15/src/platform_impl/linux/wayland/mod.rs000064400000000000000000000044521046102023000205200ustar 00000000000000#![cfg(wayland_platform)] //! Winit's Wayland backend. use std::fmt::Display; use std::sync::Arc; use sctk::reexports::client::globals::{BindError, GlobalError}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{self, ConnectError, DispatchError, Proxy}; use crate::dpi::{LogicalSize, PhysicalSize}; pub use crate::platform_impl::platform::{OsError, WindowId}; pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; pub use output::{MonitorHandle, VideoMode}; pub use window::Window; mod event_loop; mod output; mod seat; mod state; mod types; mod window; #[derive(Debug)] pub enum WaylandError { /// Error connecting to the socket. Connection(ConnectError), /// Error binding the global. Global(GlobalError), // Bind error. Bind(BindError), /// Error during the dispatching the event queue. Dispatch(DispatchError), /// Calloop error. Calloop(calloop::Error), /// Wayland Wire(client::backend::WaylandError), } impl Display for WaylandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { WaylandError::Connection(error) => error.fmt(f), WaylandError::Global(error) => error.fmt(f), WaylandError::Bind(error) => error.fmt(f), WaylandError::Dispatch(error) => error.fmt(f), WaylandError::Calloop(error) => error.fmt(f), WaylandError::Wire(error) => error.fmt(f), } } } impl From for OsError { fn from(value: WaylandError) -> Self { Self::WaylandError(Arc::new(value)) } } /// Dummy device id, since Wayland doesn't have device events. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; impl DeviceId { pub const unsafe fn dummy() -> Self { DeviceId } } /// Get the WindowId out of the surface. #[inline] fn make_wid(surface: &WlSurface) -> WindowId { WindowId(surface.id().as_ptr() as u64) } /// The default routine does floor, but we need round on Wayland. fn logical_to_physical_rounded(size: LogicalSize, scale_factor: f64) -> PhysicalSize { let width = size.width as f64 * scale_factor; let height = size.height as f64 * scale_factor; (width.round(), height.round()).into() } winit-0.29.15/src/platform_impl/linux/wayland/output.rs000064400000000000000000000112221046102023000212720ustar 00000000000000use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::Proxy; use sctk::output::OutputData; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::platform_impl::platform::VideoMode as PlatformVideoMode; use super::event_loop::EventLoopWindowTarget; impl EventLoopWindowTarget { #[inline] pub fn available_monitors(&self) -> impl Iterator { self.state .borrow() .output_state .outputs() .map(MonitorHandle::new) } #[inline] pub fn primary_monitor(&self) -> Option { // There's no primary monitor on Wayland. None } } #[derive(Clone, Debug)] pub struct MonitorHandle { pub(crate) proxy: WlOutput, } impl MonitorHandle { #[inline] pub(crate) fn new(proxy: WlOutput) -> Self { Self { proxy } } #[inline] pub fn name(&self) -> Option { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| info.name.clone()) } #[inline] pub fn native_identifier(&self) -> u32 { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| info.id) } #[inline] pub fn size(&self) -> PhysicalSize { let output_data = self.proxy.data::().unwrap(); let dimensions = output_data.with_output_info(|info| { info.modes .iter() .find_map(|mode| mode.current.then_some(mode.dimensions)) }); match dimensions { Some((width, height)) => (width as u32, height as u32), _ => (0, 0), } .into() } #[inline] pub fn position(&self) -> PhysicalPosition { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| { info.logical_position.map_or_else( || { LogicalPosition::::from(info.location) .to_physical(info.scale_factor as f64) }, |logical_position| { LogicalPosition::::from(logical_position) .to_physical(info.scale_factor as f64) }, ) }) } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { let output_data = self.proxy.data::().unwrap(); output_data.with_output_info(|info| { info.modes .iter() .find_map(|mode| mode.current.then_some(mode.refresh_rate as u32)) }) } #[inline] pub fn scale_factor(&self) -> i32 { let output_data = self.proxy.data::().unwrap(); output_data.scale_factor() } #[inline] pub fn video_modes(&self) -> impl Iterator { let output_data = self.proxy.data::().unwrap(); let modes = output_data.with_output_info(|info| info.modes.clone()); let monitor = self.clone(); modes.into_iter().map(move |mode| { PlatformVideoMode::Wayland(VideoMode { size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), refresh_rate_millihertz: mode.refresh_rate as u32, bit_depth: 32, monitor: monitor.clone(), }) }) } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.native_identifier() == other.native_identifier() } } 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 { self.native_identifier().cmp(&other.native_identifier()) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { self.native_identifier().hash(state); } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoMode { pub(crate) size: PhysicalSize, pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { self.size } #[inline] pub fn bit_depth(&self) -> u16 { self.bit_depth } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } winit-0.29.15/src/platform_impl/linux/wayland/seat/keyboard/mod.rs000064400000000000000000000351721046102023000232570ustar 00000000000000//! The keyboard input handling. use std::sync::Mutex; use std::time::Duration; use calloop::timer::{TimeoutAction, Timer}; use calloop::{LoopHandle, RegistrationToken}; use log::warn; use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_keyboard::{ Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, }; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; use crate::event::{ElementState, WindowEvent}; use crate::keyboard::ModifiersState; use crate::platform_impl::common::xkb::Context; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::seat::WinitSeatState; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; impl Dispatch for WinitState { fn event( state: &mut WinitState, wl_keyboard: &WlKeyboard, event: ::Event, data: &KeyboardData, _: &Connection, _: &QueueHandle, ) { let seat_state = match state.seats.get_mut(&data.seat.id()) { Some(seat_state) => seat_state, None => return, }; match event { WlKeyboardEvent::Keymap { format, fd, size } => match format { WEnum::Value(format) => match format { WlKeymapFormat::NoKeymap => { warn!("non-xkb compatible keymap") } WlKeymapFormat::XkbV1 => { let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; context.set_keymap_from_fd(fd, size as usize); } _ => unreachable!(), }, WEnum::Unknown(value) => { warn!("unknown keymap format 0x{:x}", value) } }, WlKeyboardEvent::Enter { surface, .. } => { let window_id = wayland::make_wid(&surface); // Mark the window as focused. let was_unfocused = match state.windows.get_mut().get(&window_id) { Some(window) => { let mut window = window.lock().unwrap(); let was_unfocused = !window.has_focus(); window.add_seat_focus(data.seat.id()); was_unfocused } None => return, }; // Drop the repeat, if there were any. let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } *data.window_id.lock().unwrap() = Some(window_id); // The keyboard focus is considered as general focus. if was_unfocused { state .events_sink .push_window_event(WindowEvent::Focused(true), window_id); } // HACK: this is just for GNOME not fixing their ordering issue of modifiers. if std::mem::take(&mut seat_state.modifiers_pending) { state.events_sink.push_window_event( WindowEvent::ModifiersChanged(seat_state.modifiers.into()), window_id, ); } } WlKeyboardEvent::Leave { surface, .. } => { let window_id = wayland::make_wid(&surface); // NOTE: we should drop the repeat regardless whethere it was for the present // window of for the window which just went gone. let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } // NOTE: The check whether the window exists is essential as we might get a // nil surface, regardless of what protocol says. let focused = match state.windows.get_mut().get(&window_id) { Some(window) => { let mut window = window.lock().unwrap(); window.remove_seat_focus(&data.seat.id()); window.has_focus() } None => return, }; // We don't need to update it above, because the next `Enter` will overwrite // anyway. *data.window_id.lock().unwrap() = None; if !focused { // Notify that no modifiers are being pressed. state.events_sink.push_window_event( WindowEvent::ModifiersChanged(ModifiersState::empty().into()), window_id, ); state .events_sink .push_window_event(WindowEvent::Focused(false), window_id); } } WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Pressed), .. } => { let key = key + 8; key_input( seat_state, &mut state.events_sink, data, key, ElementState::Pressed, false, ); let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); let delay = match keyboard_state.repeat_info { RepeatInfo::Repeat { delay, .. } => delay, RepeatInfo::Disable => return, }; if !keyboard_state .xkb_context .keymap_mut() .unwrap() .key_repeats(key) { return; } keyboard_state.current_repeat = Some(key); // NOTE terminate ongoing timer and start a new timer. if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } let timer = Timer::from_duration(delay); let wl_keyboard = wl_keyboard.clone(); keyboard_state.repeat_token = keyboard_state .loop_handle .insert_source(timer, move |_, _, state| { // Required to handle the wakeups from the repeat sources. state.dispatched_events = true; let data = wl_keyboard.data::().unwrap(); let seat_state = state.seats.get_mut(&data.seat.id()).unwrap(); // NOTE: The removed on event source is batched, but key change to // `None` is instant. let repeat_keycode = match seat_state.keyboard_state.as_ref().unwrap().current_repeat { Some(repeat_keycode) => repeat_keycode, None => return TimeoutAction::Drop, }; key_input( seat_state, &mut state.events_sink, data, repeat_keycode, ElementState::Pressed, true, ); // NOTE: the gap could change dynamically while repeat is going. match seat_state.keyboard_state.as_ref().unwrap().repeat_info { RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap), RepeatInfo::Disable => TimeoutAction::Drop, } }) .ok(); } WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => { let key = key + 8; key_input( seat_state, &mut state.events_sink, data, key, ElementState::Released, false, ); let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); if keyboard_state.repeat_info != RepeatInfo::Disable && keyboard_state .xkb_context .keymap_mut() .unwrap() .key_repeats(key) && Some(key) == keyboard_state.current_repeat { keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); } } } WlKeyboardEvent::Modifiers { mods_depressed, mods_latched, mods_locked, group, .. } => { let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; let xkb_state = match xkb_context.state_mut() { Some(state) => state, None => return, }; xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group); seat_state.modifiers = xkb_state.modifiers().into(); // HACK: part of the workaround from `WlKeyboardEvent::Enter`. let window_id = match *data.window_id.lock().unwrap() { Some(window_id) => window_id, None => { seat_state.modifiers_pending = true; return; } }; state.events_sink.push_window_event( WindowEvent::ModifiersChanged(seat_state.modifiers.into()), window_id, ); } WlKeyboardEvent::RepeatInfo { rate, delay } => { let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.repeat_info = if rate == 0 { // Stop the repeat once we get a disable event. keyboard_state.current_repeat = None; if let Some(repeat_token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(repeat_token); } RepeatInfo::Disable } else { let gap = Duration::from_micros(1_000_000 / rate as u64); let delay = Duration::from_millis(delay as u64); RepeatInfo::Repeat { gap, delay } }; } _ => unreachable!(), } } } /// The state of the keyboard on the current seat. #[derive(Debug)] pub struct KeyboardState { /// The underlying WlKeyboard. pub keyboard: WlKeyboard, /// Loop handle to handle key repeat. pub loop_handle: LoopHandle<'static, WinitState>, /// The state of the keyboard. pub xkb_context: Context, /// The information about the repeat rate obtained from the compositor. pub repeat_info: RepeatInfo, /// The token of the current handle inside the calloop's event loop. pub repeat_token: Option, /// The current repeat raw key. pub current_repeat: Option, } impl KeyboardState { pub fn new(keyboard: WlKeyboard, loop_handle: LoopHandle<'static, WinitState>) -> Self { Self { keyboard, loop_handle, xkb_context: Context::new().unwrap(), repeat_info: RepeatInfo::default(), repeat_token: None, current_repeat: None, } } } impl Drop for KeyboardState { fn drop(&mut self) { if self.keyboard.version() >= 3 { self.keyboard.release(); } if let Some(token) = self.repeat_token.take() { self.loop_handle.remove(token); } } } /// The rate at which a pressed key is repeated. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RepeatInfo { /// Keys will be repeated at the specified rate and delay. Repeat { /// The time between the key repeats. gap: Duration, /// Delay (in milliseconds) between a key press and the start of repetition. delay: Duration, }, /// Keys should not be repeated. Disable, } impl Default for RepeatInfo { /// The default repeat rate is 25 keys per second with the delay of 200ms. /// /// The values are picked based on the default in various compositors and Xorg. fn default() -> Self { Self::Repeat { gap: Duration::from_millis(40), delay: Duration::from_millis(200), } } } /// Keyboard user data. #[derive(Debug)] pub struct KeyboardData { /// The currently focused window surface. Could be `None` on bugged compositors, like mutter. window_id: Mutex>, /// The seat used to create this keyboard. seat: WlSeat, } impl KeyboardData { pub fn new(seat: WlSeat) -> Self { Self { window_id: Default::default(), seat, } } } fn key_input( seat_state: &mut WinitSeatState, event_sink: &mut EventSink, data: &KeyboardData, keycode: u32, state: ElementState, repeat: bool, ) { let window_id = match *data.window_id.lock().unwrap() { Some(window_id) => window_id, None => return, }; let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { let event = key_context.process_key_event(keycode, state, repeat); let event = WindowEvent::KeyboardInput { device_id, event, is_synthetic: false, }; event_sink.push_window_event(event, window_id); } } winit-0.29.15/src/platform_impl/linux/wayland/seat/mod.rs000064400000000000000000000161451046102023000214560ustar 00000000000000//! Seat handling. use std::sync::Arc; use ahash::AHashMap; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use crate::event::WindowEvent; use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::state::WinitState; mod keyboard; mod pointer; mod text_input; mod touch; pub use pointer::relative_pointer::RelativePointerState; pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; pub use text_input::{TextInputState, ZwpTextInputV3Ext}; use keyboard::{KeyboardData, KeyboardState}; use text_input::TextInputData; use touch::TouchPoint; #[derive(Debug, Default)] pub struct WinitSeatState { /// The pointer bound on the seat. pointer: Option>>, /// The touch bound on the seat. touch: Option, /// The mapping from touched points to the surfaces they're present. touch_map: AHashMap, /// The text input bound on the seat. text_input: Option>, /// The relative pointer bound on the seat. relative_pointer: Option, /// The keyboard bound on the seat. keyboard_state: Option, /// The current modifiers state on the seat. modifiers: ModifiersState, /// Wether we have pending modifiers. modifiers_pending: bool, } impl WinitSeatState { pub fn new() -> Self { Default::default() } } impl SeatHandler for WinitState { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_capability( &mut self, _: &Connection, queue_handle: &QueueHandle, seat: WlSeat, capability: SeatCapability, ) { let seat_state = self.seats.get_mut(&seat.id()).unwrap(); match capability { SeatCapability::Touch if seat_state.touch.is_none() => { seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok(); } SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => { let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone())); seat_state.keyboard_state = Some(KeyboardState::new(keyboard, self.loop_handle.clone())); } SeatCapability::Pointer if seat_state.pointer.is_none() => { let surface = self.compositor_state.create_surface(queue_handle); let surface_id = surface.id(); let pointer_data = WinitPointerData::new(seat.clone()); let themed_pointer = self .seat_state .get_pointer_with_theme_and_data( queue_handle, &seat, self.shm.wl_shm(), surface, ThemeSpec::System, pointer_data, ) .expect("failed to create pointer with present capability."); seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| { manager.get_relative_pointer( themed_pointer.pointer(), queue_handle, sctk::globals::GlobalData, ) }); let themed_pointer = Arc::new(themed_pointer); // Register cursor surface. self.pointer_surfaces .insert(surface_id, themed_pointer.clone()); seat_state.pointer = Some(themed_pointer); } _ => (), } if let Some(text_input_state) = seat_state .text_input .is_none() .then_some(self.text_input_state.as_ref()) .flatten() { seat_state.text_input = Some(Arc::new(text_input_state.get_text_input( &seat, queue_handle, TextInputData::default(), ))); } } fn remove_capability( &mut self, _: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, capability: SeatCapability, ) { let seat_state = self.seats.get_mut(&seat.id()).unwrap(); if let Some(text_input) = seat_state.text_input.take() { text_input.destroy(); } match capability { SeatCapability::Touch => { if let Some(touch) = seat_state.touch.take() { if touch.version() >= 3 { touch.release(); } } } SeatCapability::Pointer => { if let Some(relative_pointer) = seat_state.relative_pointer.take() { relative_pointer.destroy(); } if let Some(pointer) = seat_state.pointer.take() { let pointer_data = pointer.pointer().winit_data(); // Remove the cursor from the mapping. let surface_id = pointer.surface().id(); let _ = self.pointer_surfaces.remove(&surface_id); // Remove the inner locks/confines before dropping the pointer. pointer_data.unlock_pointer(); pointer_data.unconfine_pointer(); if pointer.pointer().version() >= 3 { pointer.pointer().release(); } } } SeatCapability::Keyboard => { seat_state.keyboard_state = None; self.on_keyboard_destroy(&seat.id()); } _ => (), } } fn new_seat( &mut self, _connection: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, ) { self.seats.insert(seat.id(), WinitSeatState::new()); } fn remove_seat( &mut self, _connection: &Connection, _queue_handle: &QueueHandle, seat: WlSeat, ) { let _ = self.seats.remove(&seat.id()); self.on_keyboard_destroy(&seat.id()); } } impl WinitState { fn on_keyboard_destroy(&mut self, seat: &ObjectId) { for (window_id, window) in self.windows.get_mut() { let mut window = window.lock().unwrap(); let had_focus = window.has_focus(); window.remove_seat_focus(seat); if had_focus != window.has_focus() { self.events_sink .push_window_event(WindowEvent::Focused(false), *window_id); } } } } sctk::delegate_seat!(WinitState); winit-0.29.15/src/platform_impl/linux/wayland/seat/pointer/mod.rs000064400000000000000000000426351046102023000231410ustar 00000000000000//! The pointer events. use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::time::Duration; use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_pointer::WlPointer; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch}; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::csd_frame::FrameClick; use sctk::compositor::SurfaceData; use sctk::globals::GlobalData; use sctk::seat::pointer::{PointerData, PointerDataExt}; use sctk::seat::pointer::{PointerEvent, PointerEventKind, PointerHandler}; use sctk::seat::SeatState; use crate::dpi::{LogicalPosition, PhysicalPosition}; use crate::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; pub mod relative_pointer; impl PointerHandler for WinitState { fn pointer_frame( &mut self, connection: &Connection, _: &QueueHandle, pointer: &WlPointer, events: &[PointerEvent], ) { let seat = pointer.winit_data().seat(); let seat_state = self.seats.get(&seat.id()).unwrap(); let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); for event in events { let surface = &event.surface; // The parent surface. let parent_surface = match event.surface.data::() { Some(data) => data.parent_surface().unwrap_or(surface), None => continue, }; let window_id = wayland::make_wid(parent_surface); // Ensure that window exists. let mut window = match self.windows.get_mut().get_mut(&window_id) { Some(window) => window.lock().unwrap(), None => continue, }; let scale_factor = window.scale_factor(); let position: PhysicalPosition = LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor); match event.kind { // Pointer movements on decorations. PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. } if parent_surface != surface => { if let Some(icon) = window.frame_point_moved( seat, surface, Duration::ZERO, event.position.0, event.position.1, ) { if let Some(pointer) = seat_state.pointer.as_ref() { let _ = pointer.set_cursor(connection, icon); } } } PointerEventKind::Leave { .. } if parent_surface != surface => { window.frame_point_left(); } ref kind @ PointerEventKind::Press { button, serial, time, } | ref kind @ PointerEventKind::Release { button, serial, time, } if parent_surface != surface => { let click = match wayland_button_to_winit(button) { MouseButton::Left => FrameClick::Normal, MouseButton::Right => FrameClick::Alternate, _ => continue, }; let pressed = matches!(kind, PointerEventKind::Press { .. }); // Emulate click on the frame. window.frame_click( click, pressed, seat, serial, Duration::from_millis(time as u64), window_id, &mut self.window_compositor_updates, ); } // Regular events on the main surface. PointerEventKind::Enter { .. } => { self.events_sink .push_window_event(WindowEvent::CursorEntered { device_id }, window_id); if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { window.pointer_entered(pointer); } // Set the currently focused surface. pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); self.events_sink.push_window_event( WindowEvent::CursorMoved { device_id, position, }, window_id, ); } PointerEventKind::Leave { .. } => { if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { window.pointer_left(pointer); } // Remove the active surface. pointer.winit_data().inner.lock().unwrap().surface = None; self.events_sink .push_window_event(WindowEvent::CursorLeft { device_id }, window_id); } PointerEventKind::Motion { .. } => { self.events_sink.push_window_event( WindowEvent::CursorMoved { device_id, position, }, window_id, ); } ref kind @ PointerEventKind::Press { button, serial, .. } | ref kind @ PointerEventKind::Release { button, serial, .. } => { // Update the last button serial. pointer .winit_data() .inner .lock() .unwrap() .latest_button_serial = serial; let button = wayland_button_to_winit(button); let state = if matches!(kind, PointerEventKind::Press { .. }) { ElementState::Pressed } else { ElementState::Released }; self.events_sink.push_window_event( WindowEvent::MouseInput { device_id, state, button, }, window_id, ); } PointerEventKind::Axis { horizontal, vertical, .. } => { // Get the current phase. let mut pointer_data = pointer.winit_data().inner.lock().unwrap(); let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0; // Figure out what to do about start/ended phases here. // // Figure out how to deal with `Started`. Also the `Ended` is not guaranteed // to be sent for mouse wheels. let phase = if horizontal.stop || vertical.stop { TouchPhase::Ended } else { match pointer_data.phase { // Descrete scroll only results in moved events. _ if has_discrete_scroll => TouchPhase::Moved, TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, _ => TouchPhase::Started, } }; // Update the phase. pointer_data.phase = phase; // Mice events have both pixel and discrete delta's at the same time. So prefer // the descrite values if they are present. let delta = if has_discrete_scroll { // XXX Wayland sign convention is the inverse of winit. MouseScrollDelta::LineDelta( (-horizontal.discrete) as f32, (-vertical.discrete) as f32, ) } else { // XXX Wayland sign convention is the inverse of winit. MouseScrollDelta::PixelDelta( LogicalPosition::new(-horizontal.absolute, -vertical.absolute) .to_physical(scale_factor), ) }; self.events_sink.push_window_event( WindowEvent::MouseWheel { device_id, delta, phase, }, window_id, ) } } } } } #[derive(Debug)] pub struct WinitPointerData { /// The inner winit data associated with the pointer. inner: Mutex, /// The data required by the sctk. sctk_data: PointerData, } impl WinitPointerData { pub fn new(seat: WlSeat) -> Self { Self { inner: Mutex::new(WinitPointerDataInner::default()), sctk_data: PointerData::new(seat), } } pub fn lock_pointer( &self, pointer_constraints: &PointerConstraintsState, surface: &WlSurface, pointer: &WlPointer, queue_handle: &QueueHandle, ) { let mut inner = self.inner.lock().unwrap(); if inner.locked_pointer.is_none() { inner.locked_pointer = Some(pointer_constraints.lock_pointer( surface, pointer, None, Lifetime::Persistent, queue_handle, GlobalData, )); } } pub fn unlock_pointer(&self) { let mut inner = self.inner.lock().unwrap(); if let Some(locked_pointer) = inner.locked_pointer.take() { locked_pointer.destroy(); } } pub fn confine_pointer( &self, pointer_constraints: &PointerConstraintsState, surface: &WlSurface, pointer: &WlPointer, queue_handle: &QueueHandle, ) { self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer( surface, pointer, None, Lifetime::Persistent, queue_handle, GlobalData, )); } pub fn unconfine_pointer(&self) { let inner = self.inner.lock().unwrap(); if let Some(confined_pointer) = inner.confined_pointer.as_ref() { confined_pointer.destroy(); } } /// Seat associated with this pointer. pub fn seat(&self) -> &WlSeat { self.sctk_data.seat() } /// Active window. pub fn focused_window(&self) -> Option { self.inner.lock().unwrap().surface } /// Last button serial. pub fn latest_button_serial(&self) -> u32 { self.sctk_data.latest_button_serial().unwrap_or_default() } /// Last enter serial. pub fn latest_enter_serial(&self) -> u32 { self.sctk_data.latest_enter_serial().unwrap_or_default() } pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) { let inner = self.inner.lock().unwrap(); if let Some(locked_pointer) = inner.locked_pointer.as_ref() { locked_pointer.set_cursor_position_hint(surface_x, surface_y); } } } impl PointerDataExt for WinitPointerData { fn pointer_data(&self) -> &PointerData { &self.sctk_data } } #[derive(Debug)] pub struct WinitPointerDataInner { /// The associated locked pointer. locked_pointer: Option, /// The associated confined pointer. confined_pointer: Option, /// Serial of the last button event. latest_button_serial: u32, /// Currently focused window. surface: Option, /// Current axis phase. phase: TouchPhase, } impl Drop for WinitPointerDataInner { fn drop(&mut self) { if let Some(locked_pointer) = self.locked_pointer.take() { locked_pointer.destroy(); } if let Some(confined_pointer) = self.confined_pointer.take() { confined_pointer.destroy(); } } } impl Default for WinitPointerDataInner { fn default() -> Self { Self { surface: None, locked_pointer: None, confined_pointer: None, latest_button_serial: 0, phase: TouchPhase::Ended, } } } /// Convert the Wayland button into winit. fn wayland_button_to_winit(button: u32) -> MouseButton { // These values are coming from . const BTN_LEFT: u32 = 0x110; const BTN_RIGHT: u32 = 0x111; const BTN_MIDDLE: u32 = 0x112; const BTN_SIDE: u32 = 0x113; const BTN_EXTRA: u32 = 0x114; const BTN_FORWARD: u32 = 0x115; const BTN_BACK: u32 = 0x116; match button { BTN_LEFT => MouseButton::Left, BTN_RIGHT => MouseButton::Right, BTN_MIDDLE => MouseButton::Middle, BTN_BACK | BTN_SIDE => MouseButton::Back, BTN_FORWARD | BTN_EXTRA => MouseButton::Forward, button => MouseButton::Other(button as u16), } } pub trait WinitPointerDataExt { fn winit_data(&self) -> &WinitPointerData; } impl WinitPointerDataExt for WlPointer { fn winit_data(&self) -> &WinitPointerData { self.data::() .expect("failed to get pointer data.") } } pub struct PointerConstraintsState { pointer_constraints: ZwpPointerConstraintsV1, } impl PointerConstraintsState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { pointer_constraints, }) } } impl Deref for PointerConstraintsState { type Target = ZwpPointerConstraintsV1; fn deref(&self) -> &Self::Target { &self.pointer_constraints } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpPointerConstraintsV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpLockedPointerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for PointerConstraintsState { fn event( _state: &mut WinitState, _proxy: &ZwpConfinedPointerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for SeatState { fn event( _: &mut WinitState, _: &WpCursorShapeDeviceV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wp_cursor_shape_manager has no events") } } impl Dispatch for SeatState { fn event( _: &mut WinitState, _: &WpCursorShapeManagerV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wp_cursor_device_manager has no events") } } delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState); winit-0.29.15/src/platform_impl/linux/wayland/seat/pointer/relative_pointer.rs000064400000000000000000000055401046102023000257270ustar 00000000000000//! Relative pointer. use std::ops::Deref; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::{delegate_dispatch, Dispatch}; use sctk::reexports::client::{Connection, QueueHandle}; use sctk::reexports::protocols::wp::relative_pointer::zv1::{ client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1}, }; use sctk::globals::GlobalData; use crate::event::DeviceEvent; use crate::platform_impl::wayland::state::WinitState; /// Wrapper around the relative pointer. pub struct RelativePointerState { manager: ZwpRelativePointerManagerV1, } impl RelativePointerState { /// Create new relative pointer manager. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } } impl Deref for RelativePointerState { type Target = ZwpRelativePointerManagerV1; fn deref(&self) -> &Self::Target { &self.manager } } impl Dispatch for RelativePointerState { fn event( _state: &mut WinitState, _proxy: &ZwpRelativePointerManagerV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for RelativePointerState { fn event( state: &mut WinitState, _proxy: &ZwpRelativePointerV1, event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { let (dx_unaccel, dy_unaccel) = match event { zwp_relative_pointer_v1::Event::RelativeMotion { dx_unaccel, dy_unaccel, .. } => (dx_unaccel, dy_unaccel), _ => return, }; state.events_sink.push_device_event( DeviceEvent::Motion { axis: 0, value: dx_unaccel, }, super::DeviceId, ); state.events_sink.push_device_event( DeviceEvent::Motion { axis: 1, value: dy_unaccel, }, super::DeviceId, ); state.events_sink.push_device_event( DeviceEvent::MouseMotion { delta: (dx_unaccel, dy_unaccel), }, super::DeviceId, ); } } delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState); delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState); winit-0.29.15/src/platform_impl/linux/wayland/seat/text_input/mod.rs000064400000000000000000000162721046102023000236620ustar 00000000000000use std::ops::Deref; use sctk::globals::GlobalData; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::Event as TextInputEvent; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{ ContentHint, ContentPurpose, ZwpTextInputV3, }; use crate::event::{Ime, WindowEvent}; use crate::platform_impl::wayland; use crate::platform_impl::wayland::state::WinitState; use crate::window::ImePurpose; pub struct TextInputState { text_input_manager: ZwpTextInputManagerV3, } impl TextInputState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { text_input_manager }) } } impl Deref for TextInputState { type Target = ZwpTextInputManagerV3; fn deref(&self) -> &Self::Target { &self.text_input_manager } } impl Dispatch for TextInputState { fn event( _state: &mut WinitState, _proxy: &ZwpTextInputManagerV3, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for TextInputState { fn event( state: &mut WinitState, text_input: &ZwpTextInputV3, event: ::Event, data: &TextInputData, _conn: &Connection, _qhandle: &QueueHandle, ) { let windows = state.windows.get_mut(); let mut text_input_data = data.inner.lock().unwrap(); match event { TextInputEvent::Enter { surface } => { let window_id = wayland::make_wid(&surface); text_input_data.surface = Some(surface); let mut window = match windows.get(&window_id) { Some(window) => window.lock().unwrap(), None => return, }; if window.ime_allowed() { text_input.enable(); text_input.set_content_type_by_purpose(window.ime_purpose()); text_input.commit(); state .events_sink .push_window_event(WindowEvent::Ime(Ime::Enabled), window_id); } window.text_input_entered(text_input); } TextInputEvent::Leave { surface } => { text_input_data.surface = None; // Always issue a disable. text_input.disable(); text_input.commit(); let window_id = wayland::make_wid(&surface); // XXX this check is essential, because `leave` could have a // refence to nil surface... let mut window = match windows.get(&window_id) { Some(window) => window.lock().unwrap(), None => return, }; window.text_input_left(text_input); state .events_sink .push_window_event(WindowEvent::Ime(Ime::Disabled), window_id); } TextInputEvent::PreeditString { text, cursor_begin, cursor_end, } => { let text = text.unwrap_or_default(); let cursor_begin = usize::try_from(cursor_begin) .ok() .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); let cursor_end = usize::try_from(cursor_end) .ok() .and_then(|idx| text.is_char_boundary(idx).then_some(idx)); text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end, }) } TextInputEvent::CommitString { text } => { text_input_data.pending_preedit = None; text_input_data.pending_commit = text; } TextInputEvent::Done { .. } => { let window_id = match text_input_data.surface.as_ref() { Some(surface) => wayland::make_wid(surface), None => return, }; // Clear preedit at the start of `Done`. state.events_sink.push_window_event( WindowEvent::Ime(Ime::Preedit(String::new(), None)), window_id, ); // Send `Commit`. if let Some(text) = text_input_data.pending_commit.take() { state .events_sink .push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id); } // Send preedit. if let Some(preedit) = text_input_data.pending_preedit.take() { let cursor_range = preedit .cursor_begin .map(|b| (b, preedit.cursor_end.unwrap_or(b))); state.events_sink.push_window_event( WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)), window_id, ); } } TextInputEvent::DeleteSurroundingText { .. } => { // Not handled. } _ => {} } } } pub trait ZwpTextInputV3Ext { fn set_content_type_by_purpose(&self, purpose: ImePurpose); } impl ZwpTextInputV3Ext for ZwpTextInputV3 { fn set_content_type_by_purpose(&self, purpose: ImePurpose) { let (hint, purpose) = match purpose { ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal), ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password), ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal), }; self.set_content_type(hint, purpose); } } /// The Data associated with the text input. #[derive(Default)] pub struct TextInputData { inner: std::sync::Mutex, } #[derive(Default)] pub struct TextInputDataInner { /// The `WlSurface` we're performing input to. surface: Option, /// The commit to submit on `done`. pending_commit: Option, /// The preedit to submit on `done`. pending_preedit: Option, } /// The state of the preedit. struct Preedit { text: String, cursor_begin: Option, cursor_end: Option, } delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState); delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState); winit-0.29.15/src/platform_impl/linux/wayland/seat/touch/mod.rs000064400000000000000000000135061046102023000225760ustar 00000000000000//! Touch handling. use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::seat::touch::{TouchData, TouchHandler}; use crate::dpi::LogicalPosition; use crate::event::{Touch, TouchPhase, WindowEvent}; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId}; impl TouchHandler for WinitState { fn down( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, _: u32, surface: WlSurface, id: i32, position: (f64, f64), ) { let window_id = wayland::make_wid(&surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; let location = LogicalPosition::::from(position); let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); // Update the state of the point. seat_state .touch_map .insert(id, TouchPoint { surface, location }); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Started, location: location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn up( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, _: u32, id: i32, ) { let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); // Remove the touch point. let touch_point = match seat_state.touch_map.remove(&id) { Some(touch_point) => touch_point, None => return, }; let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Ended, location: touch_point.location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn motion( &mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch, _: u32, id: i32, position: (f64, f64), ) { let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); // Remove the touch point. let touch_point = match seat_state.touch_map.get_mut(&id) { Some(touch_point) => touch_point, None => return, }; let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; touch_point.location = LogicalPosition::::from(position); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Moved, location: touch_point.location.to_physical(scale_factor), force: None, id: id as u64, }), window_id, ); } fn cancel(&mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch) { let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); for (id, touch_point) in seat_state.touch_map.drain() { let window_id = wayland::make_wid(&touch_point.surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), None => return, }; let location = touch_point.location.to_physical(scale_factor); self.events_sink.push_window_event( WindowEvent::Touch(Touch { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), phase: TouchPhase::Cancelled, location, force: None, id: id as u64, }), window_id, ); } } fn shape( &mut self, _: &Connection, _: &QueueHandle, _: &WlTouch, _: i32, _: f64, _: f64, ) { // Blank. } fn orientation(&mut self, _: &Connection, _: &QueueHandle, _: &WlTouch, _: i32, _: f64) { // Blank. } } /// The state of the touch point. #[derive(Debug)] pub struct TouchPoint { /// The surface on which the point is present. pub surface: WlSurface, /// The location of the point on the surface. pub location: LogicalPosition, } pub trait TouchDataExt { fn seat(&self) -> &WlSeat; } impl TouchDataExt for WlTouch { fn seat(&self) -> &WlSeat { self.data::() .expect("failed to get touch data.") .seat() } } sctk::delegate_touch!(WinitState); winit-0.29.15/src/platform_impl/linux/wayland/state.rs000064400000000000000000000341371046102023000210640ustar 00000000000000use std::cell::RefCell; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use ahash::AHashMap; use sctk::reexports::calloop::LoopHandle; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::globals::GlobalList; use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::compositor::{CompositorHandler, CompositorState}; use sctk::output::{OutputHandler, OutputState}; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::pointer::ThemedPointer; use sctk::seat::SeatState; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler}; use sctk::shell::xdg::XdgShell; use sctk::shell::WaylandSurface; use sctk::shm::{Shm, ShmHandler}; use sctk::subcompositor::SubcompositorState; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::output::MonitorHandle; use crate::platform_impl::wayland::seat::{ PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, WinitPointerDataExt, WinitSeatState, }; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager; use crate::platform_impl::wayland::types::wp_viewporter::ViewporterState; use crate::platform_impl::wayland::types::xdg_activation::XdgActivationState; use crate::platform_impl::wayland::window::{WindowRequests, WindowState}; use crate::platform_impl::wayland::{WaylandError, WindowId}; use crate::platform_impl::OsError; /// Winit's Wayland state. pub struct WinitState { /// The WlRegistry. pub registry_state: RegistryState, /// The state of the WlOutput handling. pub output_state: OutputState, /// The compositor state which is used to create new windows and regions. pub compositor_state: Arc, /// The state of the subcompositor. pub subcompositor_state: Option>, /// The seat state responsible for all sorts of input. pub seat_state: SeatState, /// The shm for software buffers, such as cursors. pub shm: Shm, /// The XDG shell that is used for widnows. pub xdg_shell: XdgShell, /// The currently present windows. pub windows: RefCell>>>, /// The requests from the `Window` to EventLoop, such as close operations and redraw requests. pub window_requests: RefCell>>, /// The events that were generated directly from the window. pub window_events_sink: Arc>, /// The update for the `windows` comming from the compositor. pub window_compositor_updates: Vec, /// Currently handled seats. pub seats: AHashMap, /// Currently present cursor surfaces. pub pointer_surfaces: AHashMap>>, /// The state of the text input on the client. pub text_input_state: Option, /// Observed monitors. pub monitors: Arc>>, /// Sink to accumulate window events from the compositor, which is latter dispatched in /// event loop run. pub events_sink: EventSink, /// Xdg activation. pub xdg_activation: Option, /// Relative pointer. pub relative_pointer: Option, /// Pointer constraints to handle pointer locking and confining. pub pointer_constraints: Option>, /// Viewporter state on the given window. pub viewporter_state: Option, /// Fractional scaling manager. pub fractional_scaling_manager: Option, /// KWin blur manager. pub kwin_blur_manager: Option, /// Loop handle to re-register event sources, such as keyboard repeat. pub loop_handle: LoopHandle<'static, Self>, /// Whether we have dispatched events to the user thus we want to /// send `AboutToWait` and normally wakeup the user. pub dispatched_events: bool, } impl WinitState { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, loop_handle: LoopHandle<'static, WinitState>, ) -> Result { let registry_state = RegistryState::new(globals); let compositor_state = CompositorState::bind(globals, queue_handle).map_err(WaylandError::Bind)?; let subcompositor_state = match SubcompositorState::bind( compositor_state.wl_compositor().clone(), globals, queue_handle, ) { Ok(c) => Some(c), Err(e) => { warn!("Subcompositor protocol not available, ignoring CSD: {e:?}"); None } }; let output_state = OutputState::new(globals, queue_handle); let monitors = output_state.outputs().map(MonitorHandle::new).collect(); let seat_state = SeatState::new(globals, queue_handle); let mut seats = AHashMap::default(); for seat in seat_state.seats() { seats.insert(seat.id(), WinitSeatState::new()); } let (viewporter_state, fractional_scaling_manager) = if let Ok(fsm) = FractionalScalingManager::new(globals, queue_handle) { (ViewporterState::new(globals, queue_handle).ok(), Some(fsm)) } else { (None, None) }; Ok(Self { registry_state, compositor_state: Arc::new(compositor_state), subcompositor_state: subcompositor_state.map(Arc::new), output_state, seat_state, shm: Shm::bind(globals, queue_handle).map_err(WaylandError::Bind)?, xdg_shell: XdgShell::bind(globals, queue_handle).map_err(WaylandError::Bind)?, xdg_activation: XdgActivationState::bind(globals, queue_handle).ok(), windows: Default::default(), window_requests: Default::default(), window_compositor_updates: Vec::new(), window_events_sink: Default::default(), viewporter_state, fractional_scaling_manager, kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), seats, text_input_state: TextInputState::new(globals, queue_handle).ok(), relative_pointer: RelativePointerState::new(globals, queue_handle).ok(), pointer_constraints: PointerConstraintsState::new(globals, queue_handle) .map(Arc::new) .ok(), pointer_surfaces: Default::default(), monitors: Arc::new(Mutex::new(monitors)), events_sink: EventSink::new(), loop_handle, // Make it true by default. dispatched_events: true, }) } pub fn scale_factor_changed( &mut self, surface: &WlSurface, scale_factor: f64, is_legacy: bool, ) { // Check if the cursor surface. let window_id = super::make_wid(surface); if let Some(window) = self.windows.get_mut().get(&window_id) { // Don't update the scaling factor, when legacy method is used. if is_legacy && self.fractional_scaling_manager.is_some() { return; } // The scale factor change is for the window. let pos = if let Some(pos) = self .window_compositor_updates .iter() .position(|update| update.window_id == window_id) { pos } else { self.window_compositor_updates .push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Update the scale factor right away. window.lock().unwrap().set_scale_factor(scale_factor); self.window_compositor_updates[pos].scale_changed = true; } else if let Some(pointer) = self.pointer_surfaces.get(&surface.id()) { // Get the window, where the pointer resides right now. let focused_window = match pointer.pointer().winit_data().focused_window() { Some(focused_window) => focused_window, None => return, }; if let Some(window_state) = self.windows.get_mut().get(&focused_window) { window_state.lock().unwrap().reload_cursor_style() } } } pub fn queue_close(updates: &mut Vec, window_id: WindowId) { let pos = if let Some(pos) = updates .iter() .position(|update| update.window_id == window_id) { pos } else { updates.push(WindowCompositorUpdate::new(window_id)); updates.len() - 1 }; updates[pos].close_window = true; } } impl ShmHandler for WinitState { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl WindowHandler for WinitState { fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { let window_id = super::make_wid(window.wl_surface()); Self::queue_close(&mut self.window_compositor_updates, window_id); } fn configure( &mut self, _: &Connection, _: &QueueHandle, window: &Window, configure: WindowConfigure, _serial: u32, ) { let window_id = super::make_wid(window.wl_surface()); let pos = if let Some(pos) = self .window_compositor_updates .iter() .position(|update| update.window_id == window_id) { pos } else { self.window_compositor_updates .push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Populate the configure to the window. self.window_compositor_updates[pos].resized |= self .windows .get_mut() .get_mut(&window_id) .expect("got configure for dead window.") .lock() .unwrap() .configure(configure, &self.shm, &self.subcompositor_state); // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the // users, since it can break a lot of things, thus it'll ask users to redraw instead. self.window_requests .get_mut() .get(&window_id) .unwrap() .redraw_requested .store(true, Ordering::Relaxed); // Manually mark that we've got an event, since configure may not generate a resize. self.dispatched_events = true; } } impl OutputHandler for WinitState { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output(&mut self, _: &Connection, _: &QueueHandle, output: WlOutput) { self.monitors .lock() .unwrap() .push(MonitorHandle::new(output)); } fn update_output(&mut self, _: &Connection, _: &QueueHandle, updated: WlOutput) { let mut monitors = self.monitors.lock().unwrap(); let updated = MonitorHandle::new(updated); if let Some(pos) = monitors.iter().position(|output| output == &updated) { monitors[pos] = updated } else { monitors.push(updated) } } fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle, removed: WlOutput) { let mut monitors = self.monitors.lock().unwrap(); let removed = MonitorHandle::new(removed); if let Some(pos) = monitors.iter().position(|output| output == &removed) { monitors.remove(pos); } } } impl CompositorHandler for WinitState { fn transform_changed( &mut self, _: &Connection, _: &QueueHandle, _: &wayland_client::protocol::wl_surface::WlSurface, _: wayland_client::protocol::wl_output::Transform, ) { // TODO(kchibisov) we need to expose it somehow in winit. } fn scale_factor_changed( &mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, scale_factor: i32, ) { self.scale_factor_changed(surface, scale_factor as f64, true) } fn frame(&mut self, _: &Connection, _: &QueueHandle, surface: &WlSurface, _: u32) { let window_id = super::make_wid(surface); let window = match self.windows.get_mut().get(&window_id) { Some(window) => window, None => return, }; // In case we have a redraw requested we must indicate the wake up. if self .window_requests .get_mut() .get(&window_id) .unwrap() .redraw_requested .load(Ordering::Relaxed) { self.dispatched_events = true; } window.lock().unwrap().frame_callback_received(); } } impl ProvidesRegistryState for WinitState { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } sctk::registry_handlers![OutputState, SeatState]; } // The window update comming from the compositor. #[derive(Debug, Clone, Copy)] pub struct WindowCompositorUpdate { /// The id of the window this updates belongs to. pub window_id: WindowId, /// New window size. pub resized: bool, /// New scale factor. pub scale_changed: bool, /// Close the window. pub close_window: bool, } impl WindowCompositorUpdate { fn new(window_id: WindowId) -> Self { Self { window_id, resized: false, scale_changed: false, close_window: false, } } } sctk::delegate_subcompositor!(WinitState); sctk::delegate_compositor!(WinitState); sctk::delegate_output!(WinitState); sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); sctk::delegate_xdg_window!(WinitState); winit-0.29.15/src/platform_impl/linux/wayland/types/kwin_blur.rs000064400000000000000000000040161046102023000230750ustar 00000000000000//! Handling of KDE-compatible blur. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; use wayland_protocols_plasma::blur::client::{ org_kde_kwin_blur::OrgKdeKwinBlur, org_kde_kwin_blur_manager::OrgKdeKwinBlurManager, }; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// KWin blur manager. #[derive(Debug, Clone)] pub struct KWinBlurManager { manager: OrgKdeKwinBlurManager, } impl KWinBlurManager { pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } pub fn blur( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> OrgKdeKwinBlur { self.manager.create(surface, queue_handle, ()) } pub fn unset(&self, surface: &WlSurface) { self.manager.unset(surface) } } impl Dispatch for KWinBlurManager { fn event( _: &mut WinitState, _: &OrgKdeKwinBlurManager, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("no events defined for org_kde_kwin_blur_manager"); } } impl Dispatch for KWinBlurManager { fn event( _: &mut WinitState, _: &OrgKdeKwinBlur, _: ::Event, _: &(), _: &Connection, _: &QueueHandle, ) { unreachable!("no events defined for org_kde_kwin_blur"); } } delegate_dispatch!(WinitState: [OrgKdeKwinBlurManager: GlobalData] => KWinBlurManager); delegate_dispatch!(WinitState: [OrgKdeKwinBlur: ()] => KWinBlurManager); winit-0.29.15/src/platform_impl/linux/wayland/types/mod.rs000064400000000000000000000002231046102023000216540ustar 00000000000000//! Wayland protocol implementation boilerplate. pub mod kwin_blur; pub mod wp_fractional_scaling; pub mod wp_viewporter; pub mod xdg_activation; winit-0.29.15/src/platform_impl/linux/wayland/types/wp_fractional_scaling.rs000064400000000000000000000053031046102023000254310ustar 00000000000000//! Handling of the fractional scaling. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::Event as FractionalScalingEvent; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// The scaling factor denominator. const SCALE_DENOMINATOR: f64 = 120.; /// Fractional scaling manager. #[derive(Debug)] pub struct FractionalScalingManager { manager: WpFractionalScaleManagerV1, } pub struct FractionalScaling { /// The surface used for scaling. surface: WlSurface, } impl FractionalScalingManager { /// Create new viewporter. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { manager }) } pub fn fractional_scaling( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> WpFractionalScaleV1 { let data = FractionalScaling { surface: surface.clone(), }; self.manager .get_fractional_scale(surface, queue_handle, data) } } impl Dispatch for FractionalScalingManager { fn event( _: &mut WinitState, _: &WpFractionalScaleManagerV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } impl Dispatch for FractionalScalingManager { fn event( state: &mut WinitState, _: &WpFractionalScaleV1, event: ::Event, data: &FractionalScaling, _: &Connection, _: &QueueHandle, ) { if let FractionalScalingEvent::PreferredScale { scale } = event { state.scale_factor_changed(&data.surface, scale as f64 / SCALE_DENOMINATOR, false); } } } delegate_dispatch!(WinitState: [WpFractionalScaleManagerV1: GlobalData] => FractionalScalingManager); delegate_dispatch!(WinitState: [WpFractionalScaleV1: FractionalScaling] => FractionalScalingManager); winit-0.29.15/src/platform_impl/linux/wayland/types/wp_viewporter.rs000064400000000000000000000036651046102023000240260ustar 00000000000000//! Handling of the wp-viewporter. use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; use sctk::reexports::client::{delegate_dispatch, Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::wp::viewporter::client::wp_viewporter::WpViewporter; use sctk::globals::GlobalData; use crate::platform_impl::wayland::state::WinitState; /// Viewporter. #[derive(Debug)] pub struct ViewporterState { viewporter: WpViewporter, } impl ViewporterState { /// Create new viewporter. pub fn new( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let viewporter = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { viewporter }) } /// Get the viewport for the given object. pub fn get_viewport( &self, surface: &WlSurface, queue_handle: &QueueHandle, ) -> WpViewport { self.viewporter .get_viewport(surface, queue_handle, GlobalData) } } impl Dispatch for ViewporterState { fn event( _: &mut WinitState, _: &WpViewporter, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } impl Dispatch for ViewporterState { fn event( _: &mut WinitState, _: &WpViewport, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { // No events. } } delegate_dispatch!(WinitState: [WpViewporter: GlobalData] => ViewporterState); delegate_dispatch!(WinitState: [WpViewport: GlobalData] => ViewporterState); winit-0.29.15/src/platform_impl/linux/wayland/types/xdg_activation.rs000064400000000000000000000070121046102023000241030ustar 00000000000000//! Handling of xdg activation, which is used for user attention requests. use std::sync::atomic::AtomicBool; use std::sync::Weak; use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::globals::BindError; use sctk::reexports::client::globals::GlobalList; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Dispatch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::{ Event as ActivationTokenEvent, XdgActivationTokenV1, }; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::globals::GlobalData; use crate::event_loop::AsyncRequestSerial; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::WindowId; use crate::window::ActivationToken; pub struct XdgActivationState { xdg_activation: XdgActivationV1, } impl XdgActivationState { pub fn bind( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { let xdg_activation = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { xdg_activation }) } pub fn global(&self) -> &XdgActivationV1 { &self.xdg_activation } } impl Dispatch for XdgActivationState { fn event( _state: &mut WinitState, _proxy: &XdgActivationV1, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { } } impl Dispatch for XdgActivationState { fn event( state: &mut WinitState, proxy: &XdgActivationTokenV1, event: ::Event, data: &XdgActivationTokenData, _: &Connection, _: &QueueHandle, ) { let token = match event { ActivationTokenEvent::Done { token } => token, _ => return, }; let global = state .xdg_activation .as_ref() .expect("got xdg_activation event without global.") .global(); match data { XdgActivationTokenData::Attention((surface, fence)) => { global.activate(token, surface); // Mark that no request attention is in process. if let Some(attention_requested) = fence.upgrade() { attention_requested.store(false, std::sync::atomic::Ordering::Relaxed); } } XdgActivationTokenData::Obtain((window_id, serial)) => { state.events_sink.push_window_event( crate::event::WindowEvent::ActivationTokenDone { serial: *serial, token: ActivationToken::_new(token), }, *window_id, ); } } proxy.destroy(); } } /// The data associated with the activation request. pub enum XdgActivationTokenData { /// Request user attention for the given surface. Attention((WlSurface, Weak)), /// Get a token to be passed outside of the winit. Obtain((WindowId, AsyncRequestSerial)), } delegate_dispatch!(WinitState: [ XdgActivationV1: GlobalData] => XdgActivationState); delegate_dispatch!(WinitState: [ XdgActivationTokenV1: XdgActivationTokenData] => XdgActivationState); winit-0.29.15/src/platform_impl/linux/wayland/window/mod.rs000064400000000000000000000602051046102023000220250ustar 00000000000000//! The Wayland window. use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use sctk::reexports::client::protocol::wl_display::WlDisplay; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::Proxy; use sctk::reexports::client::QueueHandle; use sctk::compositor::{CompositorState, Region, SurfaceData}; use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::shell::xdg::window::Window as SctkWindow; use sctk::shell::xdg::window::WindowDecorations; use sctk::shell::WaylandSurface; use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::event::{Ime, WindowEvent}; use crate::event_loop::AsyncRequestSerial; use crate::platform_impl::{ Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, PlatformSpecificWindowBuilderAttributes as PlatformAttributes, }; use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; use super::{EventLoopWindowTarget, WaylandError, WindowId}; pub(crate) mod state; pub use state::WindowState; /// The Wayland window. pub struct Window { /// Reference to the underlying SCTK window. window: SctkWindow, /// Window id. window_id: WindowId, /// The state of the window. window_state: Arc>, /// Compositor to handle WlRegion stuff. compositor: Arc, /// The wayland display used solely for raw window handle. #[allow(dead_code)] display: WlDisplay, /// Xdg activation to request user attention. xdg_activation: Option, /// The state of the requested attention from the `xdg_activation`. attention_requested: Arc, /// Handle to the main queue to perform requests. queue_handle: QueueHandle, /// Window requests to the event loop. window_requests: Arc, /// Observed monitors. monitors: Arc>>, /// Source to wake-up the event-loop for window requests. event_loop_awakener: calloop::ping::Ping, /// The event sink to deliver sythetic events. window_events_sink: Arc>, } impl Window { pub(crate) fn new( event_loop_window_target: &EventLoopWindowTarget, attributes: WindowAttributes, platform_attributes: PlatformAttributes, ) -> Result { let queue_handle = event_loop_window_target.queue_handle.clone(); let mut state = event_loop_window_target.state.borrow_mut(); let monitors = state.monitors.clone(); let surface = state.compositor_state.create_surface(&queue_handle); let compositor = state.compositor_state.clone(); let xdg_activation = state .xdg_activation .as_ref() .map(|activation_state| activation_state.global().clone()); let display = event_loop_window_target.connection.display(); let size: Size = attributes .inner_size .unwrap_or(LogicalSize::new(800., 600.).into()); // We prefer server side decorations, however to not have decorations we ask for client // side decorations instead. let default_decorations = if attributes.decorations { WindowDecorations::RequestServer } else { WindowDecorations::RequestClient }; let window = state .xdg_shell .create_window(surface.clone(), default_decorations, &queue_handle); let mut window_state = WindowState::new( event_loop_window_target.connection.clone(), &event_loop_window_target.queue_handle, &state, size, window.clone(), attributes.preferred_theme, ); // Set transparency hint. window_state.set_transparent(attributes.transparent); window_state.set_blur(attributes.blur); // Set the decorations hint. window_state.set_decorate(attributes.decorations); // Set the app_id. if let Some(name) = platform_attributes.name.map(|name| name.general) { window.set_app_id(name); } // Set the window title. window_state.set_title(attributes.title); // Set the min and max sizes. We must set the hints upon creating a window, so // we use the default `1.` scaling... let min_size = attributes.min_inner_size.map(|size| size.to_logical(1.)); let max_size = attributes.max_inner_size.map(|size| size.to_logical(1.)); window_state.set_min_inner_size(min_size); window_state.set_max_inner_size(max_size); // Non-resizable implies that the min and max sizes are set to the same value. window_state.set_resizable(attributes.resizable); // Set startup mode. match attributes.fullscreen.0.map(Into::into) { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); } Some(Fullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); window.set_fullscreen(output.as_ref()) } _ if attributes.maximized => window.set_maximized(), _ => (), }; // Activate the window when the token is passed. if let (Some(xdg_activation), Some(token)) = ( xdg_activation.as_ref(), platform_attributes.activation_token, ) { xdg_activation.activate(token._token, &surface); } // XXX Do initial commit. window.commit(); // Add the window and window requests into the state. let window_state = Arc::new(Mutex::new(window_state)); let window_id = super::make_wid(&surface); state .windows .get_mut() .insert(window_id, window_state.clone()); let window_requests = WindowRequests { redraw_requested: AtomicBool::new(true), closed: AtomicBool::new(false), }; let window_requests = Arc::new(window_requests); state .window_requests .get_mut() .insert(window_id, window_requests.clone()); // Setup the event sync to insert `WindowEvents` right from the window. let window_events_sink = state.window_events_sink.clone(); let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); let event_queue = wayland_source.queue(); // Do a roundtrip. event_queue.roundtrip(&mut state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch( error )))) })?; // XXX Wait for the initial configure to arrive. while !window_state.lock().unwrap().is_configured() { event_queue.blocking_dispatch(&mut state).map_err(|error| { os_error!(OsError::WaylandError(Arc::new(WaylandError::Dispatch( error )))) })?; } // Wake-up event loop, so it'll send initial redraw requested. let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); event_loop_awakener.ping(); Ok(Self { window, display, monitors, window_id, compositor, window_state, queue_handle, xdg_activation, attention_requested: Arc::new(AtomicBool::new(false)), event_loop_awakener, window_requests, window_events_sink, }) } } impl Window { #[inline] pub fn id(&self) -> WindowId { self.window_id } #[inline] pub fn set_title(&self, title: impl ToString) { let new_title = title.to_string(); self.window_state.lock().unwrap().set_title(new_title); } #[inline] pub fn set_visible(&self, _visible: bool) { // Not possible on Wayland. } #[inline] pub fn is_visible(&self) -> Option { None } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } #[inline] pub fn set_outer_position(&self, _: Position) { // Not possible on Wayland. } #[inline] pub fn inner_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.inner_size(), scale_factor) } #[inline] pub fn request_redraw(&self) { // NOTE: try to not wake up the loop when the event was already scheduled and not yet // processed by the loop, because if at this point the value was `true` it could only // mean that the loop still haven't dispatched the value to the client and will do // eventually, resetting it to `false`. if self .window_requests .redraw_requested .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { self.event_loop_awakener.ping(); } } #[inline] pub fn pre_present_notify(&self) { self.window_state.lock().unwrap().request_frame_callback(); } #[inline] pub fn outer_size(&self) -> PhysicalSize { let window_state = self.window_state.lock().unwrap(); let scale_factor = window_state.scale_factor(); super::logical_to_physical_rounded(window_state.outer_size(), scale_factor) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let mut window_state = self.window_state.lock().unwrap(); let new_size = window_state.request_inner_size(size); self.request_redraw(); Some(new_size) } /// Set the minimum inner size for the window. #[inline] pub fn set_min_inner_size(&self, min_size: Option) { let scale_factor = self.scale_factor(); let min_size = min_size.map(|size| size.to_logical(scale_factor)); self.window_state .lock() .unwrap() .set_min_inner_size(min_size); // NOTE: Requires commit to be applied. self.request_redraw(); } /// Set the maximum inner size for the window. #[inline] pub fn set_max_inner_size(&self, max_size: Option) { let scale_factor = self.scale_factor(); let max_size = max_size.map(|size| size.to_logical(scale_factor)); self.window_state .lock() .unwrap() .set_max_inner_size(max_size); // NOTE: Requires commit to be applied. self.request_redraw(); } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { warn!("`set_resize_increments` is not implemented for Wayland"); } #[inline] pub fn set_transparent(&self, transparent: bool) { self.window_state .lock() .unwrap() .set_transparent(transparent); } #[inline] pub fn has_focus(&self) -> bool { self.window_state.lock().unwrap().has_focus() } #[inline] pub fn is_minimized(&self) -> Option { // XXX clients don't know whether they are minimized or not. None } #[inline] pub fn show_window_menu(&self, position: Position) { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state.lock().unwrap().show_window_menu(position); } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.window_state .lock() .unwrap() .drag_resize_window(direction) } #[inline] pub fn set_resizable(&self, resizable: bool) { if self.window_state.lock().unwrap().set_resizable(resizable) { // NOTE: Requires commit to be applied. self.request_redraw(); } } #[inline] pub fn is_resizable(&self) -> bool { self.window_state.lock().unwrap().resizable() } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) { // TODO(kchibisov) v5 of the xdg_shell allows that. } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { // TODO(kchibisov) v5 of the xdg_shell allows that. WindowButtons::all() } #[inline] pub fn scale_factor(&self) -> f64 { self.window_state.lock().unwrap().scale_factor() } #[inline] pub fn set_blur(&self, blur: bool) { self.window_state.lock().unwrap().set_blur(blur); } #[inline] pub fn set_decorations(&self, decorate: bool) { self.window_state.lock().unwrap().set_decorate(decorate) } #[inline] pub fn is_decorated(&self) -> bool { self.window_state.lock().unwrap().is_decorated() } #[inline] pub fn set_window_level(&self, _level: WindowLevel) {} #[inline] pub(crate) fn set_window_icon(&self, _window_icon: Option) {} #[inline] pub fn set_minimized(&self, minimized: bool) { // You can't unminimize the window on Wayland. if !minimized { warn!("Unminimizing is ignored on Wayland."); return; } self.window.set_minimized(); } #[inline] pub fn is_maximized(&self) -> bool { self.window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_maximized()) .unwrap_or_default() } #[inline] pub fn set_maximized(&self, maximized: bool) { if maximized { self.window.set_maximized() } else { self.window.unset_maximized() } } #[inline] pub(crate) fn fullscreen(&self) -> Option { let is_fullscreen = self .window_state .lock() .unwrap() .last_configure .as_ref() .map(|last_configure| last_configure.is_fullscreen()) .unwrap_or_default(); if is_fullscreen { let current_monitor = self.current_monitor().map(PlatformMonitorHandle::Wayland); Some(Fullscreen::Borderless(current_monitor)) } else { None } } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { match fullscreen { Some(Fullscreen::Exclusive(_)) => { warn!("`Fullscreen::Exclusive` is ignored on Wayland"); } Some(Fullscreen::Borderless(monitor)) => { let output = monitor.and_then(|monitor| match monitor { PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), #[cfg(x11_platform)] PlatformMonitorHandle::X(_) => None, }); self.window.set_fullscreen(output.as_ref()) } None => self.window.unset_fullscreen(), } } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window_state.lock().unwrap().set_cursor(cursor); } #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window_state .lock() .unwrap() .set_cursor_visible(visible); } pub fn request_user_attention(&self, request_type: Option) { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => { warn!("`request_user_attention` isn't supported"); return; } }; // Urgency is only removed by the compositor and there's no need to raise urgency when it // was already raised. if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { return; } self.attention_requested.store(true, Ordering::Relaxed); let surface = self.surface().clone(); let data = XdgActivationTokenData::Attention(( surface.clone(), Arc::downgrade(&self.attention_requested), )); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(&surface); xdg_activation_token.commit(); } pub fn request_activation_token(&self) -> Result { let xdg_activation = match self.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => return Err(NotSupportedError::new()), }; let serial = AsyncRequestSerial::get(); let data = XdgActivationTokenData::Obtain((self.window_id, serial)); let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); xdg_activation_token.set_surface(self.surface()); xdg_activation_token.commit(); Ok(serial) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { self.window_state.lock().unwrap().set_cursor_grab(mode) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let scale_factor = self.scale_factor(); let position = position.to_logical(scale_factor); self.window_state .lock() .unwrap() .set_cursor_position(position) // Request redraw on success, since the state is double buffered. .map(|_| self.request_redraw()) } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { self.window_state.lock().unwrap().drag_window() } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let surface = self.window.wl_surface(); if hittest { surface.set_input_region(None); Ok(()) } else { let region = Region::new(&*self.compositor).map_err(|_| { ExternalError::Os(os_error!(OsError::Misc("failed to set input region."))) })?; region.add(0, 0, 0, 0); surface.set_input_region(Some(region.wl_region())); Ok(()) } } #[inline] pub fn set_ime_cursor_area(&self, position: Position, size: Size) { let window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() { let scale_factor = window_state.scale_factor(); let position = position.to_logical(scale_factor); let size = size.to_logical(scale_factor); window_state.set_ime_cursor_area(position, size); } } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let mut window_state = self.window_state.lock().unwrap(); if window_state.ime_allowed() != allowed && window_state.set_ime_allowed(allowed) { let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); self.window_events_sink .lock() .unwrap() .push_window_event(event, self.window_id); self.event_loop_awakener.ping(); } } #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { self.window_state.lock().unwrap().set_ime_purpose(purpose); } #[inline] pub fn focus_window(&self) {} #[inline] pub fn surface(&self) -> &WlSurface { self.window.wl_surface() } #[inline] pub fn current_monitor(&self) -> Option { let data = self.window.wl_surface().data::()?; data.outputs().next().map(MonitorHandle::new) } #[inline] pub fn available_monitors(&self) -> Vec { self.monitors.lock().unwrap().clone() } #[inline] pub fn primary_monitor(&self) -> Option { // XXX there's no such concept on Wayland. None } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::WaylandHandle::empty(); window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; window_handle.display = self.display.id().as_ptr() as *mut _; rwh_04::RawWindowHandle::Wayland(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::WaylandWindowHandle::empty(); window_handle.surface = self.window.wl_surface().id().as_ptr() as *mut _; rwh_05::RawWindowHandle::Wayland(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::WaylandDisplayHandle::empty(); display_handle.display = self.display.id().as_ptr() as *mut _; rwh_05::RawDisplayHandle::Wayland(display_handle) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { Ok(rwh_06::WaylandWindowHandle::new({ let ptr = self.window.wl_surface().id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") }) .into()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::WaylandDisplayHandle::new({ let ptr = self.display.id().as_ptr(); std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null") }) .into()) } #[inline] pub fn set_theme(&self, theme: Option) { self.window_state.lock().unwrap().set_theme(theme) } #[inline] pub fn theme(&self) -> Option { self.window_state.lock().unwrap().theme() } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn title(&self) -> String { self.window_state.lock().unwrap().title().to_owned() } } impl Drop for Window { fn drop(&mut self) { self.window_requests.closed.store(true, Ordering::Relaxed); self.event_loop_awakener.ping(); } } /// The request from the window to the event loop. #[derive(Debug)] pub struct WindowRequests { /// The window was closed. pub closed: AtomicBool, /// Redraw Requested. pub redraw_requested: AtomicBool, } impl WindowRequests { pub fn take_closed(&self) -> bool { self.closed.swap(false, Ordering::Relaxed) } pub fn take_redraw_requested(&self) -> bool { self.redraw_requested.swap(false, Ordering::Relaxed) } } impl TryFrom<&str> for Theme { type Error = (); /// ``` /// use winit::window::Theme; /// /// assert_eq!("dark".try_into(), Ok(Theme::Dark)); /// assert_eq!("lIghT".try_into(), Ok(Theme::Light)); /// ``` fn try_from(theme: &str) -> Result { if theme.eq_ignore_ascii_case("dark") { Ok(Self::Dark) } else if theme.eq_ignore_ascii_case("light") { Ok(Self::Light) } else { Err(()) } } } winit-0.29.15/src/platform_impl/linux/wayland/window/state.rs000064400000000000000000001117111046102023000223650ustar 00000000000000//! The state of the window, which is shared with the event-loop. use std::num::NonZeroU32; use std::sync::{Arc, Weak}; use std::time::Duration; use ahash::HashSet; use log::{info, warn}; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_shm::WlShm; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::reexports::csd_frame::{ DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowState as XdgWindowState, }; use sctk::reexports::protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1::WpFractionalScaleV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; use sctk::compositor::{CompositorState, Region}; use sctk::seat::pointer::ThemedPointer; use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; use sctk::shell::xdg::XdgSurface; use sctk::shell::WaylandSurface; use sctk::shm::Shm; use sctk::subcompositor::SubcompositorState; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size}; use crate::error::{ExternalError, NotSupportedError}; use crate::platform_impl::wayland::logical_to_physical_rounded; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::WindowId; use crate::window::{CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme}; use crate::platform_impl::wayland::seat::{ PointerConstraintsState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, }; use crate::platform_impl::wayland::state::{WindowCompositorUpdate, WinitState}; #[cfg(feature = "sctk-adwaita")] pub type WinitFrame = sctk_adwaita::AdwaitaFrame; #[cfg(not(feature = "sctk-adwaita"))] pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame; // Minimum window inner size. const MIN_WINDOW_SIZE: LogicalSize = LogicalSize::new(2, 1); /// The state of the window which is being updated from the [`WinitState`]. pub struct WindowState { /// The connection to Wayland server. pub connection: Connection, /// The window frame, which is created from the configure request. frame: Option, /// The `Shm` to set cursor. pub shm: WlShm, /// The last received configure. pub last_configure: Option, /// The pointers observed on the window. pub pointers: Vec>>, /// Cursor icon. pub cursor_icon: CursorIcon, /// Wether the cursor is visible. pub cursor_visible: bool, /// Pointer constraints to lock/confine pointer. pub pointer_constraints: Option>, /// Queue handle. pub queue_handle: QueueHandle, /// Theme varaint. theme: Option, /// The current window title. title: String, /// Whether the frame is resizable. resizable: bool, // NOTE: we can't use simple counter, since it's racy when seat getting destroyed and new // is created, since add/removed stuff could be delivered a bit out of order. /// Seats that has keyboard focus on that window. seat_focus: HashSet, /// The scale factor of the window. scale_factor: f64, /// Whether the window is transparent. transparent: bool, /// The state of the compositor to create WlRegions. compositor: Arc, /// The current cursor grabbing mode. cursor_grab_mode: GrabState, /// Whether the IME input is allowed for that window. ime_allowed: bool, /// The current IME purpose. ime_purpose: ImePurpose, /// The text inputs observed on the window. text_inputs: Vec, /// The inner size of the window, as in without client side decorations. size: LogicalSize, /// Whether the CSD fail to create, so we don't try to create them on each iteration. csd_fails: bool, /// Whether we should decorate the frame. decorate: bool, /// Min size. min_inner_size: LogicalSize, max_inner_size: Option>, /// The size of the window when no states were applied to it. The primary use for it /// is to fallback to original window size, before it was maximized, if the compositor /// sends `None` for the new size in the configure. stateless_size: LogicalSize, /// Initial window size provided by the user. Removed on the first /// configure. initial_size: Option, /// The state of the frame callback. frame_callback_state: FrameCallbackState, viewport: Option, fractional_scale: Option, blur: Option, blur_manager: Option, /// Whether the client side decorations have pending move operations. /// /// The value is the serial of the event triggered moved. has_pending_move: Option, /// The underlying SCTK window. pub window: Window, } impl WindowState { /// Create new window state. pub fn new( connection: Connection, queue_handle: &QueueHandle, winit_state: &WinitState, initial_size: Size, window: Window, theme: Option, ) -> Self { let compositor = winit_state.compositor_state.clone(); let pointer_constraints = winit_state.pointer_constraints.clone(); let viewport = winit_state .viewporter_state .as_ref() .map(|state| state.get_viewport(window.wl_surface(), queue_handle)); let fractional_scale = winit_state .fractional_scaling_manager .as_ref() .map(|fsm| fsm.fractional_scaling(window.wl_surface(), queue_handle)); Self { blur: None, blur_manager: winit_state.kwin_blur_manager.clone(), compositor, connection, csd_fails: false, cursor_grab_mode: GrabState::new(), cursor_icon: CursorIcon::Default, cursor_visible: true, decorate: true, fractional_scale, frame: None, frame_callback_state: FrameCallbackState::None, seat_focus: Default::default(), has_pending_move: None, ime_allowed: false, ime_purpose: ImePurpose::Normal, last_configure: None, max_inner_size: None, min_inner_size: MIN_WINDOW_SIZE, pointer_constraints, pointers: Default::default(), queue_handle: queue_handle.clone(), resizable: true, scale_factor: 1., shm: winit_state.shm.wl_shm().clone(), size: initial_size.to_logical(1.), stateless_size: initial_size.to_logical(1.), initial_size: Some(initial_size), text_inputs: Vec::new(), theme, title: String::default(), transparent: false, viewport, window, } } /// Apply closure on the given pointer. fn apply_on_poiner, &WinitPointerData)>( &self, callback: F, ) { self.pointers .iter() .filter_map(Weak::upgrade) .for_each(|pointer| { let data = pointer.pointer().winit_data(); callback(pointer.as_ref(), data); }) } /// Get the current state of the frame callback. pub fn frame_callback_state(&self) -> FrameCallbackState { self.frame_callback_state } /// The frame callback was received, but not yet sent to the user. pub fn frame_callback_received(&mut self) { self.frame_callback_state = FrameCallbackState::Received; } /// Reset the frame callbacks state. pub fn frame_callback_reset(&mut self) { self.frame_callback_state = FrameCallbackState::None; } /// Request a frame callback if we don't have one for this window in flight. pub fn request_frame_callback(&mut self) { let surface = self.window.wl_surface(); match self.frame_callback_state { FrameCallbackState::None | FrameCallbackState::Received => { self.frame_callback_state = FrameCallbackState::Requested; surface.frame(&self.queue_handle, surface.clone()); } FrameCallbackState::Requested => (), } } pub fn configure( &mut self, configure: WindowConfigure, shm: &Shm, subcompositor: &Option>, ) -> bool { // NOTE: when using fractional scaling or wl_compositor@v6 the scaling // should be delivered before the first configure, thus apply it to // properly scale the physical sizes provided by the users. if let Some(initial_size) = self.initial_size.take() { self.size = initial_size.to_logical(self.scale_factor()); self.stateless_size = self.size; } if let Some(subcompositor) = subcompositor.as_ref().filter(|_| { configure.decoration_mode == DecorationMode::Client && self.frame.is_none() && !self.csd_fails }) { match WinitFrame::new( &self.window, shm, #[cfg(feature = "sctk-adwaita")] self.compositor.clone(), subcompositor.clone(), self.queue_handle.clone(), #[cfg(feature = "sctk-adwaita")] into_sctk_adwaita_config(self.theme), ) { Ok(mut frame) => { frame.set_title(&self.title); frame.set_scaling_factor(self.scale_factor); // Hide the frame if we were asked to not decorate. frame.set_hidden(!self.decorate); self.frame = Some(frame); } Err(err) => { warn!("Failed to create client side decorations frame: {err}"); self.csd_fails = true; } } } else if configure.decoration_mode == DecorationMode::Server { // Drop the frame for server side decorations to save resources. self.frame = None; } let stateless = Self::is_stateless(&configure); let (mut new_size, constrain) = if let Some(frame) = self.frame.as_mut() { // Configure the window states. frame.update_state(configure.state); match configure.new_size { (Some(width), Some(height)) => { let (width, height) = frame.subtract_borders(width, height); let width = width.map(|w| w.get()).unwrap_or(1); let height = height.map(|h| h.get()).unwrap_or(1); ((width, height).into(), false) } (_, _) if stateless => (self.stateless_size, true), _ => (self.size, true), } } else { match configure.new_size { (Some(width), Some(height)) => ((width.get(), height.get()).into(), false), _ if stateless => (self.stateless_size, true), _ => (self.size, true), } }; // Apply configure bounds only when compositor let the user decide what size to pick. if constrain { let bounds = self.inner_size_bounds(&configure); new_size.width = bounds .0 .map(|bound_w| new_size.width.min(bound_w.get())) .unwrap_or(new_size.width); new_size.height = bounds .1 .map(|bound_h| new_size.height.min(bound_h.get())) .unwrap_or(new_size.height); } let new_state = configure.state; let old_state = self .last_configure .as_ref() .map(|configure| configure.state); let state_change_requires_resize = old_state .map(|old_state| { !old_state .symmetric_difference(new_state) .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) .is_empty() }) // NOTE: `None` is present for the initial configure, thus we must always resize. .unwrap_or(true); // NOTE: Set the configure before doing a resize, since we query it during it. self.last_configure = Some(configure); if state_change_requires_resize || new_size != self.inner_size() { self.resize(new_size); true } else { false } } /// Compute the bounds for the inner size of the surface. fn inner_size_bounds( &self, configure: &WindowConfigure, ) -> (Option, Option) { let configure_bounds = match configure.suggested_bounds { Some((width, height)) => (NonZeroU32::new(width), NonZeroU32::new(height)), None => (None, None), }; if let Some(frame) = self.frame.as_ref() { let (width, height) = frame.subtract_borders( configure_bounds.0.unwrap_or(NonZeroU32::new(1).unwrap()), configure_bounds.1.unwrap_or(NonZeroU32::new(1).unwrap()), ); ( configure_bounds.0.and(width), configure_bounds.1.and(height), ) } else { configure_bounds } } #[inline] fn is_stateless(configure: &WindowConfigure) -> bool { !(configure.is_maximized() || configure.is_fullscreen() || configure.is_tiled()) } /// Start interacting drag resize. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { let xdg_toplevel = self.window.xdg_toplevel(); // TODO(kchibisov) handle touch serials. self.apply_on_poiner(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); xdg_toplevel.resize(seat, serial, direction.into()); }); Ok(()) } /// Start the window drag. pub fn drag_window(&self) -> Result<(), ExternalError> { let xdg_toplevel = self.window.xdg_toplevel(); // TODO(kchibisov) handle touch serials. self.apply_on_poiner(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); xdg_toplevel._move(seat, serial); }); Ok(()) } /// Tells whether the window should be closed. #[allow(clippy::too_many_arguments)] pub fn frame_click( &mut self, click: FrameClick, pressed: bool, seat: &WlSeat, serial: u32, timestamp: Duration, window_id: WindowId, updates: &mut Vec, ) -> Option { match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { FrameAction::Minimize => self.window.set_minimized(), FrameAction::Maximize => self.window.set_maximized(), FrameAction::UnMaximize => self.window.unset_maximized(), FrameAction::Close => WinitState::queue_close(updates, window_id), FrameAction::Move => self.has_pending_move = Some(serial), FrameAction::Resize(edge) => { let edge = match edge { ResizeEdge::None => XdgResizeEdge::None, ResizeEdge::Top => XdgResizeEdge::Top, ResizeEdge::Bottom => XdgResizeEdge::Bottom, ResizeEdge::Left => XdgResizeEdge::Left, ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, ResizeEdge::Right => XdgResizeEdge::Right, ResizeEdge::TopRight => XdgResizeEdge::TopRight, ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, _ => return None, }; self.window.resize(seat, serial, edge); } FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), _ => (), }; Some(false) } pub fn frame_point_left(&mut self) { if let Some(frame) = self.frame.as_mut() { frame.click_point_left(); } } // Move the point over decorations. pub fn frame_point_moved( &mut self, seat: &WlSeat, surface: &WlSurface, timestamp: Duration, x: f64, y: f64, ) -> Option { // Take the serial if we had any, so it doesn't stick around. let serial = self.has_pending_move.take(); if let Some(frame) = self.frame.as_mut() { let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); // If we have a cursor change, that means that cursor is over the decorations, // so try to apply move. if let Some(serial) = cursor.is_some().then_some(serial).flatten() { self.window.move_(seat, serial); None } else { cursor } } else { None } } /// Get the stored resizable state. #[inline] pub fn resizable(&self) -> bool { self.resizable } /// Set the resizable state on the window. /// /// Returns `true` when the state was applied. #[inline] pub fn set_resizable(&mut self, resizable: bool) -> bool { if self.resizable == resizable { return false; } self.resizable = resizable; if resizable { // Restore min/max sizes of the window. self.reload_min_max_hints(); } else { self.set_min_inner_size(Some(self.size)); self.set_max_inner_size(Some(self.size)); } // Reload the state on the frame as well. if let Some(frame) = self.frame.as_mut() { frame.set_resizable(resizable); } true } /// Whether the window is focused by any seat. #[inline] pub fn has_focus(&self) -> bool { !self.seat_focus.is_empty() } /// Whether the IME is allowed. #[inline] pub fn ime_allowed(&self) -> bool { self.ime_allowed } /// Get the size of the window. #[inline] pub fn inner_size(&self) -> LogicalSize { self.size } /// Whether the window received initial configure event from the compositor. #[inline] pub fn is_configured(&self) -> bool { self.last_configure.is_some() } #[inline] pub fn is_decorated(&mut self) -> bool { let csd = self .last_configure .as_ref() .map(|configure| configure.decoration_mode == DecorationMode::Client) .unwrap_or(false); if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { !frame.is_hidden() } else { // Server side decorations. true } } /// Get the outer size of the window. #[inline] pub fn outer_size(&self) -> LogicalSize { self.frame .as_ref() .map(|frame| frame.add_borders(self.size.width, self.size.height).into()) .unwrap_or(self.size) } /// Register pointer on the top-level. pub fn pointer_entered(&mut self, added: Weak>) { self.pointers.push(added); self.reload_cursor_style(); let mode = self.cursor_grab_mode.user_grab_mode; let _ = self.set_cursor_grab_inner(mode); } /// Pointer has left the top-level. pub fn pointer_left(&mut self, removed: Weak>) { let mut new_pointers = Vec::new(); for pointer in self.pointers.drain(..) { if let Some(pointer) = pointer.upgrade() { if pointer.pointer() != removed.upgrade().unwrap().pointer() { new_pointers.push(Arc::downgrade(&pointer)); } } } self.pointers = new_pointers; } /// Refresh the decorations frame if it's present returning whether the client should redraw. pub fn refresh_frame(&mut self) -> bool { if let Some(frame) = self.frame.as_mut() { if !frame.is_hidden() && frame.is_dirty() { return frame.draw(); } } false } /// Reload the cursor style on the given window. pub fn reload_cursor_style(&mut self) { if self.cursor_visible { self.set_cursor(self.cursor_icon); } else { self.set_cursor_visible(self.cursor_visible); } } /// Reissue the transparency hint to the compositor. pub fn reload_transparency_hint(&self) { let surface = self.window.wl_surface(); if self.transparent { surface.set_opaque_region(None); } else if let Ok(region) = Region::new(&*self.compositor) { region.add(0, 0, i32::MAX, i32::MAX); surface.set_opaque_region(Some(region.wl_region())); } else { warn!("Failed to mark window opaque."); } } /// Try to resize the window when the user can do so. pub fn request_inner_size(&mut self, inner_size: Size) -> PhysicalSize { if self .last_configure .as_ref() .map(Self::is_stateless) .unwrap_or(true) { self.resize(inner_size.to_logical(self.scale_factor())) } logical_to_physical_rounded(self.inner_size(), self.scale_factor()) } /// Resize the window to the new inner size. fn resize(&mut self, inner_size: LogicalSize) { self.size = inner_size; // Update the stateless size. if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { self.stateless_size = inner_size; } // Update the inner frame. let ((x, y), outer_size) = if let Some(frame) = self.frame.as_mut() { // Resize only visible frame. if !frame.is_hidden() { frame.resize( NonZeroU32::new(self.size.width).unwrap(), NonZeroU32::new(self.size.height).unwrap(), ); } ( frame.location(), frame.add_borders(self.size.width, self.size.height).into(), ) } else { ((0, 0), self.size) }; // Reload the hint. self.reload_transparency_hint(); // Set the window geometry. self.window.xdg_surface().set_window_geometry( x, y, outer_size.width as i32, outer_size.height as i32, ); // Update the target viewport, this is used if and only if fractional scaling is in use. if let Some(viewport) = self.viewport.as_ref() { // Set inner size without the borders. viewport.set_destination(self.size.width as _, self.size.height as _); } } /// Get the scale factor of the window. #[inline] pub fn scale_factor(&self) -> f64 { self.scale_factor } /// Set the cursor icon. /// /// Providing `None` will hide the cursor. pub fn set_cursor(&mut self, cursor_icon: CursorIcon) { self.cursor_icon = cursor_icon; if !self.cursor_visible { return; } self.apply_on_poiner(|pointer, _| { if pointer.set_cursor(&self.connection, cursor_icon).is_err() { warn!("Failed to set cursor to {:?}", cursor_icon); } }) } /// Set maximum inner window size. pub fn set_min_inner_size(&mut self, size: Option>) { // Ensure that the window has the right minimum size. let mut size = size.unwrap_or(MIN_WINDOW_SIZE); size.width = size.width.max(MIN_WINDOW_SIZE.width); size.height = size.height.max(MIN_WINDOW_SIZE.height); // Add the borders. let size = self .frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) .unwrap_or(size); self.min_inner_size = size; self.window.set_min_size(Some(size.into())); } /// Set maximum inner window size. pub fn set_max_inner_size(&mut self, size: Option>) { let size = size.map(|size| { self.frame .as_ref() .map(|frame| frame.add_borders(size.width, size.height).into()) .unwrap_or(size) }); self.max_inner_size = size; self.window.set_max_size(size.map(Into::into)); } /// Set the CSD theme. pub fn set_theme(&mut self, theme: Option) { self.theme = theme; #[cfg(feature = "sctk-adwaita")] if let Some(frame) = self.frame.as_mut() { frame.set_config(into_sctk_adwaita_config(theme)) } } /// The current theme for CSD decorations. #[inline] pub fn theme(&self) -> Option { self.theme } /// Set the cursor grabbing state on the top-level. pub fn set_cursor_grab(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> { if self.cursor_grab_mode.user_grab_mode == mode { return Ok(()); } self.set_cursor_grab_inner(mode)?; // Update user grab on success. self.cursor_grab_mode.user_grab_mode = mode; Ok(()) } /// Reload the hints for minimum and maximum sizes. pub fn reload_min_max_hints(&mut self) { self.set_min_inner_size(Some(self.min_inner_size)); self.set_max_inner_size(self.max_inner_size); } /// Set the grabbing state on the surface. fn set_cursor_grab_inner(&mut self, mode: CursorGrabMode) -> Result<(), ExternalError> { let pointer_constraints = match self.pointer_constraints.as_ref() { Some(pointer_constraints) => pointer_constraints, None if mode == CursorGrabMode::None => return Ok(()), None => return Err(ExternalError::NotSupported(NotSupportedError::new())), }; // Replace the current mode. let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode); match old_mode { CursorGrabMode::None => (), CursorGrabMode::Confined => self.apply_on_poiner(|_, data| { data.unconfine_pointer(); }), CursorGrabMode::Locked => { self.apply_on_poiner(|_, data| data.unlock_pointer()); } } let surface = self.window.wl_surface(); match mode { CursorGrabMode::Locked => self.apply_on_poiner(|pointer, data| { let pointer = pointer.pointer(); data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle) }), CursorGrabMode::Confined => self.apply_on_poiner(|pointer, data| { let pointer = pointer.pointer(); data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle) }), CursorGrabMode::None => { // Current lock/confine was already removed. } } Ok(()) } pub fn show_window_menu(&self, position: LogicalPosition) { // TODO(kchibisov) handle touch serials. self.apply_on_poiner(|_, data| { let serial = data.latest_button_serial(); let seat = data.seat(); self.window.show_window_menu(seat, serial, position.into()); }); } /// Set the position of the cursor. pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { if self.pointer_constraints.is_none() { return Err(ExternalError::NotSupported(NotSupportedError::new())); } // Positon can be set only for locked cursor. if self.cursor_grab_mode.current_grab_mode != CursorGrabMode::Locked { return Err(ExternalError::Os(os_error!( crate::platform_impl::OsError::Misc( "cursor position can be set only for locked cursor." ) ))); } self.apply_on_poiner(|_, data| { data.set_locked_cursor_position(position.x, position.y); }); Ok(()) } /// Set the visibility state of the cursor. pub fn set_cursor_visible(&mut self, cursor_visible: bool) { self.cursor_visible = cursor_visible; if self.cursor_visible { self.set_cursor(self.cursor_icon); } else { for pointer in self.pointers.iter().filter_map(|pointer| pointer.upgrade()) { let latest_enter_serial = pointer.pointer().winit_data().latest_enter_serial(); pointer .pointer() .set_cursor(latest_enter_serial, None, 0, 0); } } } /// Whether show or hide client side decorations. #[inline] pub fn set_decorate(&mut self, decorate: bool) { if decorate == self.decorate { return; } self.decorate = decorate; match self .last_configure .as_ref() .map(|configure| configure.decoration_mode) { Some(DecorationMode::Server) if !self.decorate => { // To disable decorations we should request client and hide the frame. self.window .request_decoration_mode(Some(DecorationMode::Client)) } _ if self.decorate => self .window .request_decoration_mode(Some(DecorationMode::Server)), _ => (), } if let Some(frame) = self.frame.as_mut() { frame.set_hidden(!decorate); // Force the resize. self.resize(self.size); } } /// Add seat focus for the window. #[inline] pub fn add_seat_focus(&mut self, seat: ObjectId) { self.seat_focus.insert(seat); } /// Remove seat focus from the window. #[inline] pub fn remove_seat_focus(&mut self, seat: &ObjectId) { self.seat_focus.remove(seat); } /// Returns `true` if the requested state was applied. pub fn set_ime_allowed(&mut self, allowed: bool) -> bool { self.ime_allowed = allowed; let mut applied = false; for text_input in &self.text_inputs { applied = true; if allowed { text_input.enable(); text_input.set_content_type_by_purpose(self.ime_purpose); } else { text_input.disable(); } text_input.commit(); } applied } /// Set the IME position. pub fn set_ime_cursor_area(&self, position: LogicalPosition, size: LogicalSize) { // FIXME: This won't fly unless user will have a way to request IME window per seat, since // the ime windows will be overlapping, but winit doesn't expose API to specify for // which seat we're setting IME position. let (x, y) = (position.x as i32, position.y as i32); let (width, height) = (size.width as i32, size.height as i32); for text_input in self.text_inputs.iter() { text_input.set_cursor_rectangle(x, y, width, height); text_input.commit(); } } /// Set the IME purpose. pub fn set_ime_purpose(&mut self, purpose: ImePurpose) { self.ime_purpose = purpose; for text_input in &self.text_inputs { text_input.set_content_type_by_purpose(purpose); text_input.commit(); } } /// Get the IME purpose. pub fn ime_purpose(&self) -> ImePurpose { self.ime_purpose } /// Set the scale factor for the given window. #[inline] pub fn set_scale_factor(&mut self, scale_factor: f64) { self.scale_factor = scale_factor; // NOTE: When fractional scaling is not used update the buffer scale. if self.fractional_scale.is_none() { let _ = self.window.set_buffer_scale(self.scale_factor as _); } if let Some(frame) = self.frame.as_mut() { frame.set_scaling_factor(scale_factor); } } /// Make window background blurred #[inline] pub fn set_blur(&mut self, blurred: bool) { if blurred && self.blur.is_none() { if let Some(blur_manager) = self.blur_manager.as_ref() { let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); blur.commit(); self.blur = Some(blur); } else { info!("Blur manager unavailable, unable to change blur") } } else if !blurred && self.blur.is_some() { self.blur_manager .as_ref() .unwrap() .unset(self.window.wl_surface()); self.blur.take().unwrap().release(); } } /// Set the window title to a new value. /// /// This will autmatically truncate the title to something meaningfull. pub fn set_title(&mut self, mut title: String) { // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol // messages if title.len() > 1024 { let mut new_len = 1024; while !title.is_char_boundary(new_len) { new_len -= 1; } title.truncate(new_len); } // Update the CSD title. if let Some(frame) = self.frame.as_mut() { frame.set_title(&title); } self.window.set_title(&title); self.title = title; } /// Mark the window as transparent. #[inline] pub fn set_transparent(&mut self, transparent: bool) { self.transparent = transparent; self.reload_transparency_hint(); } /// Register text input on the top-level. #[inline] pub fn text_input_entered(&mut self, text_input: &ZwpTextInputV3) { if !self.text_inputs.iter().any(|t| t == text_input) { self.text_inputs.push(text_input.clone()); } } /// The text input left the top-level. #[inline] pub fn text_input_left(&mut self, text_input: &ZwpTextInputV3) { if let Some(position) = self.text_inputs.iter().position(|t| t == text_input) { self.text_inputs.remove(position); } } /// Get the cached title. #[inline] pub fn title(&self) -> &str { &self.title } } impl Drop for WindowState { fn drop(&mut self) { if let Some(blur) = self.blur.take() { blur.release(); } if let Some(fs) = self.fractional_scale.take() { fs.destroy(); } if let Some(viewport) = self.viewport.take() { viewport.destroy(); } // NOTE: the wl_surface used by the window is being cleaned up when // dropping SCTK `Window`. } } /// The state of the cursor grabs. #[derive(Clone, Copy)] struct GrabState { /// The grab mode requested by the user. user_grab_mode: CursorGrabMode, /// The current grab mode. current_grab_mode: CursorGrabMode, } impl GrabState { fn new() -> Self { Self { user_grab_mode: CursorGrabMode::None, current_grab_mode: CursorGrabMode::None, } } } /// The state of the frame callback. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum FrameCallbackState { /// No frame callback was requsted. #[default] None, /// The frame callback was requested, but not yet arrived, the redraw events are throttled. Requested, /// The callback was marked as done, and user could receive redraw requested Received, } impl From for XdgResizeEdge { fn from(value: ResizeDirection) -> Self { match value { ResizeDirection::North => XdgResizeEdge::Top, ResizeDirection::West => XdgResizeEdge::Left, ResizeDirection::NorthWest => XdgResizeEdge::TopLeft, ResizeDirection::NorthEast => XdgResizeEdge::TopRight, ResizeDirection::East => XdgResizeEdge::Right, ResizeDirection::SouthWest => XdgResizeEdge::BottomLeft, ResizeDirection::SouthEast => XdgResizeEdge::BottomRight, ResizeDirection::South => XdgResizeEdge::Bottom, } } } // NOTE: Rust doesn't allow `From>`. #[cfg(feature = "sctk-adwaita")] fn into_sctk_adwaita_config(theme: Option) -> sctk_adwaita::FrameConfig { match theme { Some(Theme::Light) => sctk_adwaita::FrameConfig::light(), Some(Theme::Dark) => sctk_adwaita::FrameConfig::dark(), None => sctk_adwaita::FrameConfig::auto(), } } winit-0.29.15/src/platform_impl/linux/x11/activation.rs000064400000000000000000000144061046102023000210540ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 //! X11 activation handling. //! //! X11 has a "startup notification" specification similar to Wayland's, see this URL: //! use super::{atoms::*, VoidCookie, X11Error, XConnection}; use std::ffi::CString; use std::fmt::Write; use x11rb::protocol::xproto::{self, ConnectionExt as _}; impl XConnection { /// "Request" a new activation token from the server. pub(crate) fn request_activation_token(&self, window_title: &str) -> Result { // The specification recommends the format "hostname+pid+"_TIME"+current time" let uname = rustix::system::uname(); let pid = rustix::process::getpid(); let time = self.timestamp(); let activation_token = format!( "{}{}_TIME{}", uname.nodename().to_str().unwrap_or("winit"), pid.as_raw_nonzero(), time ); // Set up the new startup notification. let notification = { let mut buffer = Vec::new(); buffer.extend_from_slice(b"new: ID="); quote_string(&activation_token, &mut buffer); buffer.extend_from_slice(b" NAME="); quote_string(window_title, &mut buffer); buffer.extend_from_slice(b" SCREEN="); push_display(&mut buffer, &self.default_screen_index()); CString::new(buffer) .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))? .into_bytes_with_nul() }; self.send_message(¬ification)?; Ok(activation_token) } /// Finish launching a window with the given startup ID. pub(crate) fn remove_activation_token( &self, window: xproto::Window, startup_id: &str, ) -> Result<(), X11Error> { let atoms = self.atoms(); // Set the _NET_STARTUP_ID property on the window. self.xcb_connection() .change_property( xproto::PropMode::REPLACE, window, atoms[_NET_STARTUP_ID], xproto::AtomEnum::STRING, 8, startup_id.len().try_into().unwrap(), startup_id.as_bytes(), )? .check()?; // Send the message indicating that the startup is over. let message = { const MESSAGE_ROOT: &str = "remove: ID="; let mut buffer = Vec::with_capacity( MESSAGE_ROOT .len() .checked_add(startup_id.len()) .and_then(|x| x.checked_add(1)) .unwrap(), ); buffer.extend_from_slice(MESSAGE_ROOT.as_bytes()); quote_string(startup_id, &mut buffer); CString::new(buffer) .map_err(|err| X11Error::InvalidActivationToken(err.into_vec()))? .into_bytes_with_nul() }; self.send_message(&message) } /// Send a startup notification message to the window manager. fn send_message(&self, message: &[u8]) -> Result<(), X11Error> { let atoms = self.atoms(); // Create a new window to send the message over. let screen = self.default_root(); let window = xproto::WindowWrapper::create_window( self.xcb_connection(), screen.root_depth, screen.root, -100, -100, 1, 1, 0, xproto::WindowClass::INPUT_OUTPUT, screen.root_visual, &xproto::CreateWindowAux::new() .override_redirect(1) .event_mask( xproto::EventMask::STRUCTURE_NOTIFY | xproto::EventMask::PROPERTY_CHANGE, ), )?; // Serialize the messages in 20-byte chunks. let mut message_type = atoms[_NET_STARTUP_INFO_BEGIN]; message .chunks(20) .map(|chunk| { let mut buffer = [0u8; 20]; buffer[..chunk.len()].copy_from_slice(chunk); let event = xproto::ClientMessageEvent::new(8, window.window(), message_type, buffer); // Set the message type to the continuation atom for the next chunk. message_type = atoms[_NET_STARTUP_INFO]; event }) .try_for_each(|event| { // Send each event in order. self.xcb_connection() .send_event( false, screen.root, xproto::EventMask::PROPERTY_CHANGE, event, ) .map(VoidCookie::ignore_error) })?; Ok(()) } } /// Quote a literal string as per the startup notification specification. fn quote_string(s: &str, target: &mut Vec) { let total_len = s.len().checked_add(3).expect("quote string overflow"); target.reserve(total_len); // Add the opening quote. target.push(b'"'); // Iterate over the string split by literal quotes. s.as_bytes().split(|&b| b == b'"').for_each(|part| { // Add the part. target.extend_from_slice(part); // Escape the quote. target.push(b'\\'); target.push(b'"'); }); // Un-escape the last quote. target.remove(target.len() - 2); } /// Push a `Display` implementation to the buffer. fn push_display(buffer: &mut Vec, display: &impl std::fmt::Display) { struct Writer<'a> { buffer: &'a mut Vec, } impl<'a> std::fmt::Write for Writer<'a> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.buffer.extend_from_slice(s.as_bytes()); Ok(()) } } write!(Writer { buffer }, "{}", display).unwrap(); } #[cfg(test)] mod tests { use super::*; #[test] fn properly_escapes_x11_messages() { let assert_eq = |input: &str, output: &[u8]| { let mut buf = vec![]; quote_string(input, &mut buf); assert_eq!(buf, output); }; assert_eq("", b"\"\""); assert_eq("foo", b"\"foo\""); assert_eq("foo\"bar", b"\"foo\\\"bar\""); } } winit-0.29.15/src/platform_impl/linux/x11/atoms.rs000064400000000000000000000052131046102023000200320ustar 00000000000000//! Collects every atom used by the platform implementation. use core::ops::Index; macro_rules! atom_manager { ($($name:ident $(:$lit:literal)?),*) => { x11rb::atom_manager! { /// The atoms used by `winit` pub Atoms: AtomsCookie { $($name $(:$lit)?,)* } } /// Indices into the `Atoms` struct. #[derive(Copy, Clone, Debug)] #[allow(non_camel_case_types)] pub enum AtomName { $($name,)* } impl AtomName { pub(crate) fn atom_from( self, atoms: &Atoms ) -> &x11rb::protocol::xproto::Atom { match self { $(AtomName::$name => &atoms.$name,)* } } } }; } atom_manager! { // General Use Atoms CARD32, UTF8_STRING, WM_CHANGE_STATE, WM_CLIENT_MACHINE, WM_DELETE_WINDOW, WM_PROTOCOLS, WM_STATE, XIM_SERVERS, // Assorted ICCCM Atoms _NET_WM_ICON, _NET_WM_MOVERESIZE, _NET_WM_NAME, _NET_WM_PID, _NET_WM_PING, _NET_WM_STATE, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_WINDOW_TYPE, // Activation atoms. _NET_STARTUP_INFO_BEGIN, _NET_STARTUP_INFO, _NET_STARTUP_ID, // WM window types. _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_POPUP_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP, _NET_WM_WINDOW_TYPE_NOTIFICATION, _NET_WM_WINDOW_TYPE_COMBO, _NET_WM_WINDOW_TYPE_DND, _NET_WM_WINDOW_TYPE_NORMAL, // Drag-N-Drop Atoms XdndAware, XdndEnter, XdndLeave, XdndDrop, XdndPosition, XdndStatus, XdndActionPrivate, XdndSelection, XdndFinished, XdndTypeList, TextUriList: b"text/uri-list", None: b"None", // Miscellaneous Atoms _GTK_THEME_VARIANT, _MOTIF_WM_HINTS, _NET_ACTIVE_WINDOW, _NET_CLIENT_LIST, _NET_FRAME_EXTENTS, _NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK, _XEMBED, _XSETTINGS_SETTINGS } impl Index for Atoms { type Output = x11rb::protocol::xproto::Atom; fn index(&self, index: AtomName) -> &Self::Output { index.atom_from(self) } } pub(crate) use AtomName::*; // Make sure `None` is still defined. pub(crate) use core::option::Option::None; winit-0.29.15/src/platform_impl/linux/x11/dnd.rs000064400000000000000000000125571046102023000174650ustar 00000000000000use std::{ io, os::raw::*, path::{Path, PathBuf}, str::Utf8Error, sync::Arc, }; use percent_encoding::percent_decode; use x11rb::protocol::xproto::{self, ConnectionExt}; use super::{ atoms::{AtomName::None as DndNone, *}, util, CookieResultExt, X11Error, XConnection, }; #[derive(Debug, Clone, Copy)] pub enum DndState { Accepted, Rejected, } #[derive(Debug)] pub enum DndDataParseError { EmptyData, InvalidUtf8(Utf8Error), HostnameSpecified(String), UnexpectedProtocol(String), UnresolvablePath(io::Error), } impl From for DndDataParseError { fn from(e: Utf8Error) -> Self { DndDataParseError::InvalidUtf8(e) } } impl From for DndDataParseError { fn from(e: io::Error) -> Self { DndDataParseError::UnresolvablePath(e) } } pub struct Dnd { xconn: Arc, // Populated by XdndEnter event handler pub version: Option, pub type_list: Option>, // Populated by XdndPosition event handler pub source_window: Option, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, } impl Dnd { pub fn new(xconn: Arc) -> Result { Ok(Dnd { xconn, version: None, type_list: None, source_window: None, result: None, }) } pub fn reset(&mut self) { self.version = None; self.type_list = None; self.source_window = None; self.result = None; } pub unsafe fn send_status( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg( target_window, target_window, atoms[XdndStatus] as _, None, [this_window, accepted, 0, 0, action as _], )? .ignore_error(); Ok(()) } pub unsafe fn send_finished( &self, this_window: xproto::Window, target_window: xproto::Window, state: DndState, ) -> Result<(), X11Error> { let atoms = self.xconn.atoms(); let (accepted, action) = match state { DndState::Accepted => (1, atoms[XdndActionPrivate]), DndState::Rejected => (0, atoms[DndNone]), }; self.xconn .send_client_msg( target_window, target_window, atoms[XdndFinished] as _, None, [this_window, accepted, action as _, 0, 0], )? .ignore_error(); Ok(()) } pub unsafe fn get_type_list( &self, source_window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn.get_property( source_window, atoms[XdndTypeList], xproto::Atom::from(xproto::AtomEnum::ATOM), ) } pub unsafe fn convert_selection(&self, window: xproto::Window, time: xproto::Timestamp) { let atoms = self.xconn.atoms(); self.xconn .xcb_connection() .convert_selection( window, atoms[XdndSelection], atoms[TextUriList], atoms[XdndSelection], time, ) .expect_then_ignore_error("Failed to send XdndSelection event") } pub unsafe fn read_data( &self, window: xproto::Window, ) -> Result, util::GetPropertyError> { let atoms = self.xconn.atoms(); self.xconn .get_property(window, atoms[XdndSelection], atoms[TextUriList]) } pub fn parse_data(&self, data: &mut [c_uchar]) -> Result, DndDataParseError> { if !data.is_empty() { let mut path_list = Vec::new(); let decoded = percent_decode(data).decode_utf8()?.into_owned(); for uri in decoded.split("\r\n").filter(|u| !u.is_empty()) { // The format is specified as protocol://host/path // However, it's typically simply protocol:///path let path_str = if uri.starts_with("file://") { let path_str = uri.replace("file://", ""); if !path_str.starts_with('/') { // A hostname is specified // Supporting this case is beyond the scope of my mental health return Err(DndDataParseError::HostnameSpecified(path_str)); } path_str } else { // Only the file protocol is supported return Err(DndDataParseError::UnexpectedProtocol(uri.to_owned())); }; let path = Path::new(&path_str).canonicalize()?; path_list.push(path); } Ok(path_list) } else { Err(DndDataParseError::EmptyData) } } } winit-0.29.15/src/platform_impl/linux/x11/event_processor.rs000064400000000000000000002212131046102023000221270ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::os::raw::{c_char, c_int, c_long, c_ulong}; use std::slice; use std::sync::{Arc, Mutex}; use x11_dl::xinput2::{ self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent, XILeaveEvent, XIModifierState, XIRawEvent, }; use x11_dl::xlib::{ self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent, XDestroyWindowEvent, XEvent, XExposeEvent, XKeyEvent, XMapEvent, XPropertyEvent, XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent, XkbStateRec, }; use x11rb::protocol::xinput; use x11rb::protocol::xkb::ID as XkbId; use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask}; use x11rb::x11_utils::ExtensionInformation; use x11rb::x11_utils::Serialize; use xkbcommon_dl::xkb_mod_mask_t; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::event::{ DeviceEvent, ElementState, Event, Ime, MouseScrollDelta, RawKeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event::{InnerSizeWriter, MouseButton}; use crate::event_loop::EventLoopWindowTarget as RootELW; use crate::keyboard::ModifiersState; use crate::platform_impl::common::xkb::{self, XkbState}; use crate::platform_impl::platform::common::xkb::Context; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest}; use crate::platform_impl::platform::x11::EventLoopWindowTarget; use crate::platform_impl::platform::EventLoopWindowTarget as PlatformEventLoopWindowTarget; use crate::platform_impl::x11::util::cookie::GenericEventCookie; use crate::platform_impl::x11::{ atoms::*, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, Dnd, DndState, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, }; /// The maximum amount of X modifiers to replay. pub const MAX_MOD_REPLAY_LEN: usize = 32; /// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`". const KEYCODE_OFFSET: u8 = 8; pub struct EventProcessor { pub dnd: Dnd, pub ime_receiver: ImeReceiver, pub ime_event_receiver: ImeEventReceiver, pub randr_event_offset: u8, pub devices: RefCell>, pub xi2ext: ExtensionInformation, pub xkbext: ExtensionInformation, pub target: RootELW, pub xkb_context: Context, // Number of touch events currently in progress pub num_touch: u32, // This is the last pressed key that is repeatable (if it hasn't been // released). // // Used to detect key repeats. pub held_key_press: Option, pub first_touch: Option, // Currently focused window belonging to this process pub active_window: Option, /// Latest modifiers we've sent for the user to trigger change in event. pub modifiers: Cell, pub xfiltered_modifiers: VecDeque, pub xmodmap: util::ModifierKeymap, pub is_composing: bool, } impl EventProcessor { pub fn process_event(&mut self, xev: &mut XEvent, mut callback: F) where F: FnMut(&RootELW, Event), { self.process_xevent(xev, &mut callback); let window_target = Self::window_target_mut(&mut self.target); // Handle IME requests. while let Ok(request) = self.ime_receiver.try_recv() { let ime = match window_target.ime.as_mut() { Some(ime) => ime, None => continue, }; let ime = ime.get_mut(); match request { ImeRequest::Position(window_id, x, y) => { ime.send_xim_spot(window_id, x, y); } ImeRequest::Allow(window_id, allowed) => { ime.set_ime_allowed(window_id, allowed); } } } // Drain IME events. while let Ok((window, event)) = self.ime_event_receiver.try_recv() { let window_id = mkwid(window as xproto::Window); let event = match event { ImeEvent::Enabled => WindowEvent::Ime(Ime::Enabled), ImeEvent::Start => { self.is_composing = true; WindowEvent::Ime(Ime::Preedit("".to_owned(), None)) } ImeEvent::Update(text, position) if self.is_composing => { WindowEvent::Ime(Ime::Preedit(text, Some((position, position)))) } ImeEvent::End => { self.is_composing = false; // Issue empty preedit on `Done`. WindowEvent::Ime(Ime::Preedit(String::new(), None)) } ImeEvent::Disabled => { self.is_composing = false; WindowEvent::Ime(Ime::Disabled) } _ => continue, }; callback(&self.target, Event::WindowEvent { window_id, 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. fn filter_event(&mut self, xev: &mut XEvent) -> bool { let wt = Self::window_target(&self.target); unsafe { (wt.xconn.xlib.XFilterEvent)(xev, { let xev: &XAnyEvent = xev.as_ref(); xev.window }) == xlib::True } } fn process_xevent(&mut self, xev: &mut XEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let event_type = xev.get_type(); if self.filter_event(xev) { if event_type == xlib::KeyPress || event_type == xlib::KeyRelease { let xev: &XKeyEvent = xev.as_ref(); if self.xmodmap.is_modifier(xev.keycode as u8) { // Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen // when the modifiers are consumed entirely or serials are altered. // // Both cases shouldn't happen in well behaving clients. if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN { self.xfiltered_modifiers.pop_back(); } self.xfiltered_modifiers.push_front(xev.serial); } } return; } match event_type { xlib::ClientMessage => self.client_message(xev.as_ref(), &mut callback), xlib::SelectionNotify => self.selection_notify(xev.as_ref(), &mut callback), xlib::ConfigureNotify => self.configure_notify(xev.as_ref(), &mut callback), xlib::ReparentNotify => self.reparent_notify(xev.as_ref()), xlib::MapNotify => self.map_notify(xev.as_ref(), &mut callback), xlib::DestroyNotify => self.destroy_notify(xev.as_ref(), &mut callback), xlib::PropertyNotify => self.property_notify(xev.as_ref(), &mut callback), xlib::VisibilityNotify => self.visibility_notify(xev.as_ref(), &mut callback), xlib::Expose => self.expose(xev.as_ref(), &mut callback), // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events. ty @ xlib::KeyPress | ty @ xlib::KeyRelease => { let state = if ty == xlib::KeyPress { ElementState::Pressed } else { ElementState::Released }; self.xinput_key_input(xev.as_mut(), state, &mut callback); } xlib::GenericEvent => { let wt = Self::window_target(&self.target); let xev: GenericEventCookie = match GenericEventCookie::from_event(wt.xconn.clone(), *xev) { Some(xev) if xev.extension() == self.xi2ext.major_opcode => xev, _ => return, }; let evtype = xev.evtype(); match evtype { ty @ xinput2::XI_ButtonPress | ty @ xinput2::XI_ButtonRelease => { let state = if ty == xinput2::XI_ButtonPress { ElementState::Pressed } else { ElementState::Released }; let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_button_input(xev, state, &mut callback); } xinput2::XI_Motion => { let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_mouse_motion(xev, &mut callback); } xinput2::XI_Enter => { let xev: &XIEnterEvent = unsafe { xev.as_event() }; self.xinput2_mouse_enter(xev, &mut callback); } xinput2::XI_Leave => { let xev: &XILeaveEvent = unsafe { xev.as_event() }; self.update_mods_from_xinput2_event( &xev.mods, &xev.group, false, &mut callback, ); self.xinput2_mouse_left(xev, &mut callback); } xinput2::XI_FocusIn => { let xev: &XIFocusInEvent = unsafe { xev.as_event() }; self.xinput2_focused(xev, &mut callback); } xinput2::XI_FocusOut => { let xev: &XIFocusOutEvent = unsafe { xev.as_event() }; self.xinput2_unfocused(xev, &mut callback); } xinput2::XI_TouchBegin | xinput2::XI_TouchUpdate | xinput2::XI_TouchEnd => { let phase = match evtype { xinput2::XI_TouchBegin => TouchPhase::Started, xinput2::XI_TouchUpdate => TouchPhase::Moved, xinput2::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!(), }; let xev: &XIDeviceEvent = unsafe { xev.as_event() }; self.xinput2_touch(xev, phase, &mut callback); } xinput2::XI_RawButtonPress | xinput2::XI_RawButtonRelease => { let state = match evtype { xinput2::XI_RawButtonPress => ElementState::Pressed, xinput2::XI_RawButtonRelease => ElementState::Released, _ => unreachable!(), }; let xev: &XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_button_input(xev, state, &mut callback); } xinput2::XI_RawMotion => { let xev: &XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_mouse_motion(xev, &mut callback); } xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => { let state = match evtype { xinput2::XI_RawKeyPress => ElementState::Pressed, xinput2::XI_RawKeyRelease => ElementState::Released, _ => unreachable!(), }; let xev: &xinput2::XIRawEvent = unsafe { xev.as_event() }; self.xinput2_raw_key_input(xev, state, &mut callback); } xinput2::XI_HierarchyChanged => { let xev: &XIHierarchyEvent = unsafe { xev.as_event() }; self.xinput2_hierarchy_changed(xev, &mut callback); } _ => {} } } _ => { if event_type == self.xkbext.first_event as _ { let xev: &XkbAnyEvent = unsafe { &*(xev as *const _ as *const XkbAnyEvent) }; self.xkb_event(xev, &mut callback); } if event_type == self.randr_event_offset as c_int { self.process_dpi_change(&mut callback); } } } } pub fn poll(&self) -> bool { let window_target = Self::window_target(&self.target); let result = unsafe { (window_target.xconn.xlib.XPending)(window_target.xconn.display) }; result != 0 } pub unsafe fn poll_one_event(&mut self, event_ptr: *mut XEvent) -> bool { let window_target = Self::window_target(&self.target); // This function is used to poll and remove a single event // from the Xlib event queue in a non-blocking, atomic way. // XCheckIfEvent is non-blocking and removes events from queue. // XNextEvent can't be used because it blocks while holding the // global Xlib mutex. // XPeekEvent does not remove events from the queue. unsafe extern "C" fn predicate( _display: *mut XDisplay, _event: *mut XEvent, _arg: *mut c_char, ) -> c_int { // This predicate always returns "true" (1) to accept all events 1 } let result = unsafe { (window_target.xconn.xlib.XCheckIfEvent)( window_target.xconn.display, event_ptr, Some(predicate), std::ptr::null_mut(), ) }; result != 0 } pub fn init_device(&self, device: xinput::DeviceId) { let window_target = Self::window_target(&self.target); let mut devices = self.devices.borrow_mut(); if let Some(info) = DeviceInfo::get(&window_target.xconn, device as _) { for info in info.iter() { devices.insert(DeviceId(info.deviceid as _), Device::new(info)); } } } pub fn with_window(&self, window_id: xproto::Window, callback: F) -> Option where F: Fn(&Arc) -> Ret, { let mut deleted = false; let window_id = WindowId(window_id as _); let window_target = Self::window_target(&self.target); let result = window_target .windows .borrow() .get(&window_id) .and_then(|window| { let arc = window.upgrade(); deleted = arc.is_none(); arc }) .map(|window| callback(&window)); if deleted { // Garbage collection window_target.windows.borrow_mut().remove(&window_id); } result } // NOTE: we avoid `self` to not borrow the entire `self` as not mut. /// Get the platform window target. pub fn window_target(window_target: &RootELW) -> &EventLoopWindowTarget { match &window_target.p { PlatformEventLoopWindowTarget::X(target) => target, #[cfg(wayland_platform)] _ => unreachable!(), } } /// Get the platform window target. pub fn window_target_mut(window_target: &mut RootELW) -> &mut EventLoopWindowTarget { match &mut window_target.p { PlatformEventLoopWindowTarget::X(target) => target, #[cfg(wayland_platform)] _ => unreachable!(), } } fn client_message(&mut self, xev: &XClientMessageEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let atoms = wt.xconn.atoms(); let window = xev.window as xproto::Window; let window_id = mkwid(window); if xev.data.get_long(0) as xproto::Atom == wt.wm_delete_window { let event = Event::WindowEvent { window_id, event: WindowEvent::CloseRequested, }; callback(&self.target, event); return; } if xev.data.get_long(0) as xproto::Atom == wt.net_wm_ping { let client_msg = xproto::ClientMessageEvent { response_type: xproto::CLIENT_MESSAGE_EVENT, format: xev.format as _, sequence: xev.serial as _, window: wt.root, type_: xev.message_type as _, data: xproto::ClientMessageData::from({ let [a, b, c, d, e]: [c_long; 5] = xev.data.as_longs().try_into().unwrap(); [a as u32, b as u32, c as u32, d as u32, e as u32] }), }; wt.xconn .xcb_connection() .send_event( false, wt.root, xproto::EventMask::SUBSTRUCTURE_NOTIFY | xproto::EventMask::SUBSTRUCTURE_REDIRECT, client_msg.serialize(), ) .expect_then_ignore_error("Failed to send `ClientMessage` event."); return; } if xev.message_type == atoms[XdndEnter] as c_ulong { let source_window = xev.data.get_long(0) as xproto::Window; let flags = xev.data.get_long(1); let version = flags >> 24; self.dnd.version = Some(version); let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; if !has_more_types { let type_list = vec![ xev.data.get_long(2) as xproto::Atom, xev.data.get_long(3) as xproto::Atom, xev.data.get_long(4) as xproto::Atom, ]; self.dnd.type_list = Some(type_list); } else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } { self.dnd.type_list = Some(more_types); } return; } if xev.message_type == atoms[XdndPosition] as c_ulong { // This event occurs every time the mouse moves while a file's being dragged // over our window. We emit HoveredFile in response; while the macOS backend // does that upon a drag entering, XDND doesn't have access to the actual drop // data until this event. For parity with other platforms, we only emit // `HoveredFile` the first time, though if winit's API is later extended to // supply position updates with `HoveredFile` or another event, implementing // that here would be trivial. let source_window = xev.data.get_long(0) as xproto::Window; // Equivalent to `(x << shift) | y` // where `shift = mem::size_of::() * 8` // Note that coordinates are in "desktop space", not "window space" // (in X11 parlance, they're root window coordinates) //let packed_coordinates = xev.data.get_long(2); //let shift = mem::size_of::() * 8; //let x = packed_coordinates >> shift; //let y = packed_coordinates & !(x << shift); // By our own state flow, `version` should never be `None` at this point. let version = self.dnd.version.unwrap_or(5); // Action is specified in versions 2 and up, though we don't need it anyway. //let action = xev.data.get_long(4); let accepted = if let Some(ref type_list) = self.dnd.type_list { type_list.contains(&atoms[TextUriList]) } else { false }; if !accepted { unsafe { self.dnd .send_status(window, source_window, DndState::Rejected) .expect("Failed to send `XdndStatus` message."); } self.dnd.reset(); return; } self.dnd.source_window = Some(source_window); if self.dnd.result.is_none() { let time = if version >= 1 { xev.data.get_long(3) as xproto::Timestamp } else { // In version 0, time isn't specified x11rb::CURRENT_TIME }; // Log this timestamp. wt.xconn.set_timestamp(time); // This results in the `SelectionNotify` event below unsafe { self.dnd.convert_selection(window, time); } } unsafe { self.dnd .send_status(window, source_window, DndState::Accepted) .expect("Failed to send `XdndStatus` message."); } return; } if xev.message_type == atoms[XdndDrop] as c_ulong { let (source_window, state) = if let Some(source_window) = self.dnd.source_window { if let Some(Ok(ref path_list)) = self.dnd.result { for path in path_list { let event = Event::WindowEvent { window_id, event: WindowEvent::DroppedFile(path.clone()), }; callback(&self.target, event); } } (source_window, DndState::Accepted) } else { // `source_window` won't be part of our DND state if we already rejected the drop in our // `XdndPosition` handler. let source_window = xev.data.get_long(0) as xproto::Window; (source_window, DndState::Rejected) }; unsafe { self.dnd .send_finished(window, source_window, state) .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); return; } if xev.message_type == atoms[XdndLeave] as c_ulong { self.dnd.reset(); let event = Event::WindowEvent { window_id, event: WindowEvent::HoveredFileCancelled, }; callback(&self.target, event); } } fn selection_notify(&mut self, xev: &XSelectionEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let atoms = wt.xconn.atoms(); let window = xev.requestor as xproto::Window; let window_id = mkwid(window); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if xev.property != atoms[XdndSelection] as c_ulong { return; } // This is where we receive data from drag and drop self.dnd.result = None; if let Ok(mut data) = unsafe { self.dnd.read_data(window) } { let parse_result = self.dnd.parse_data(&mut data); if let Ok(ref path_list) = parse_result { for path in path_list { let event = Event::WindowEvent { window_id, event: WindowEvent::HoveredFile(path.clone()), }; callback(&self.target, event); } } self.dnd.result = Some(parse_result); } } fn configure_notify(&self, xev: &XConfigureEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let xwindow = xev.window as xproto::Window; let window_id = mkwid(xwindow); let window = match self.with_window(xwindow, Arc::clone) { Some(window) => window, None => return, }; // So apparently... // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent // https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5 // We don't want to send `Moved` when this is false, since then every `Resized` // (whether the window moved or not) is accompanied by an extraneous `Moved` event // that has a position relative to the parent window. let is_synthetic = xev.send_event == xlib::True; // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); let new_inner_position = (xev.x, xev.y); let (mut resized, moved) = { let mut shared_state_lock = window.shared_state_lock(); let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size); let moved = if is_synthetic { util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position) } else { // Detect when frame extents change. // Since this isn't synthetic, as per the notes above, this position is relative to the // parent window. let rel_parent = new_inner_position; if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent) { // This ensures we process the next `Moved`. shared_state_lock.inner_position = None; // Extra insurance against stale frame extents. shared_state_lock.frame_extents = None; } false }; (resized, moved) }; let position = window.shared_state_lock().position; let new_outer_position = if let (Some(position), false) = (position, moved) { position } else { let mut shared_state_lock = window.shared_state_lock(); // We need to convert client area position to window position. let frame_extents = shared_state_lock .frame_extents .as_ref() .cloned() .unwrap_or_else(|| { let frame_extents = wt.xconn.get_frame_extents_heuristic(xwindow, wt.root); shared_state_lock.frame_extents = Some(frame_extents.clone()); frame_extents }); let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1); shared_state_lock.position = Some(outer); // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); if moved { callback( &self.target, Event::WindowEvent { window_id, event: WindowEvent::Moved(outer.into()), }, ); } outer }; if is_synthetic { let mut shared_state_lock = window.shared_state_lock(); // If we don't use the existing adjusted value when available, then the user can screw up the // resizing by dragging across monitors *without* dropping the window. let (width, height) = shared_state_lock .dpi_adjusted .unwrap_or((xev.width as u32, xev.height as u32)); let last_scale_factor = shared_state_lock.last_monitor.scale_factor; let new_scale_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); let monitor = wt .xconn .get_monitor_for_window(Some(window_rect)) .expect("Failed to find monitor for window"); if monitor.is_dummy() { // Avoid updating monitor using a dummy monitor handle last_scale_factor } else { shared_state_lock.last_monitor = monitor.clone(); monitor.scale_factor } }; if last_scale_factor != new_scale_factor { let (new_width, new_height) = window.adjust_for_dpi( last_scale_factor, new_scale_factor, width, height, &shared_state_lock, ); let old_inner_size = PhysicalSize::new(width, height); let new_inner_size = PhysicalSize::new(new_width, new_height); // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); let inner_size = Arc::new(Mutex::new(new_inner_size)); callback( &self.target, Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), }, }, ); let new_inner_size = *inner_size.lock().unwrap(); drop(inner_size); if new_inner_size != old_inner_size { window.request_inner_size_physical(new_inner_size.width, new_inner_size.height); window.shared_state_lock().dpi_adjusted = Some(new_inner_size.into()); // if the DPI factor changed, force a resize event to ensure the logical // size is computed with the right DPI factor resized = true; } } } // NOTE: Ensure that the lock is dropped before handling the resized and // sending the event back to user. let hittest = { let mut shared_state_lock = window.shared_state_lock(); let hittest = shared_state_lock.cursor_hittest; // This is a hack to ensure that the DPI adjusted resize is actually // applied on all WMs. KWin doesn't need this, but Xfwm does. The hack // should not be run on other WMs, since tiling WMs constrain the window // size, making the resize fail. This would cause an endless stream of // XResizeWindow requests, making Xorg, the winit client, and the WM // consume 100% of CPU. if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) { // When this finally happens, the event will not be synthetic. shared_state_lock.dpi_adjusted = None; } else { // Unlock shared state to prevent deadlock in callback below drop(shared_state_lock); window.request_inner_size_physical(adjusted_size.0, adjusted_size.1); } } hittest }; // Reload hittest. if hittest.unwrap_or(false) { let _ = window.set_cursor_hittest(true); } if resized { callback( &self.target, Event::WindowEvent { window_id, event: WindowEvent::Resized(new_inner_size.into()), }, ); } } /// This is generally a reliable way to detect when the window manager's been /// replaced, though this event is only fired by reparenting window managers /// (which is almost all of them). Failing to correctly update WM info doesn't /// really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only /// effect is that we waste some time trying to query unsupported properties. fn reparent_notify(&self, xev: &XReparentEvent) { let wt = Self::window_target(&self.target); wt.xconn.update_cached_wm_info(wt.root); self.with_window(xev.window as xproto::Window, |window| { window.invalidate_cached_frame_extents(); }); } fn map_notify(&self, xev: &XMapEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let window = xev.window as xproto::Window; let window_id = mkwid(window); // NOTE: Re-issue the focus state when mapping the window. // // The purpose of it is to deliver initial focused state of the newly created // window, given that we can't rely on `CreateNotify`, due to it being not // sent. let focus = self .with_window(window, |window| window.has_focus()) .unwrap_or_default(); let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(focus), }; callback(&self.target, event); } fn destroy_notify(&self, xev: &XDestroyWindowEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let window = xev.window as xproto::Window; let window_id = mkwid(window); // In the event that the window's been destroyed without being dropped first, we // cleanup again here. wt.windows.borrow_mut().remove(&WindowId(window as _)); // Since all XIM stuff needs to happen from the same thread, we destroy the input // context here instead of when dropping the window. if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut() .remove_context(window as XWindow) .expect("Failed to destroy input context"); } callback( &self.target, Event::WindowEvent { window_id, event: WindowEvent::Destroyed, }, ); } fn property_notify(&mut self, xev: &XPropertyEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let atoms = wt.x_connection().atoms(); let atom = xev.atom as xproto::Atom; if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER) || atom == atoms[_XSETTINGS_SETTINGS] { self.process_dpi_change(&mut callback); } } fn visibility_notify(&self, xev: &XVisibilityEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let xwindow = xev.window as xproto::Window; let event = Event::WindowEvent { window_id: mkwid(xwindow), event: WindowEvent::Occluded(xev.state == xlib::VisibilityFullyObscured), }; callback(&self.target, event); self.with_window(xwindow, |window| { window.visibility_notify(); }); } fn expose(&self, xev: &XExposeEvent, mut callback: F) where F: FnMut(&RootELW, Event), { // Multiple Expose events may be received for subareas of a window. // We issue `RedrawRequested` only for the last event of such a series. if xev.count == 0 { let window = xev.window as xproto::Window; let window_id = mkwid(window); let event = Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }; callback(&self.target, event); } } fn xinput_key_input(&mut self, xev: &mut XKeyEvent, state: ElementState, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let window = match self.active_window { Some(window) => window, None => return, }; let window_id = mkwid(window); let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); let keycode = xev.keycode as _; // Update state to track key repeats and determine whether this key was a repeat. // // Note, when a key is held before focusing on this window the first // (non-synthetic) event will not be flagged as a repeat (also note that the // synthetic press event that is generated before this when the window gains focus // will also not be flagged as a repeat). // // Only keys that can repeat should change the held_key_press state since a // continuously held repeatable key may continue repeating after the press of a // non-repeatable key. let key_repeats = self .xkb_context .keymap_mut() .map(|k| k.key_repeats(keycode)) .unwrap_or(false); let repeat = if key_repeats { let is_latest_held = self.held_key_press == Some(keycode); if state == ElementState::Pressed { self.held_key_press = Some(keycode); is_latest_held } else { // Check that the released key is the latest repeatable key that has been // pressed, since repeats will continue for the latest key press if a // different previously pressed key is released. if is_latest_held { self.held_key_press = None; } false } } else { false }; // NOTE: When the modifier was captured by the XFilterEvents the modifiers for the modifier // itself are out of sync due to XkbState being delivered before XKeyEvent, since it's // being replayed by the XIM, thus we should replay ourselves. let replay = if let Some(position) = self .xfiltered_modifiers .iter() .rev() .position(|&s| s == xev.serial) { // We don't have to replay modifiers pressed before the current event if some events // were not forwarded to us, since their state is irrelevant. self.xfiltered_modifiers .resize(self.xfiltered_modifiers.len() - 1 - position, 0); true } else { false }; // Always update the modifiers when we're not replaying. if !replay { self.udpate_mods_from_core_event(window_id, xev.state as u16, &mut callback); } if keycode != 0 && !self.is_composing { // Don't alter the modifiers state from replaying. if replay { self.send_synthic_modifier_from_core(window_id, xev.state as u16, &mut callback); } if let Some(mut key_processor) = self.xkb_context.key_context() { let event = key_processor.process_key_event(keycode, state, repeat); let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: false, }, }; callback(&self.target, event); } // Restore the client's modifiers state after replay. if replay { self.send_modifiers(window_id, self.modifiers.get(), true, &mut callback); } return; } let wt = Self::window_target(&self.target); if let Some(ic) = wt .ime .as_ref() .and_then(|ime| ime.borrow().get_context(window as XWindow)) { let written = wt.xconn.lookup_utf8(ic, xev); if !written.is_empty() { let event = Event::WindowEvent { window_id, event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }; callback(&self.target, event); let event = Event::WindowEvent { window_id, event: WindowEvent::Ime(Ime::Commit(written)), }; self.is_composing = false; callback(&self.target, event); } } } fn send_synthic_modifier_from_core( &mut self, window_id: crate::window::WindowId, state: u16, mut callback: F, ) where F: FnMut(&RootELW, Event), { let keymap = match self.xkb_context.keymap_mut() { Some(keymap) => keymap, None => return, }; let wt = Self::window_target(&self.target); let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); // Use synthetic state since we're replaying the modifier. The user modifier state // will be restored later. let mut xkb_state = match XkbState::new_x11(xcb, keymap) { Some(xkb_state) => xkb_state, None => return, }; let mask = self.xkb_mod_mask_from_core(state); xkb_state.update_modifiers(mask, 0, 0, 0, 0, Self::core_keyboard_group(state)); let mods: ModifiersState = xkb_state.modifiers().into(); let event = Event::WindowEvent { window_id, event: WindowEvent::ModifiersChanged(mods.into()), }; callback(&self.target, event); } fn xinput2_button_input(&self, event: &XIDeviceEvent, state: ElementState, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let window_id = mkwid(event.event as xproto::Window); let device_id = mkdid(event.deviceid as xinput::DeviceId); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); // Deliver multi-touch events instead of emulated mouse events. if (event.flags & xinput2::XIPointerEmulated) != 0 { return; } let event = match event.detail as u32 { xlib::Button1 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Left, }, xlib::Button2 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Middle, }, xlib::Button3 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Right, }, // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in // turn) as axis motion, so we don't otherwise special-case these button presses. 4..=7 => WindowEvent::MouseWheel { device_id, delta: match event.detail { 4 => MouseScrollDelta::LineDelta(0.0, 1.0), 5 => MouseScrollDelta::LineDelta(0.0, -1.0), 6 => MouseScrollDelta::LineDelta(1.0, 0.0), 7 => MouseScrollDelta::LineDelta(-1.0, 0.0), _ => unreachable!(), }, phase: TouchPhase::Moved, }, 8 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Back, }, 9 => WindowEvent::MouseInput { device_id, state, button: MouseButton::Forward, }, x => WindowEvent::MouseInput { device_id, state, button: MouseButton::Other(x as u16), }, }; let event = Event::WindowEvent { window_id, event }; callback(&self.target, event); } fn xinput2_mouse_motion(&self, event: &XIDeviceEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); let device_id = mkdid(event.deviceid as xinput::DeviceId); let window = event.event as xproto::Window; let window_id = mkwid(window); let new_cursor_pos = (event.event_x, event.event_y); let cursor_moved = self.with_window(window, |window| { let mut shared_state_lock = window.shared_state_lock(); util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); if cursor_moved == Some(true) { let position = PhysicalPosition::new(event.event_x, event.event_y); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id, position, }, }; callback(&self.target, event); } else if cursor_moved.is_none() { return; } // More gymnastics, for self.devices let mask = unsafe { slice::from_raw_parts(event.valuators.mask, event.valuators.mask_len as usize) }; let mut devices = self.devices.borrow_mut(); let physical_device = match devices.get_mut(&DeviceId(event.sourceid as xinput::DeviceId)) { Some(device) => device, None => return, }; let mut events = Vec::new(); let mut value = event.valuators.values; for i in 0..event.valuators.mask_len * 8 { if !xinput2::XIMaskIsSet(mask, i) { continue; } let x = unsafe { *value }; let event = if let Some(&mut (_, ref mut info)) = physical_device .scroll_axes .iter_mut() .find(|&&mut (axis, _)| axis == i as _) { let delta = (x - info.position) / info.increment; info.position = x; // X11 vertical scroll coordinates are opposite to winit's let delta = match info.orientation { ScrollOrientation::Horizontal => { MouseScrollDelta::LineDelta(-delta as f32, 0.0) } ScrollOrientation::Vertical => MouseScrollDelta::LineDelta(0.0, -delta as f32), }; WindowEvent::MouseWheel { device_id, delta, phase: TouchPhase::Moved, } } else { WindowEvent::AxisMotion { device_id, axis: i as u32, value: unsafe { *value }, } }; events.push(Event::WindowEvent { window_id, event }); value = unsafe { value.offset(1) }; } for event in events { callback(&self.target, event); } } fn xinput2_mouse_enter(&self, event: &XIEnterEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); let window = event.event as xproto::Window; let window_id = mkwid(window); let device_id = mkdid(event.deviceid as xinput::DeviceId); if let Some(all_info) = DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into()) { let mut devices = self.devices.borrow_mut(); for device_info in all_info.iter() { if device_info.deviceid == event.sourceid // This is needed for resetting to work correctly on i3, and // presumably some other WMs. On those, `XI_Enter` doesn't include // the physical device ID, so both `sourceid` and `deviceid` are // the virtual device. || device_info.attachment == event.sourceid { let device_id = DeviceId(device_info.deviceid as _); if let Some(device) = devices.get_mut(&device_id) { device.reset_scroll_position(device_info); } } } } if self.window_exists(window) { let position = PhysicalPosition::new(event.event_x, event.event_y); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorEntered { device_id }, }; callback(&self.target, event); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id, position, }, }; callback(&self.target, event); } } fn xinput2_mouse_left(&self, event: &XILeaveEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let window = event.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(event.time as xproto::Timestamp); // Leave, FocusIn, and FocusOut can be received by a window that's already // been destroyed, which the user presumably doesn't want to deal with. if self.window_exists(window) { let event = Event::WindowEvent { window_id: mkwid(window), event: WindowEvent::CursorLeft { device_id: mkdid(event.deviceid as xinput::DeviceId), }, }; callback(&self.target, event); } } fn xinput2_focused(&mut self, xev: &XIFocusInEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let window = xev.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut() .focus(xev.event) .expect("Failed to focus input context"); } if self.active_window == Some(window) { return; } self.active_window = Some(window); wt.update_listen_device_events(true); let window_id = mkwid(window); let position = PhysicalPosition::new(xev.event_x, xev.event_y); if let Some(window) = self.with_window(window, Arc::clone) { window.shared_state_lock().has_focus = true; } let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(true), }; callback(&self.target, event); // Issue key press events for all pressed keys Self::handle_pressed_keys( &self.target, window_id, ElementState::Pressed, &mut self.xkb_context, &mut callback, ); self.update_mods_from_query(window_id, &mut callback); // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. let pointer_id = self .devices .borrow() .get(&DeviceId(xev.deviceid as xinput::DeviceId)) .map(|device| device.attachment) .unwrap_or(2); let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id: mkdid(pointer_id as _), position, }, }; callback(&self.target, event); } fn xinput2_unfocused(&mut self, xev: &XIFocusOutEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let window = xev.event as xproto::Window; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if !self.window_exists(window) { return; } if let Some(ime) = wt.ime.as_ref() { ime.borrow_mut() .unfocus(xev.event) .expect("Failed to unfocus input context"); } if self.active_window.take() == Some(window) { let window_id = mkwid(window); wt.update_listen_device_events(false); // Clear the modifiers when unfocusing the window. if let Some(xkb_state) = self.xkb_context.state_mut() { xkb_state.update_modifiers(0, 0, 0, 0, 0, 0); let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), true, &mut callback); } // Issue key release events for all pressed keys Self::handle_pressed_keys( &self.target, window_id, ElementState::Released, &mut self.xkb_context, &mut callback, ); // Clear this so detecting key repeats is consistently handled when the // window regains focus. self.held_key_press = None; if let Some(window) = self.with_window(window, Arc::clone) { window.shared_state_lock().has_focus = false; } let event = Event::WindowEvent { window_id, event: WindowEvent::Focused(false), }; callback(&self.target, event) } } fn xinput2_touch(&mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let window = xev.event as xproto::Window; if self.window_exists(window) { let window_id = mkwid(window); let id = xev.detail as u64; let location = PhysicalPosition::new(xev.event_x, xev.event_y); // Mouse cursor position changes when touch events are received. // Only the first concurrently active touch ID moves the mouse cursor. if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) { let event = Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { device_id: mkdid(util::VIRTUAL_CORE_POINTER), position: location.cast(), }, }; callback(&self.target, event); } let event = Event::WindowEvent { window_id, event: WindowEvent::Touch(Touch { device_id: mkdid(xev.deviceid as xinput::DeviceId), phase, location, force: None, // TODO id, }), }; callback(&self.target, event) } } fn xinput2_raw_button_input(&self, xev: &XIRawEvent, state: ElementState, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if xev.flags & xinput2::XIPointerEmulated == 0 { let event = Event::DeviceEvent { device_id: mkdid(xev.deviceid as xinput::DeviceId), event: DeviceEvent::Button { state, button: xev.detail as u32, }, }; callback(&self.target, event); } } fn xinput2_raw_mouse_motion(&self, xev: &XIRawEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let did = mkdid(xev.deviceid as xinput::DeviceId); let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; let mut value = xev.raw_values; let mut mouse_delta = util::Delta::default(); let mut scroll_delta = util::Delta::default(); for i in 0..xev.valuators.mask_len * 8 { if !xinput2::XIMaskIsSet(mask, i) { continue; } let x = unsafe { *value }; // We assume that every XInput2 device with analog axes is a pointing device emitting // relative coordinates. match i { 0 => mouse_delta.set_x(x), 1 => mouse_delta.set_y(x), 2 => scroll_delta.set_x(x as f32), 3 => scroll_delta.set_y(x as f32), _ => {} } let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { axis: i as u32, value: x, }, }; callback(&self.target, event); value = unsafe { value.offset(1) }; } if let Some(mouse_delta) = mouse_delta.consume() { let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion { delta: mouse_delta }, }; callback(&self.target, event); } if let Some(scroll_delta) = scroll_delta.consume() { let event = Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(scroll_delta.0, scroll_delta.1), }, }; callback(&self.target, event); } } fn xinput2_raw_key_input(&mut self, xev: &XIRawEvent, state: ElementState, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let device_id = mkdid(xev.sourceid as xinput::DeviceId); let keycode = xev.detail as u32; if keycode < KEYCODE_OFFSET as u32 { return; } let physical_key = xkb::raw_keycode_to_physicalkey(keycode); callback( &self.target, Event::DeviceEvent { device_id, event: DeviceEvent::Key(RawKeyEvent { physical_key, state, }), }, ); } fn xinput2_hierarchy_changed(&mut self, xev: &XIHierarchyEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let infos = unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }; for info in infos { if 0 != info.flags & (xinput2::XISlaveAdded | xinput2::XIMasterAdded) { self.init_device(info.deviceid as xinput::DeviceId); callback( &self.target, Event::DeviceEvent { device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Added, }, ); } else if 0 != info.flags & (xinput2::XISlaveRemoved | xinput2::XIMasterRemoved) { callback( &self.target, Event::DeviceEvent { device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Removed, }, ); let mut devices = self.devices.borrow_mut(); devices.remove(&DeviceId(info.deviceid as xinput::DeviceId)); } } } fn xkb_event(&mut self, xev: &XkbAnyEvent, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); match xev.xkb_type { xlib::XkbNewKeyboardNotify => { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbNewKeyboardNotifyEvent) }; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); let keycodes_changed_flag = 0x1; let geometry_changed_flag = 0x1 << 1; let keycodes_changed = util::has_flag(xev.changed, keycodes_changed_flag); let geometry_changed = util::has_flag(xev.changed, geometry_changed_flag); if xev.device == self.xkb_context.core_keyboard_id && (keycodes_changed || geometry_changed) { let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); self.xkb_context.set_keymap_from_x11(xcb); self.xmodmap.reload_from_x_connection(&wt.xconn); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; if let Some(state) = self.xkb_context.state_mut() { let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } } } xlib::XkbMapNotify => { let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection(); self.xkb_context.set_keymap_from_x11(xcb); self.xmodmap.reload_from_x_connection(&wt.xconn); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; if let Some(state) = self.xkb_context.state_mut() { let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } } xlib::XkbStateNotify => { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) }; // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); if let Some(state) = self.xkb_context.state_mut() { state.update_modifiers( xev.base_mods, xev.latched_mods, xev.locked_mods, xev.base_group as u32, xev.latched_group as u32, xev.locked_group as u32, ); let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; let mods = state.modifiers().into(); self.send_modifiers(window_id, mods, true, &mut callback); } } _ => {} } } pub fn update_mods_from_xinput2_event( &mut self, mods: &XIModifierState, group: &XIModifierState, force: bool, mut callback: F, ) where F: FnMut(&RootELW, Event), { if let Some(state) = self.xkb_context.state_mut() { state.update_modifiers( mods.base as u32, mods.latched as u32, mods.locked as u32, group.base as u32, group.latched as u32, group.locked as u32, ); // NOTE: we use active window since generally sub windows don't have keyboard input, // and winit assumes that unfocused window doesn't have modifiers. let window_id = match self.active_window.map(super::mkwid) { Some(window_id) => window_id, None => return, }; let mods = state.modifiers(); self.send_modifiers(window_id, mods.into(), force, &mut callback); } } fn update_mods_from_query(&mut self, window_id: crate::window::WindowId, mut callback: F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); let xkb_state = match self.xkb_context.state_mut() { Some(xkb_state) => xkb_state, None => return, }; unsafe { let mut state: XkbStateRec = std::mem::zeroed(); if (wt.xconn.xlib.XkbGetState)(wt.xconn.display, XkbId::USE_CORE_KBD.into(), &mut state) == xlib::True { xkb_state.update_modifiers( state.base_mods as u32, state.latched_mods as u32, state.locked_mods as u32, state.base_group as u32, state.latched_group as u32, state.locked_group as u32, ); } } let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), true, &mut callback) } pub fn udpate_mods_from_core_event( &mut self, window_id: crate::window::WindowId, state: u16, mut callback: F, ) where F: FnMut(&RootELW, Event), { let xkb_mask = self.xkb_mod_mask_from_core(state); let xkb_state = match self.xkb_context.state_mut() { Some(xkb_state) => xkb_state, None => return, }; // NOTE: this is inspired by Qt impl. let mut depressed = xkb_state.depressed_modifiers() & xkb_mask; let latched = xkb_state.latched_modifiers() & xkb_mask; let locked = xkb_state.locked_modifiers() & xkb_mask; // Set modifiers in depressed if they don't appear in any of the final masks. depressed |= !(depressed | latched | locked) & xkb_mask; xkb_state.update_modifiers( depressed, latched, locked, 0, 0, Self::core_keyboard_group(state), ); let mods = xkb_state.modifiers(); self.send_modifiers(window_id, mods.into(), false, &mut callback); } // Bits 13 and 14 report the state keyboard group. pub fn core_keyboard_group(state: u16) -> u32 { ((state >> 13) & 3) as u32 } pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t { let mods_indices = match self.xkb_context.keymap_mut() { Some(keymap) => keymap.mods_indices(), None => return 0, }; // Build the XKB modifiers from the regular state. let mut depressed = 0u32; if let Some(shift) = mods_indices .shift .filter(|_| ModMask::SHIFT.intersects(state)) { depressed |= 1 << shift; } if let Some(caps) = mods_indices .caps .filter(|_| ModMask::LOCK.intersects(state)) { depressed |= 1 << caps; } if let Some(ctrl) = mods_indices .ctrl .filter(|_| ModMask::CONTROL.intersects(state)) { depressed |= 1 << ctrl; } if let Some(alt) = mods_indices.alt.filter(|_| ModMask::M1.intersects(state)) { depressed |= 1 << alt; } if let Some(num) = mods_indices.num.filter(|_| ModMask::M2.intersects(state)) { depressed |= 1 << num; } if let Some(mod3) = mods_indices.mod3.filter(|_| ModMask::M3.intersects(state)) { depressed |= 1 << mod3; } if let Some(logo) = mods_indices.logo.filter(|_| ModMask::M4.intersects(state)) { depressed |= 1 << logo; } if let Some(mod5) = mods_indices.mod5.filter(|_| ModMask::M5.intersects(state)) { depressed |= 1 << mod5; } depressed } /// Send modifiers for the active window. /// /// The event won't be sent when the `modifiers` match the previously `sent` modifiers value, /// unless `force` is passed. The `force` should be passed when the active window changes. fn send_modifiers, Event)>( &self, window_id: crate::window::WindowId, modifiers: ModifiersState, force: bool, callback: &mut F, ) { // NOTE: Always update the modifiers to account for case when they've changed // and forced was `true`. if self.modifiers.replace(modifiers) != modifiers || force { let event = Event::WindowEvent { window_id, event: WindowEvent::ModifiersChanged(self.modifiers.get().into()), }; callback(&self.target, event); } } fn handle_pressed_keys( target: &RootELW, window_id: crate::window::WindowId, state: ElementState, xkb_context: &mut Context, callback: &mut F, ) where F: FnMut(&RootELW, Event), { let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); // Update modifiers state and emit key events based on which keys are currently pressed. let window_target = Self::window_target(target); let xcb = window_target .xconn .xcb_connection() .get_raw_xcb_connection(); let keymap = match xkb_context.keymap_mut() { Some(keymap) => keymap, None => return, }; // Send the keys using the sythetic state to not alter the main state. let mut xkb_state = match XkbState::new_x11(xcb, keymap) { Some(xkb_state) => xkb_state, None => return, }; let mut key_processor = match xkb_context.key_context_with_state(&mut xkb_state) { Some(key_processor) => key_processor, None => return, }; for keycode in window_target .xconn .query_keymap() .into_iter() .filter(|k| *k >= KEYCODE_OFFSET) { let event = key_processor.process_key_event(keycode as u32, state, false); let event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, event, is_synthetic: true, }, }; callback(target, event); } } fn process_dpi_change(&self, callback: &mut F) where F: FnMut(&RootELW, Event), { let wt = Self::window_target(&self.target); wt.xconn .reload_database() .expect("failed to reload Xft database"); // In the future, it would be quite easy to emit monitor hotplug events. let prev_list = { let prev_list = wt.xconn.invalidate_cached_monitor_list(); match prev_list { Some(prev_list) => prev_list, None => return, } }; let new_list = wt .xconn .available_monitors() .expect("Failed to get monitor list"); for new_monitor in new_list { // Previous list may be empty, in case of disconnecting and // reconnecting the only one monitor. We still need to emit events in // this case. let maybe_prev_scale_factor = prev_list .iter() .find(|prev_monitor| prev_monitor.name == new_monitor.name) .map(|prev_monitor| prev_monitor.scale_factor); if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { for window in wt.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) { window.refresh_dpi_for_monitor(&new_monitor, maybe_prev_scale_factor, |event| { callback(&self.target, event); }) } } } } fn window_exists(&self, window_id: xproto::Window) -> bool { self.with_window(window_id, |_| ()).is_some() } } fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { match phase { TouchPhase::Started => { if *num == 0 { *first = Some(id); } *num += 1; } TouchPhase::Cancelled | TouchPhase::Ended => { if *first == Some(id) { *first = None; } *num = num.saturating_sub(1); } _ => (), } *first == Some(id) } winit-0.29.15/src/platform_impl/linux/x11/ffi.rs000064400000000000000000000001221046102023000174450ustar 00000000000000pub use x11_dl::{error::OpenError, xcursor::*, xinput2::*, xlib::*, xlib_xcb::*}; winit-0.29.15/src/platform_impl/linux/x11/ime/callbacks.rs000064400000000000000000000164471046102023000214130ustar 00000000000000use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc}; use super::{ffi, XConnection, XError}; use super::{ context::{ImeContext, ImeContextCreationError}, inner::{close_im, ImeInner}, input_method::PotentialInputMethods, }; pub(crate) unsafe fn xim_set_callback( xconn: &Arc, xim: ffi::XIM, field: *const c_char, callback: *mut ffi::XIMCallback, ) -> Result<(), XError> { // It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize // access that isn't type-checked. unsafe { (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()) }; xconn.check_errors() } // Set a callback for when an input method matching the current locale modifiers becomes // available. Note that this has nothing to do with what input methods are open or able to be // opened, and simply uses the modifiers that are set when the callback is set. // * This is called per locale modifier, not per input method opened with that locale modifier. // * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt // input contexts would always silently fail to use the input method. pub(crate) unsafe fn set_instantiate_callback( xconn: &Arc, client_data: ffi::XPointer, ) -> Result<(), XError> { unsafe { (xconn.xlib.XRegisterIMInstantiateCallback)( xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), Some(xim_instantiate_callback), client_data, ) }; xconn.check_errors() } pub(crate) unsafe fn unset_instantiate_callback( xconn: &Arc, client_data: ffi::XPointer, ) -> Result<(), XError> { unsafe { (xconn.xlib.XUnregisterIMInstantiateCallback)( xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), Some(xim_instantiate_callback), client_data, ) }; xconn.check_errors() } pub(crate) unsafe fn set_destroy_callback( xconn: &Arc, im: ffi::XIM, inner: &ImeInner, ) -> Result<(), XError> { unsafe { xim_set_callback( xconn, im, ffi::XNDestroyCallback_0.as_ptr() as *const _, &inner.destroy_callback as *const _ as *mut _, ) } } #[derive(Debug)] #[allow(clippy::enum_variant_names)] enum ReplaceImError { // Boxed to prevent large error type MethodOpenFailed(#[allow(dead_code)] Box), ContextCreationFailed(#[allow(dead_code)] ImeContextCreationError), SetDestroyCallbackFailed(#[allow(dead_code)] XError), } // Attempt to replace current IM (which may or may not be presently valid) with a new one. This // includes replacing all existing input contexts and free'ing resources as necessary. This only // modifies existing state if all operations succeed. unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> { let xconn = unsafe { &(*inner).xconn }; let (new_im, is_fallback) = { let new_im = unsafe { (*inner).potential_input_methods.open_im(xconn, None) }; let is_fallback = new_im.is_fallback(); ( new_im.ok().ok_or_else(|| { ReplaceImError::MethodOpenFailed(Box::new(unsafe { (*inner).potential_input_methods.clone() })) })?, is_fallback, ) }; // It's important to always set a destroy callback, since there's otherwise potential for us // to try to use or free a resource that's already been destroyed on the server. { let result = unsafe { set_destroy_callback(xconn, new_im.im, &*inner) }; if result.is_err() { let _ = unsafe { close_im(xconn, new_im.im) }; } result } .map_err(ReplaceImError::SetDestroyCallbackFailed)?; let mut new_contexts = HashMap::new(); for (window, old_context) in unsafe { (*inner).contexts.iter() } { let spot = old_context.as_ref().map(|old_context| old_context.ic_spot); // Check if the IME was allowed on that context. let is_allowed = old_context .as_ref() .map(|old_context| old_context.is_allowed()) .unwrap_or_default(); // We can't use the style from the old context here, since it may change on reload, so // pick style from the new XIM based on the old state. let style = if is_allowed { new_im.preedit_style } else { new_im.none_style }; let new_context = { let result = unsafe { ImeContext::new( xconn, new_im.im, style, *window, spot, (*inner).event_sender.clone(), ) }; if result.is_err() { let _ = unsafe { close_im(xconn, new_im.im) }; } result.map_err(ReplaceImError::ContextCreationFailed)? }; new_contexts.insert(*window, Some(new_context)); } // If we've made it this far, everything succeeded. unsafe { let _ = (*inner).destroy_all_contexts_if_necessary(); let _ = (*inner).close_im_if_necessary(); (*inner).im = Some(new_im); (*inner).contexts = new_contexts; (*inner).is_destroyed = false; (*inner).is_fallback = is_fallback; } Ok(()) } pub unsafe extern "C" fn xim_instantiate_callback( _display: *mut ffi::Display, client_data: ffi::XPointer, // This field is unsupplied. _call_data: ffi::XPointer, ) { let inner: *mut ImeInner = client_data as _; if !inner.is_null() { let xconn = unsafe { &(*inner).xconn }; match unsafe { replace_im(inner) } { Ok(()) => unsafe { let _ = unset_instantiate_callback(xconn, client_data); (*inner).is_fallback = false; }, Err(err) => unsafe { if (*inner).is_destroyed { // We have no usable input methods! panic!("Failed to reopen input method: {err:?}"); } }, } } } // This callback is triggered when the input method is closed on the server end. When this // happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been // free'd (attempting to do so causes our connection to freeze). pub unsafe extern "C" fn xim_destroy_callback( _xim: ffi::XIM, client_data: ffi::XPointer, // This field is unsupplied. _call_data: ffi::XPointer, ) { let inner: *mut ImeInner = client_data as _; if !inner.is_null() { unsafe { (*inner).is_destroyed = true }; let xconn = unsafe { &(*inner).xconn }; if unsafe { !(*inner).is_fallback } { let _ = unsafe { set_instantiate_callback(xconn, client_data) }; // Attempt to open fallback input method. match unsafe { replace_im(inner) } { Ok(()) => unsafe { (*inner).is_fallback = true }, Err(err) => { // We have no usable input methods! panic!("Failed to open fallback input method: {err:?}"); } } } } } winit-0.29.15/src/platform_impl/linux/x11/ime/context.rs000064400000000000000000000301551046102023000211500ustar 00000000000000use std::ffi::CStr; use std::os::raw::c_short; use std::sync::Arc; use std::{mem, ptr}; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle}; use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; use super::{ffi, util, XConnection, XError}; /// IME creation error. #[derive(Debug)] pub enum ImeContextCreationError { /// Got the error from Xlib. XError(XError), /// Got null pointer from Xlib but without exact reason. Null, } /// The callback used by XIM preedit functions. type XIMProcNonnull = unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer); /// Wrapper for creating XIM callbacks. #[inline] fn create_xim_callback(client_data: ffi::XPointer, callback: XIMProcNonnull) -> ffi::XIMCallback { XIMCallback { client_data, callback: Some(callback), } } /// The server started preedit. extern "C" fn preedit_start_callback( _xim: ffi::XIM, client_data: ffi::XPointer, _call_data: ffi::XPointer, ) -> i32 { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; client_data.text.clear(); client_data.cursor_pos = 0; client_data .event_sender .send((client_data.window, ImeEvent::Start)) .expect("failed to send preedit start event"); -1 } /// Done callback is used when the preedit should be hidden. extern "C" fn preedit_done_callback( _xim: ffi::XIM, client_data: ffi::XPointer, _call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; // Drop text buffer and reset cursor position on done. client_data.text = Vec::new(); client_data.cursor_pos = 0; client_data .event_sender .send((client_data.window, ImeEvent::End)) .expect("failed to send preedit end event"); } fn calc_byte_position(text: &[char], pos: usize) -> usize { text.iter() .take(pos) .fold(0, |byte_pos, text| byte_pos + text.len_utf8()) } /// Preedit text information to be drawn inline by the client. extern "C" fn preedit_draw_callback( _xim: ffi::XIM, client_data: ffi::XPointer, call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; let call_data = unsafe { &mut *(call_data as *mut XIMPreeditDrawCallbackStruct) }; client_data.cursor_pos = call_data.caret as usize; let chg_range = call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize; if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() { warn!( "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}", client_data.text.len(), call_data.chg_first, call_data.chg_length ); return; } // NULL indicate text deletion let mut new_chars = if call_data.text.is_null() { Vec::new() } else { let xim_text = unsafe { &mut *(call_data.text) }; if xim_text.encoding_is_wchar > 0 { return; } let new_text = unsafe { xim_text.string.multi_byte }; if new_text.is_null() { return; } let new_text = unsafe { CStr::from_ptr(new_text) }; String::from(new_text.to_str().expect("Invalid UTF-8 String from IME")) .chars() .collect() }; let mut old_text_tail = client_data.text.split_off(chg_range.end); client_data.text.truncate(chg_range.start); client_data.text.append(&mut new_chars); client_data.text.append(&mut old_text_tail); let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); client_data .event_sender .send(( client_data.window, ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), )) .expect("failed to send preedit update event"); } /// Handling of cursor movements in preedit text. extern "C" fn preedit_caret_callback( _xim: ffi::XIM, client_data: ffi::XPointer, call_data: ffi::XPointer, ) { let client_data = unsafe { &mut *(client_data as *mut ImeContextClientData) }; let call_data = unsafe { &mut *(call_data as *mut XIMPreeditCaretCallbackStruct) }; if call_data.direction == ffi::XIMCaretDirection::XIMAbsolutePosition { client_data.cursor_pos = call_data.position as usize; let cursor_byte_pos = calc_byte_position(&client_data.text, client_data.cursor_pos); client_data .event_sender .send(( client_data.window, ImeEvent::Update(client_data.text.iter().collect(), cursor_byte_pos), )) .expect("failed to send preedit update event"); } } /// Struct to simplify callback creation and latter passing into Xlib XIM. struct PreeditCallbacks { start_callback: ffi::XIMCallback, done_callback: ffi::XIMCallback, draw_callback: ffi::XIMCallback, caret_callback: ffi::XIMCallback, } impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { mem::transmute(preedit_start_callback as usize) }); let done_callback = create_xim_callback(client_data, preedit_done_callback); let caret_callback = create_xim_callback(client_data, preedit_caret_callback); let draw_callback = create_xim_callback(client_data, preedit_draw_callback); PreeditCallbacks { start_callback, done_callback, caret_callback, draw_callback, } } } struct ImeContextClientData { window: ffi::Window, event_sender: ImeEventSender, text: Vec, cursor_pos: usize, } // XXX: this struct doesn't destroy its XIC resource when dropped. // This is intentional, as it doesn't have enough information to know whether or not the context // still exists on the server. Since `ImeInner` has that awareness, destruction must be handled // through `ImeInner`. pub struct ImeContext { pub(crate) ic: ffi::XIC, pub(crate) ic_spot: ffi::XPoint, pub(crate) style: Style, // Since the data is passed shared between X11 XIM callbacks, but couldn't be direclty free from // there we keep the pointer to automatically deallocate it. _client_data: Box, } impl ImeContext { pub(crate) unsafe fn new( xconn: &Arc, im: ffi::XIM, style: Style, window: ffi::Window, ic_spot: Option, event_sender: ImeEventSender, ) -> Result { let client_data = Box::into_raw(Box::new(ImeContextClientData { window, event_sender, text: Vec::new(), cursor_pos: 0, })); let ic = match style as _ { Style::Preedit(style) => unsafe { ImeContext::create_preedit_ic( xconn, im, style, window, client_data as ffi::XPointer, ) }, Style::Nothing(style) => unsafe { ImeContext::create_nothing_ic(xconn, im, style, window) }, Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) }, } .ok_or(ImeContextCreationError::Null)?; xconn .check_errors() .map_err(ImeContextCreationError::XError)?; let mut context = ImeContext { ic, ic_spot: ffi::XPoint { x: 0, y: 0 }, style, _client_data: unsafe { Box::from_raw(client_data) }, }; // Set the spot location, if it's present. if let Some(ic_spot) = ic_spot { context.set_spot(xconn, ic_spot.x, ic_spot.y) } Ok(context) } unsafe fn create_none_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, ) -> Option { let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } unsafe fn create_preedit_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, client_data: ffi::XPointer, ) -> Option { let preedit_callbacks = PreeditCallbacks::new(client_data); let preedit_attr = util::memory::XSmartPointer::new(xconn, unsafe { (xconn.xlib.XVaCreateNestedList)( 0, ffi::XNPreeditStartCallback_0.as_ptr() as *const _, &(preedit_callbacks.start_callback) as *const _, ffi::XNPreeditDoneCallback_0.as_ptr() as *const _, &(preedit_callbacks.done_callback) as *const _, ffi::XNPreeditCaretCallback_0.as_ptr() as *const _, &(preedit_callbacks.caret_callback) as *const _, ffi::XNPreeditDrawCallback_0.as_ptr() as *const _, &(preedit_callbacks.draw_callback) as *const _, ptr::null_mut::<()>(), ) }) .expect("XVaCreateNestedList returned NULL"); let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ffi::XNPreeditAttributes_0.as_ptr() as *const _, preedit_attr.ptr, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } unsafe fn create_nothing_ic( xconn: &Arc, im: ffi::XIM, style: XIMStyle, window: ffi::Window, ) -> Option { let ic = unsafe { (xconn.xlib.XCreateIC)( im, ffi::XNInputStyle_0.as_ptr() as *const _, style, ffi::XNClientWindow_0.as_ptr() as *const _, window, ptr::null_mut::<()>(), ) }; (!ic.is_null()).then_some(ic) } pub(crate) fn focus(&self, xconn: &Arc) -> Result<(), XError> { unsafe { (xconn.xlib.XSetICFocus)(self.ic); } xconn.check_errors() } pub(crate) fn unfocus(&self, xconn: &Arc) -> Result<(), XError> { unsafe { (xconn.xlib.XUnsetICFocus)(self.ic); } xconn.check_errors() } pub fn is_allowed(&self) -> bool { !matches!(self.style, Style::None(_)) } // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks // are being used. Certain IMEs do show selection window, but it's placed in bottom left of the // window and couldn't be changed. // // For me see: https://bugs.freedesktop.org/show_bug.cgi?id=1580. pub(crate) fn set_spot(&mut self, xconn: &Arc, x: c_short, y: c_short) { if !self.is_allowed() || self.ic_spot.x == x && self.ic_spot.y == y { return; } self.ic_spot = ffi::XPoint { x, y }; unsafe { let preedit_attr = util::memory::XSmartPointer::new( xconn, (xconn.xlib.XVaCreateNestedList)( 0, ffi::XNSpotLocation_0.as_ptr(), &self.ic_spot, ptr::null_mut::<()>(), ), ) .expect("XVaCreateNestedList returned NULL"); (xconn.xlib.XSetICValues)( self.ic, ffi::XNPreeditAttributes_0.as_ptr() as *const _, preedit_attr.ptr, ptr::null_mut::<()>(), ); } } } winit-0.29.15/src/platform_impl/linux/x11/ime/inner.rs000064400000000000000000000045021046102023000205740ustar 00000000000000use std::{collections::HashMap, mem, sync::Arc}; use super::{ffi, XConnection, XError}; use super::{ context::ImeContext, input_method::{InputMethod, PotentialInputMethods}, }; use crate::platform_impl::platform::x11::ime::ImeEventSender; pub(crate) unsafe fn close_im(xconn: &Arc, im: ffi::XIM) -> Result<(), XError> { unsafe { (xconn.xlib.XCloseIM)(im) }; xconn.check_errors() } pub(crate) unsafe fn destroy_ic(xconn: &Arc, ic: ffi::XIC) -> Result<(), XError> { unsafe { (xconn.xlib.XDestroyIC)(ic) }; xconn.check_errors() } pub(crate) struct ImeInner { pub xconn: Arc, pub im: Option, pub potential_input_methods: PotentialInputMethods, pub contexts: HashMap>, // WARNING: this is initially zeroed! pub destroy_callback: ffi::XIMCallback, pub event_sender: ImeEventSender, // Indicates whether or not the the input method was destroyed on the server end // (i.e. if ibus/fcitx/etc. was terminated/restarted) pub is_destroyed: bool, pub is_fallback: bool, } impl ImeInner { pub(crate) fn new( xconn: Arc, potential_input_methods: PotentialInputMethods, event_sender: ImeEventSender, ) -> Self { ImeInner { xconn, im: None, potential_input_methods, contexts: HashMap::new(), destroy_callback: unsafe { mem::zeroed() }, event_sender, is_destroyed: false, is_fallback: false, } } pub unsafe fn close_im_if_necessary(&self) -> Result { if !self.is_destroyed && self.im.is_some() { unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true) } else { Ok(false) } } pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result { if !self.is_destroyed { unsafe { destroy_ic(&self.xconn, ic) }.map(|_| true) } else { Ok(false) } } pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result { for context in self.contexts.values().flatten() { unsafe { self.destroy_ic_if_necessary(context.ic)? }; } Ok(!self.is_destroyed) } } winit-0.29.15/src/platform_impl/linux/x11/ime/input_method.rs000064400000000000000000000301231046102023000221560ustar 00000000000000use std::{ env, ffi::{CStr, CString, IntoStringError}, fmt, os::raw::{c_char, c_ulong, c_ushort}, ptr, sync::{Arc, Mutex}, }; use super::{super::atoms::*, ffi, util, XConnection, XError}; use once_cell::sync::Lazy; use x11rb::protocol::xproto; static GLOBAL_LOCK: Lazy> = Lazy::new(Default::default); unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option { let _lock = GLOBAL_LOCK.lock(); // XSetLocaleModifiers returns... // * The current locale modifiers if it's given a NULL pointer. // * The new locale modifiers if we succeeded in setting them. // * NULL if the locale modifiers string is malformed or if the // current locale is not supported by Xlib. unsafe { (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()) }; let im = unsafe { (xconn.xlib.XOpenIM)( xconn.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ) }; if im.is_null() { None } else { Some(im) } } #[derive(Debug)] pub struct InputMethod { pub im: ffi::XIM, pub preedit_style: Style, pub none_style: Style, _name: String, } impl InputMethod { fn new(xconn: &Arc, im: ffi::XIM, name: String) -> Option { let mut styles: *mut XIMStyles = std::ptr::null_mut(); // Query the styles supported by the XIM. unsafe { if !(xconn.xlib.XGetIMValues)( im, ffi::XNQueryInputStyle_0.as_ptr() as *const _, (&mut styles) as *mut _, std::ptr::null_mut::<()>(), ) .is_null() { return None; } } let mut preedit_style = None; let mut none_style = None; unsafe { std::slice::from_raw_parts((*styles).supported_styles, (*styles).count_styles as _) .iter() .for_each(|style| match *style { XIM_PREEDIT_STYLE => { preedit_style = Some(Style::Preedit(*style)); } XIM_NOTHING_STYLE if preedit_style.is_none() => { preedit_style = Some(Style::Nothing(*style)) } XIM_NONE_STYLE => none_style = Some(Style::None(*style)), _ => (), }); (xconn.xlib.XFree)(styles.cast()); }; if preedit_style.is_none() && none_style.is_none() { return None; } let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap()); let none_style = none_style.unwrap_or(preedit_style); Some(InputMethod { im, _name: name, preedit_style, none_style, }) } } const XIM_PREEDIT_STYLE: XIMStyle = (ffi::XIMPreeditCallbacks | ffi::XIMStatusNothing) as XIMStyle; const XIM_NOTHING_STYLE: XIMStyle = (ffi::XIMPreeditNothing | ffi::XIMStatusNothing) as XIMStyle; const XIM_NONE_STYLE: XIMStyle = (ffi::XIMPreeditNone | ffi::XIMStatusNone) as XIMStyle; /// Style of the IME context. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Style { /// Preedit callbacks. Preedit(XIMStyle), /// Nothing. Nothing(XIMStyle), /// No IME. None(XIMStyle), } impl Default for Style { fn default() -> Self { Style::None(XIM_NONE_STYLE) } } #[repr(C)] #[derive(Debug)] struct XIMStyles { count_styles: c_ushort, supported_styles: *const XIMStyle, } pub(crate) type XIMStyle = c_ulong; #[derive(Debug)] pub enum InputMethodResult { /// Input method used locale modifier from `XMODIFIERS` environment variable. XModifiers(InputMethod), /// Input method used internal fallback locale modifier. Fallback(InputMethod), /// Input method could not be opened using any locale modifier tried. Failure, } impl InputMethodResult { pub fn is_fallback(&self) -> bool { matches!(self, InputMethodResult::Fallback(_)) } pub fn ok(self) -> Option { use self::InputMethodResult::*; match self { XModifiers(im) | Fallback(im) => Some(im), Failure => None, } } } #[derive(Debug, Clone)] enum GetXimServersError { XError(#[allow(dead_code)] XError), GetPropertyError(#[allow(dead_code)] util::GetPropertyError), InvalidUtf8(#[allow(dead_code)] IntoStringError), } impl From for GetXimServersError { fn from(error: util::GetPropertyError) -> Self { GetXimServersError::GetPropertyError(error) } } // The root window has a property named XIM_SERVERS, which contains a list of atoms represeting // the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named // "@server=ibus". It's possible for this property to contain multiple atoms, though presumably // rare. Note that we replace "@server=" with "@im=" in order to match the format of locale // modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set // XMODIFIERS to `@server=ibus`?!?" unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXimServersError> { let atoms = xconn.atoms(); let servers_atom = atoms[XIM_SERVERS]; let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; let mut atoms: Vec = xconn .get_property::( root as xproto::Window, servers_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ) .map_err(GetXimServersError::GetPropertyError)? .into_iter() .map(ffi::Atom::from) .collect::>(); let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); unsafe { (xconn.xlib.XGetAtomNames)( xconn.display, atoms.as_mut_ptr(), atoms.len() as _, names.as_mut_ptr() as _, ) }; unsafe { names.set_len(atoms.len()) }; let mut formatted_names = Vec::with_capacity(names.len()); for name in names { let string = unsafe { CStr::from_ptr(name) } .to_owned() .into_string() .map_err(GetXimServersError::InvalidUtf8)?; unsafe { (xconn.xlib.XFree)(name as _) }; formatted_names.push(string.replace("@server=", "@im=")); } xconn.check_errors().map_err(GetXimServersError::XError)?; Ok(formatted_names) } #[derive(Clone)] struct InputMethodName { c_string: CString, string: String, } impl InputMethodName { pub fn from_string(string: String) -> Self { let c_string = CString::new(string.clone()) .expect("String used to construct CString contained null byte"); InputMethodName { c_string, string } } pub fn from_str(string: &str) -> Self { let c_string = CString::new(string).expect("String used to construct CString contained null byte"); InputMethodName { c_string, string: string.to_owned(), } } } impl fmt::Debug for InputMethodName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.string.fmt(f) } } #[derive(Debug, Clone)] struct PotentialInputMethod { name: InputMethodName, successful: Option, } impl PotentialInputMethod { pub fn from_string(string: String) -> Self { PotentialInputMethod { name: InputMethodName::from_string(string), successful: None, } } pub fn from_str(string: &str) -> Self { PotentialInputMethod { name: InputMethodName::from_str(string), successful: None, } } pub fn reset(&mut self) { self.successful = None; } pub fn open_im(&mut self, xconn: &Arc) -> Option { let im = unsafe { open_im(xconn, &self.name.c_string) }; self.successful = Some(im.is_some()); im.and_then(|im| InputMethod::new(xconn, im, self.name.string.clone())) } } // By logging this struct, you get a sequential listing of every locale modifier tried, where it // came from, and if it succeeded. #[derive(Debug, Clone)] pub(crate) struct PotentialInputMethods { // On correctly configured systems, the XMODIFIERS environment variable tells us everything we // need to know. xmodifiers: Option, // We have some standard options at our disposal that should ostensibly always work. For users // who only need compose sequences, this ensures that the program launches without a hitch // For users who need more sophisticated IME features, this is more or less a silent failure. // Logging features should be added in the future to allow both audiences to be effectively // served. fallbacks: [PotentialInputMethod; 2], // For diagnostic purposes, we include the list of XIM servers that the server reports as // being available. _xim_servers: Result, GetXimServersError>, } impl PotentialInputMethods { pub fn new(xconn: &Arc) -> Self { let xmodifiers = env::var("XMODIFIERS") .ok() .map(PotentialInputMethod::from_string); PotentialInputMethods { // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is // defined in the profile (or parent environment) then that parent XMODIFIERS is used. // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then // XSetLocaleModifiers uses the default local input method. Note that defining // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in // that case, we get `None` and end up skipping ahead to the next method. xmodifiers, fallbacks: [ // This is a standard input method that supports compose sequences, which should // always be available. `@im=none` appears to mean the same thing. PotentialInputMethod::from_str("@im=local"), // This explicitly specifies to use the implementation-dependent default, though // that seems to be equivalent to just using the local input method. PotentialInputMethod::from_str("@im="), ], // The XIM_SERVERS property can have surprising values. For instance, when I exited // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is // that the fcitx input method could only be successfully opened using "@im=ibus". // Presumably due to this quirk, it's actually possible to alternate between ibus and // fcitx in a running application. _xim_servers: unsafe { get_xim_servers(xconn) }, } } // This resets the `successful` field of every potential input method, ensuring we have // accurate information when this struct is re-used by the destruction/instantiation callbacks. fn reset(&mut self) { if let Some(ref mut input_method) = self.xmodifiers { input_method.reset(); } for input_method in &mut self.fallbacks { input_method.reset(); } } pub fn open_im( &mut self, xconn: &Arc, callback: Option<&dyn Fn()>, ) -> InputMethodResult { use self::InputMethodResult::*; self.reset(); if let Some(ref mut input_method) = self.xmodifiers { let im = input_method.open_im(xconn); if let Some(im) = im { return XModifiers(im); } else if let Some(ref callback) = callback { callback(); } } for input_method in &mut self.fallbacks { let im = input_method.open_im(xconn); if let Some(im) = im { return Fallback(im); } } Failure } } winit-0.29.15/src/platform_impl/linux/x11/ime/mod.rs000064400000000000000000000166561046102023000202550ustar 00000000000000// Important: all XIM calls need to happen from the same thread! mod callbacks; mod context; mod inner; mod input_method; use std::sync::{ mpsc::{Receiver, Sender}, Arc, }; use super::{ffi, util, XConnection, XError}; pub use self::context::ImeContextCreationError; use self::{ callbacks::*, context::ImeContext, inner::{close_im, ImeInner}, input_method::{PotentialInputMethods, Style}, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ImeEvent { Enabled, Start, Update(String, usize), End, Disabled, } pub type ImeReceiver = Receiver; pub type ImeSender = Sender; pub type ImeEventReceiver = Receiver<(ffi::Window, ImeEvent)>; pub type ImeEventSender = Sender<(ffi::Window, ImeEvent)>; /// Request to control XIM handler from the window. pub enum ImeRequest { /// Set IME spot position for given `window_id`. Position(ffi::Window, i16, i16), /// Allow IME input for the given `window_id`. Allow(ffi::Window, bool), } #[derive(Debug)] pub(crate) enum ImeCreationError { // Boxed to prevent large error type OpenFailure(Box), SetDestroyCallbackFailed(#[allow(dead_code)] XError), } pub(crate) struct Ime { xconn: Arc, // The actual meat of this struct is boxed away, since it needs to have a fixed location in // memory so we can pass a pointer to it around. inner: Box, } impl Ime { pub fn new( xconn: Arc, event_sender: ImeEventSender, ) -> Result { let potential_input_methods = PotentialInputMethods::new(&xconn); let (mut inner, client_data) = { let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods, event_sender)); let inner_ptr = Box::into_raw(inner); let client_data = inner_ptr as _; let destroy_callback = ffi::XIMCallback { client_data, callback: Some(xim_destroy_callback), }; inner = unsafe { Box::from_raw(inner_ptr) }; inner.destroy_callback = destroy_callback; (inner, client_data) }; let xconn = Arc::clone(&inner.xconn); let input_method = inner.potential_input_methods.open_im( &xconn, Some(&|| { let _ = unsafe { set_instantiate_callback(&xconn, client_data) }; }), ); let is_fallback = input_method.is_fallback(); if let Some(input_method) = input_method.ok() { inner.is_fallback = is_fallback; unsafe { let result = set_destroy_callback(&xconn, input_method.im, &inner) .map_err(ImeCreationError::SetDestroyCallbackFailed); if result.is_err() { let _ = close_im(&xconn, input_method.im); } result?; } inner.im = Some(input_method); Ok(Ime { xconn, inner }) } else { Err(ImeCreationError::OpenFailure(Box::new( inner.potential_input_methods, ))) } } pub fn is_destroyed(&self) -> bool { self.inner.is_destroyed } // This pattern is used for various methods here: // Ok(_) indicates that nothing went wrong internally // Ok(true) indicates that the action was actually performed // Ok(false) indicates that the action is not presently applicable pub fn create_context( &mut self, window: ffi::Window, with_preedit: bool, ) -> Result { let context = if self.is_destroyed() { // Create empty entry in map, so that when IME is rebuilt, this window has a context. None } else { let im = self.inner.im.as_ref().unwrap(); let style = if with_preedit { im.preedit_style } else { im.none_style }; let context = unsafe { ImeContext::new( &self.inner.xconn, im.im, style, window, None, self.inner.event_sender.clone(), )? }; // Check the state on the context, since it could fail to enable or disable preedit. let event = if matches!(style, Style::None(_)) { if with_preedit { debug!("failed to create IME context with preedit support.") } ImeEvent::Disabled } else { if !with_preedit { debug!("failed to create IME context without preedit support.") } ImeEvent::Enabled }; self.inner .event_sender .send((window, event)) .expect("Failed to send enabled event"); Some(context) }; self.inner.contexts.insert(window, context); Ok(!self.is_destroyed()) } pub fn get_context(&self, window: ffi::Window) -> Option { if self.is_destroyed() { return None; } if let Some(Some(context)) = self.inner.contexts.get(&window) { Some(context.ic) } else { None } } pub fn remove_context(&mut self, window: ffi::Window) -> Result { if let Some(Some(context)) = self.inner.contexts.remove(&window) { unsafe { self.inner.destroy_ic_if_necessary(context.ic)?; } Ok(true) } else { Ok(false) } } pub fn focus(&mut self, window: ffi::Window) -> Result { if self.is_destroyed() { return Ok(false); } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.focus(&self.xconn).map(|_| true) } else { Ok(false) } } pub fn unfocus(&mut self, window: ffi::Window) -> Result { if self.is_destroyed() { return Ok(false); } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.unfocus(&self.xconn).map(|_| true) } else { Ok(false) } } pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) { if self.is_destroyed() { return; } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { context.set_spot(&self.xconn, x as _, y as _); } } pub fn set_ime_allowed(&mut self, window: ffi::Window, allowed: bool) { if self.is_destroyed() { return; } if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { if allowed == context.is_allowed() { return; } } // Remove context for that window. let _ = self.remove_context(window); // Create new context supporting IME input. let _ = self.create_context(window, allowed); } } impl Drop for Ime { fn drop(&mut self) { unsafe { let _ = self.inner.destroy_all_contexts_if_necessary(); let _ = self.inner.close_im_if_necessary(); } } } winit-0.29.15/src/platform_impl/linux/x11/mod.rs000064400000000000000000001057771046102023000175060ustar 00000000000000#![cfg(x11_platform)] use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet, VecDeque}; use std::ffi::CStr; use std::fmt; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::ops::Deref; use std::os::raw::*; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::sync::mpsc::{self, Receiver, Sender, TryRecvError}; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; use std::{ptr, slice, str}; pub use self::xdisplay::{XError, XNotSupported}; use calloop::generic::Generic; use calloop::EventLoop as Loop; use calloop::{ping::Ping, Readiness}; use libc::{setlocale, LC_CTYPE}; use log::warn; use x11rb::connection::RequestConnection; use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}; use x11rb::protocol::xinput::{self, ConnectionExt as _}; use x11rb::protocol::xkb; use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::x11_utils::X11Error as LogicalError; use x11rb::xcb_ffi::ReplyOrIdError; use super::{ControlFlow, OsError}; use crate::{ error::{EventLoopError, OsError as RootOsError}, event::{Event, StartCause, WindowEvent}, event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform::pump_events::PumpStatus, platform_impl::common::xkb::Context, platform_impl::{ platform::{min_timeout, WindowId}, PlatformSpecificWindowBuilderAttributes, }, window::WindowAttributes, }; mod activation; mod atoms; mod dnd; mod event_processor; pub mod ffi; mod ime; mod monitor; pub mod util; mod window; mod xdisplay; mod xsettings; use atoms::*; use dnd::{Dnd, DndState}; use event_processor::{EventProcessor, MAX_MOD_REPLAY_LEN}; use ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}; pub(crate) use monitor::{MonitorHandle, VideoMode}; use window::UnownedWindow; pub(crate) use xdisplay::XConnection; // Xinput constants not defined in x11rb const ALL_DEVICES: u16 = 0; const ALL_MASTER_DEVICES: u16 = 1; const ICONIC_STATE: u32 = 3; /// The underlying x11rb connection that we are using. type X11rbConnection = x11rb::xcb_ffi::XCBConnection; type X11Source = Generic>; struct WakeSender { sender: Sender, waker: Ping, } impl Clone for WakeSender { fn clone(&self) -> Self { Self { sender: self.sender.clone(), waker: self.waker.clone(), } } } impl WakeSender { pub fn send(&self, t: T) -> Result<(), EventLoopClosed> { let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0)); if res.is_ok() { self.waker.ping(); } res } } struct PeekableReceiver { recv: Receiver, first: Option, } impl PeekableReceiver { pub fn from_recv(recv: Receiver) -> Self { Self { recv, first: None } } pub fn has_incoming(&mut self) -> bool { if self.first.is_some() { return true; } match self.recv.try_recv() { Ok(v) => { self.first = Some(v); true } Err(TryRecvError::Empty) => false, Err(TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); false } } } pub fn try_recv(&mut self) -> Result { if let Some(first) = self.first.take() { return Ok(first); } self.recv.try_recv() } } pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: xproto::Atom, net_wm_ping: xproto::Atom, ime_sender: ImeSender, control_flow: Cell, exit: Cell>, root: xproto::Window, ime: Option>, windows: RefCell>>, redraw_sender: WakeSender, activation_sender: WakeSender, device_events: Cell, _marker: ::std::marker::PhantomData, } pub struct EventLoop { loop_running: bool, event_loop: Loop<'static, EventLoopState>, waker: calloop::ping::Ping, event_processor: EventProcessor, redraw_receiver: PeekableReceiver, user_receiver: PeekableReceiver, activation_receiver: PeekableReceiver, user_sender: Sender, /// The current state of the event loop. state: EventLoopState, } type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial); struct EventLoopState { /// The latest readiness state for the x11 file descriptor x11_readiness: Readiness, } pub struct EventLoopProxy { user_sender: WakeSender, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_sender: self.user_sender.clone(), } } } impl EventLoop { pub(crate) fn new(xconn: Arc) -> EventLoop { let root = xconn.default_root().root; let atoms = xconn.atoms(); let wm_delete_window = atoms[WM_DELETE_WINDOW]; let net_wm_ping = atoms[_NET_WM_PING]; let dnd = Dnd::new(Arc::clone(&xconn)) .expect("Failed to call XInternAtoms when initializing drag and drop"); let (ime_sender, ime_receiver) = mpsc::channel(); let (ime_event_sender, ime_event_receiver) = mpsc::channel(); // Input methods will open successfully without setting the locale, but it won't be // possible to actually commit pre-edit sequences. unsafe { // Remember default locale to restore it if target locale is unsupported // by Xlib let default_locale = setlocale(LC_CTYPE, ptr::null()); setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); // Check if set locale is supported by Xlib. // If not, calls to some Xlib functions like `XSetLocaleModifiers` // will fail. let locale_supported = (xconn.xlib.XSupportsLocale)() == 1; if !locale_supported { let unsupported_locale = setlocale(LC_CTYPE, ptr::null()); warn!( "Unsupported locale \"{}\". Restoring default locale \"{}\".", CStr::from_ptr(unsupported_locale).to_string_lossy(), CStr::from_ptr(default_locale).to_string_lossy() ); // Restore default locale setlocale(LC_CTYPE, default_locale); } } let ime = Ime::new(Arc::clone(&xconn), ime_event_sender); if let Err(ImeCreationError::OpenFailure(state)) = ime.as_ref() { warn!("Failed to open input method: {state:#?}"); } else if let Err(err) = ime.as_ref() { warn!("Failed to set input method destruction callback: {err:?}"); } let ime = ime.ok().map(RefCell::new); let randr_event_offset = xconn .select_xrandr_input(root) .expect("Failed to query XRandR extension"); let xi2ext = xconn .xcb_connection() .extension_information(xinput::X11_EXTENSION_NAME) .expect("Failed to query XInput extension") .expect("X server missing XInput extension"); let xkbext = xconn .xcb_connection() .extension_information(xkb::X11_EXTENSION_NAME) .expect("Failed to query XKB extension") .expect("X server missing XKB extension"); // Check for XInput2 support. xconn .xcb_connection() .xinput_xi_query_version(2, 3) .expect("Failed to send XInput2 query version request") .reply() .expect("Error while checking for XInput2 query version reply"); xconn.update_cached_wm_info(root); // Create an event loop. let event_loop = Loop::::try_new().expect("Failed to initialize the event loop"); let handle = event_loop.handle(); // Create the X11 event dispatcher. let source = X11Source::new( // SAFETY: xcb owns the FD and outlives the source. unsafe { BorrowedFd::borrow_raw(xconn.xcb_connection().as_raw_fd()) }, calloop::Interest::READ, calloop::Mode::Level, ); handle .insert_source(source, |readiness, _, state| { state.x11_readiness = readiness; Ok(calloop::PostAction::Continue) }) .expect("Failed to register the X11 event dispatcher"); let (waker, waker_source) = calloop::ping::make_ping().expect("Failed to create event loop waker"); event_loop .handle() .insert_source(waker_source, move |_, _, _| { // No extra handling is required, we just need to wake-up. }) .expect("Failed to register the event loop waker source"); // Create a channel for handling redraw requests. let (redraw_sender, redraw_channel) = mpsc::channel(); // Create a channel for sending activation tokens. let (activation_token_sender, activation_token_channel) = mpsc::channel(); // Create a channel for sending user events. let (user_sender, user_channel) = mpsc::channel(); let xkb_context = Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); let mut xmodmap = util::ModifierKeymap::new(); xmodmap.reload_from_x_connection(&xconn); let window_target = EventLoopWindowTarget { ime, root, control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), windows: Default::default(), _marker: ::std::marker::PhantomData, ime_sender, xconn, wm_delete_window, net_wm_ping, redraw_sender: WakeSender { sender: redraw_sender, // not used again so no clone waker: waker.clone(), }, activation_sender: WakeSender { sender: activation_token_sender, // not used again so no clone waker: waker.clone(), }, device_events: Default::default(), }; // Set initial device event filter. window_target.update_listen_device_events(true); let root_window_target = RootELW { p: super::EventLoopWindowTarget::X(window_target), _marker: PhantomData, }; let event_processor = EventProcessor { target: root_window_target, dnd, devices: Default::default(), randr_event_offset, ime_receiver, ime_event_receiver, xi2ext, xfiltered_modifiers: VecDeque::with_capacity(MAX_MOD_REPLAY_LEN), xmodmap, xkbext, xkb_context, num_touch: 0, held_key_press: None, first_touch: None, active_window: None, modifiers: Default::default(), is_composing: false, }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) let xconn = &EventProcessor::window_target(&event_processor.target).xconn; xconn .select_xinput_events( root, ALL_DEVICES, x11rb::protocol::xinput::XIEventMask::HIERARCHY, ) .expect_then_ignore_error("Failed to register for XInput2 device hotplug events"); xconn .select_xkb_events( 0x100, // Use the "core keyboard device" xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::MAP_NOTIFY | xkb::EventType::STATE_NOTIFY, ) .unwrap(); event_processor.init_device(ALL_DEVICES); EventLoop { loop_running: false, event_loop, waker, event_processor, redraw_receiver: PeekableReceiver::from_recv(redraw_channel), activation_receiver: PeekableReceiver::from_recv(activation_token_channel), user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, state: EventLoopState { x11_readiness: Readiness::EMPTY, }, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: WakeSender { sender: self.user_sender.clone(), waker: self.waker.clone(), }, } } pub(crate) fn window_target(&self) -> &RootELW { &self.event_processor.target } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootELW), { if self.loop_running { return Err(EventLoopError::AlreadyRunning); } let exit = loop { match self.pump_events(None, &mut event_handler) { PumpStatus::Exit(0) => { break Ok(()); } PumpStatus::Exit(code) => { break Err(EventLoopError::ExitFailure(code)); } _ => { continue; } } }; // Applications aren't allowed to carry windows between separate // `run_on_demand` calls but if they have only just dropped their // windows we need to make sure those last requests are sent to the // X Server. let wt = EventProcessor::window_target(&self.event_processor.target); wt.x_connection().sync_with_server().map_err(|x_err| { EventLoopError::Os(os_error!(OsError::XError(Arc::new(X11Error::Xlib(x_err))))) })?; exit } pub fn pump_events(&mut self, timeout: Option, mut callback: F) -> PumpStatus where F: FnMut(Event, &RootELW), { if !self.loop_running { self.loop_running = true; // run the initial loop iteration self.single_iteration(&mut callback, StartCause::Init); } // Consider the possibility that the `StartCause::Init` iteration could // request to Exit. if !self.exiting() { self.poll_events_with_timeout(timeout, &mut callback); } if let Some(code) = self.exit_code() { self.loop_running = false; callback(Event::LoopExiting, self.window_target()); PumpStatus::Exit(code) } else { PumpStatus::Continue } } fn has_pending(&mut self) -> bool { self.event_processor.poll() || self.user_receiver.has_incoming() || self.redraw_receiver.has_incoming() } pub fn poll_events_with_timeout(&mut self, mut timeout: Option, mut callback: F) where F: FnMut(Event, &RootELW), { let start = Instant::now(); let has_pending = self.has_pending(); timeout = if has_pending { // If we already have work to do then we don't want to block on the next poll. Some(Duration::ZERO) } else { let control_flow_timeout = match self.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { Some(wait_deadline.saturating_duration_since(start)) } }; min_timeout(control_flow_timeout, timeout) }; self.state.x11_readiness = Readiness::EMPTY; if let Err(error) = self .event_loop .dispatch(timeout, &mut self.state) .map_err(std::io::Error::from) { log::error!("Failed to poll for events: {error:?}"); let exit_code = error.raw_os_error().unwrap_or(1); self.set_exit_code(exit_code); return; } // NB: `StartCause::Init` is handled as a special case and doesn't need // to be considered here let cause = match self.control_flow() { ControlFlow::Poll => StartCause::Poll, ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None, }, ControlFlow::WaitUntil(deadline) => { if Instant::now() < deadline { StartCause::WaitCancelled { start, requested_resume: Some(deadline), } } else { StartCause::ResumeTimeReached { start, requested_resume: deadline, } } } }; // False positive / spurious wake ups could lead to us spamming // redundant iterations of the event loop with no new events to // dispatch. // // If there's no readable event source then we just double check if we // have any pending `_receiver` events and if not we return without // running a loop iteration. // If we don't have any pending `_receiver` if !self.has_pending() && !matches!( &cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll ) { return; } self.single_iteration(&mut callback, cause); } fn single_iteration(&mut self, callback: &mut F, cause: StartCause) where F: FnMut(Event, &RootELW), { callback(Event::NewEvents(cause), &self.event_processor.target); // NB: For consistency all platforms must emit a 'resumed' event even though X11 // applications don't themselves have a formal suspend/resume lifecycle. if cause == StartCause::Init { callback(Event::Resumed, &self.event_processor.target); } // Process all pending events self.drain_events(callback); // Empty activation tokens. while let Ok((window_id, serial)) = self.activation_receiver.try_recv() { let token = self .event_processor .with_window(window_id.0 as xproto::Window, |window| { window.generate_activation_token() }); match token { Some(Ok(token)) => { let event = Event::WindowEvent { window_id: crate::window::WindowId(window_id), event: WindowEvent::ActivationTokenDone { serial, token: crate::window::ActivationToken::_new(token), }, }; callback(event, &self.event_processor.target) } Some(Err(e)) => { log::error!("Failed to get activation token: {}", e); } None => {} } } // Empty the user event buffer { while let Ok(event) = self.user_receiver.try_recv() { callback(Event::UserEvent(event), &self.event_processor.target); } } // Empty the redraw requests { let mut windows = HashSet::new(); while let Ok(window_id) = self.redraw_receiver.try_recv() { windows.insert(window_id); } for window_id in windows { let window_id = crate::window::WindowId(window_id); callback( Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }, &self.event_processor.target, ); } } // This is always the last event we dispatch before poll again { callback(Event::AboutToWait, &self.event_processor.target); } } fn drain_events(&mut self, callback: &mut F) where F: FnMut(Event, &RootELW), { let mut xev = MaybeUninit::uninit(); while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { let mut xev = unsafe { xev.assume_init() }; self.event_processor .process_event(&mut xev, |window_target, event| { if let Event::WindowEvent { window_id: crate::window::WindowId(wid), event: WindowEvent::RedrawRequested, } = event { let window_target = EventProcessor::window_target(window_target); window_target.redraw_sender.send(wid).unwrap(); } else { callback(event, window_target); } }); } } fn control_flow(&self) -> ControlFlow { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.control_flow() } fn exiting(&self) -> bool { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.exiting() } fn set_exit_code(&self, code: i32) { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.set_exit_code(code); } fn exit_code(&self) -> Option { let window_target = EventProcessor::window_target(&self.event_processor.target); window_target.exit_code() } } impl AsFd for EventLoop { fn as_fd(&self) -> BorrowedFd<'_> { self.event_loop.as_fd() } } impl AsRawFd for EventLoop { fn as_raw_fd(&self) -> RawFd { self.event_loop.as_raw_fd() } } impl EventLoopWindowTarget { /// Returns the `XConnection` of this events loop. #[inline] pub(crate) fn x_connection(&self) -> &Arc { &self.xconn } pub fn available_monitors(&self) -> impl Iterator { self.xconn.available_monitors().into_iter().flatten() } pub fn primary_monitor(&self) -> Option { self.xconn.primary_monitor().ok() } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.device_events.set(allowed); } /// Update the device event based on window focus. pub fn update_listen_device_events(&self, focus: bool) { let device_events = self.device_events.get() == DeviceEvents::Always || (focus && self.device_events.get() == DeviceEvents::WhenFocused); let mut mask = xinput::XIEventMask::from(0u32); if device_events { mask = xinput::XIEventMask::RAW_MOTION | xinput::XIEventMask::RAW_BUTTON_PRESS | xinput::XIEventMask::RAW_BUTTON_RELEASE | xinput::XIEventMask::RAW_KEY_PRESS | xinput::XIEventMask::RAW_KEY_RELEASE; } self.xconn .select_xinput_events(self.root, ALL_MASTER_DEVICES, mask) .expect_then_ignore_error("Failed to update device event filter"); } #[cfg(feature = "rwh_05")] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::XlibDisplayHandle::empty(); display_handle.display = self.xconn.display as *mut _; display_handle.screen = self.xconn.default_screen_index() as c_int; display_handle.into() } #[cfg(feature = "rwh_06")] pub fn raw_display_handle_rwh_06( &self, ) -> Result { let display_handle = rwh_06::XlibDisplayHandle::new( // SAFETY: display will never be null Some( std::ptr::NonNull::new(self.xconn.display as *mut _) .expect("X11 display should never be null"), ), self.xconn.default_screen_index() as c_int, ); Ok(display_handle.into()) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(Some(0)) } pub(crate) fn clear_exit(&self) { self.exit.set(None) } pub(crate) fn exiting(&self) -> bool { self.exit.get().is_some() } pub(crate) fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub(crate) fn exit_code(&self) -> Option { self.exit.get() } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_sender .send(event) .map_err(|e| EventLoopClosed(e.0)) } } struct DeviceInfo<'a> { xconn: &'a XConnection, info: *const ffi::XIDeviceInfo, count: usize, } impl<'a> DeviceInfo<'a> { fn get(xconn: &'a XConnection, device: c_int) -> Option { unsafe { let mut count = 0; let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count); xconn.check_errors().ok()?; if info.is_null() || count == 0 { None } else { Some(DeviceInfo { xconn, info, count: count as usize, }) } } } } impl<'a> Drop for DeviceInfo<'a> { fn drop(&mut self) { assert!(!self.info.is_null()); unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; } } impl<'a> Deref for DeviceInfo<'a> { type Target = [ffi::XIDeviceInfo]; fn deref(&self) -> &Self::Target { unsafe { slice::from_raw_parts(self.info, self.count) } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(xinput::DeviceId); impl DeviceId { #[allow(unused)] pub const unsafe fn dummy() -> Self { DeviceId(0) } } pub(crate) struct Window(Arc); impl Deref for Window { type Target = UnownedWindow; #[inline] fn deref(&self) -> &UnownedWindow { &self.0 } } impl Window { pub(crate) fn new( event_loop: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let window = Arc::new(UnownedWindow::new(event_loop, attribs, pl_attribs)?); event_loop .windows .borrow_mut() .insert(window.id(), Arc::downgrade(&window)); Ok(Window(window)) } } impl Drop for Window { fn drop(&mut self) { let window = self.deref(); let xconn = &window.xconn; if let Ok(c) = xconn .xcb_connection() .destroy_window(window.id().0 as xproto::Window) { c.ignore_error(); } } } /// Generic sum error type for X11 errors. #[derive(Debug)] pub enum X11Error { /// An error from the Xlib library. Xlib(XError), /// An error that occurred while trying to connect to the X server. Connect(ConnectError), /// An error that occurred over the connection medium. Connection(ConnectionError), /// An error that occurred logically on the X11 end. X11(LogicalError), /// The XID range has been exhausted. XidsExhausted(IdsExhausted), /// Got `null` from an Xlib function without a reason. UnexpectedNull(&'static str), /// Got an invalid activation token. InvalidActivationToken(Vec), /// An extension that we rely on is not available. MissingExtension(&'static str), /// Could not find a matching X11 visual for this visualid NoSuchVisual(xproto::Visualid), /// Unable to parse xsettings. XsettingsParse(xsettings::ParserError), /// Failed to get property. GetProperty(util::GetPropertyError), } impl fmt::Display for X11Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { X11Error::Xlib(e) => write!(f, "Xlib error: {}", e), X11Error::Connect(e) => write!(f, "X11 connection error: {}", e), X11Error::Connection(e) => write!(f, "X11 connection error: {}", e), X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e), X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e), X11Error::X11(e) => write!(f, "X11 error: {:?}", e), X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s), X11Error::InvalidActivationToken(s) => write!( f, "Invalid activation token: {}", std::str::from_utf8(s).unwrap_or("") ), X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s), X11Error::NoSuchVisual(visualid) => { write!( f, "Could not find a matching X11 visual for ID `{:x}`", visualid ) } X11Error::XsettingsParse(err) => { write!(f, "Failed to parse xsettings: {:?}", err) } } } } impl std::error::Error for X11Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { X11Error::Xlib(e) => Some(e), X11Error::Connect(e) => Some(e), X11Error::Connection(e) => Some(e), X11Error::XidsExhausted(e) => Some(e), _ => None, } } } impl From for X11Error { fn from(e: XError) -> Self { X11Error::Xlib(e) } } impl From for X11Error { fn from(e: ConnectError) -> Self { X11Error::Connect(e) } } impl From for X11Error { fn from(e: ConnectionError) -> Self { X11Error::Connection(e) } } impl From for X11Error { fn from(e: LogicalError) -> Self { X11Error::X11(e) } } impl From for X11Error { fn from(value: ReplyError) -> Self { match value { ReplyError::ConnectionError(e) => e.into(), ReplyError::X11Error(e) => e.into(), } } } impl From for X11Error { fn from(value: ime::ImeContextCreationError) -> Self { match value { ime::ImeContextCreationError::XError(e) => e.into(), ime::ImeContextCreationError::Null => Self::UnexpectedNull("XOpenIM"), } } } impl From for X11Error { fn from(value: ReplyOrIdError) -> Self { match value { ReplyOrIdError::ConnectionError(e) => e.into(), ReplyOrIdError::X11Error(e) => e.into(), ReplyOrIdError::IdsExhausted => Self::XidsExhausted(IdsExhausted), } } } impl From for X11Error { fn from(value: xsettings::ParserError) -> Self { Self::XsettingsParse(value) } } impl From for X11Error { fn from(value: util::GetPropertyError) -> Self { Self::GetProperty(value) } } /// Type alias for a void cookie. type VoidCookie<'a> = x11rb::cookie::VoidCookie<'a, X11rbConnection>; /// Extension trait for `Result`. trait CookieResultExt { /// Unwrap the send error and ignore the result. fn expect_then_ignore_error(self, msg: &str); } impl<'a, E: fmt::Debug> CookieResultExt for Result, E> { fn expect_then_ignore_error(self, msg: &str) { self.expect(msg).ignore_error() } } fn mkwid(w: xproto::Window) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) } fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] pub struct Device { _name: String, scroll_axes: Vec<(i32, ScrollAxis)>, // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. attachment: c_int, } #[derive(Debug, Copy, Clone)] struct ScrollAxis { increment: f64, orientation: ScrollOrientation, position: f64, } #[derive(Debug, Copy, Clone)] enum ScrollOrientation { Vertical, Horizontal, } impl Device { fn new(info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); if Device::physical_device(info) { // Identify scroll axes for &class_ptr in Device::classes(info) { let ty = unsafe { (*class_ptr)._type }; if ty == ffi::XIScrollClass { let info = unsafe { &*(class_ptr as *const ffi::XIScrollClassInfo) }; scroll_axes.push(( info.number, ScrollAxis { increment: info.increment, orientation: match info.scroll_type { ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, _ => unreachable!(), }, position: 0.0, }, )); } } } let mut device = Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment, }; device.reset_scroll_position(info); device } fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) { if Device::physical_device(info) { for &class_ptr in Device::classes(info) { let ty = unsafe { (*class_ptr)._type }; if ty == ffi::XIValuatorClass { let info = unsafe { &*(class_ptr as *const ffi::XIValuatorClassInfo) }; if let Some(&mut (_, ref mut axis)) = self .scroll_axes .iter_mut() .find(|&&mut (axis, _)| axis == info.number) { axis.position = info.value; } } } } } #[inline] fn physical_device(info: &ffi::XIDeviceInfo) -> bool { info._use == ffi::XISlaveKeyboard || info._use == ffi::XISlavePointer || info._use == ffi::XIFloatingSlave } #[inline] fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] { unsafe { slice::from_raw_parts( info.classes as *const *const ffi::XIAnyClassInfo, info.num_classes as usize, ) } } } /// Convert the raw X11 representation for a 32-bit floating point to a double. #[inline] fn xinput_fp1616_to_float(fp: xinput::Fp1616) -> f64 { (fp as f64) / ((1 << 16) as f64) } winit-0.29.15/src/platform_impl/linux/x11/monitor.rs000064400000000000000000000252641046102023000204060ustar 00000000000000use super::{util, X11Error, XConnection}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, platform_impl::VideoMode as PlatformVideoMode, }; use x11rb::{ connection::RequestConnection, protocol::{ randr::{self, ConnectionExt as _}, xproto, }, }; // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; impl XConnection { pub fn invalidate_cached_monitor_list(&self) -> Option> { // We update this lazily. self.monitor_handles.lock().unwrap().take() } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) native_mode: randr::Mode, pub(crate) monitor: Option, } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { self.size.into() } #[inline] pub fn bit_depth(&self) -> u16 { self.bit_depth } #[inline] pub fn refresh_rate_millihertz(&self) -> u32 { self.refresh_rate_millihertz } #[inline] pub fn monitor(&self) -> MonitorHandle { self.monitor.clone().unwrap() } } #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id pub(crate) id: randr::Crtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor dimensions: (u32, u32), /// The position of the monitor in the X screen position: (i32, i32), /// If the monitor is the primary one primary: bool, /// The refresh rate used by monitor. refresh_rate_millihertz: Option, /// The DPI scale factor pub(crate) scale_factor: f64, /// Used to determine which windows are on this monitor pub(crate) rect: util::AaRect, /// Supported video modes on this monitor video_modes: Vec, } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.id == other.id } } 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 { self.id.cmp(&other.id) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { self.id.hash(state); } } #[inline] pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option { if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 { #[allow(clippy::unnecessary_cast)] Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32) } else { None } } impl MonitorHandle { fn new( xconn: &XConnection, resources: &ScreenResources, id: randr::Crtc, crtc: &randr::GetCrtcInfoReply, primary: bool, ) -> Option { let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?; let dimensions = (crtc.width as u32, crtc.height as u32); let position = (crtc.x as i32, crtc.y as i32); // Get the refresh rate of the current video mode. let current_mode = crtc.mode; let screen_modes = resources.modes(); let refresh_rate_millihertz = screen_modes .iter() .find(|mode| mode.id == current_mode) .and_then(mode_refresh_rate_millihertz); let rect = util::AaRect::new(position, dimensions); Some(MonitorHandle { id, name, refresh_rate_millihertz, scale_factor, dimensions, position, primary, rect, video_modes, }) } pub fn dummy() -> Self { MonitorHandle { id: 0, name: "".into(), scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), refresh_rate_millihertz: None, primary: true, rect: util::AaRect::new((0, 0), (1, 1)), video_modes: Vec::new(), } } pub(crate) fn is_dummy(&self) -> bool { // Zero is an invalid XID value; no real monitor will have it self.id == 0 } pub fn name(&self) -> Option { Some(self.name.clone()) } #[inline] pub fn native_identifier(&self) -> u32 { self.id as _ } pub fn size(&self) -> PhysicalSize { self.dimensions.into() } pub fn position(&self) -> PhysicalPosition { self.position.into() } pub fn refresh_rate_millihertz(&self) -> Option { self.refresh_rate_millihertz } #[inline] pub fn scale_factor(&self) -> f64 { self.scale_factor } #[inline] pub fn video_modes(&self) -> impl Iterator { let monitor = self.clone(); self.video_modes.clone().into_iter().map(move |mut x| { x.monitor = Some(monitor.clone()); PlatformVideoMode::X(x) }) } } impl XConnection { pub fn get_monitor_for_window( &self, window_rect: Option, ) -> Result { let monitors = self.available_monitors()?; if monitors.is_empty() { // Return a dummy monitor to avoid panicking return Ok(MonitorHandle::dummy()); } let default = monitors.first().unwrap(); let window_rect = match window_rect { Some(rect) => rect, None => return Ok(default.to_owned()), }; let mut largest_overlap = 0; let mut matched_monitor = default; for monitor in &monitors { let overlapping_area = window_rect.get_overlapping_area(&monitor.rect); if overlapping_area > largest_overlap { largest_overlap = overlapping_area; matched_monitor = monitor; } } Ok(matched_monitor.to_owned()) } fn query_monitor_list(&self) -> Result, X11Error> { let root = self.default_root(); let resources = ScreenResources::from_connection(self.xcb_connection(), root, self.randr_version())?; // Pipeline all of the get-crtc requests. let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len()); for &crtc in resources.crtcs() { crtc_cookies.push( self.xcb_connection() .randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?, ); } // Do this here so we do all of our requests in one shot. let primary = self .xcb_connection() .randr_get_output_primary(root.root)? .reply()? .output; let mut crtc_infos = Vec::with_capacity(crtc_cookies.len()); for cookie in crtc_cookies { let reply = cookie.reply()?; crtc_infos.push(reply); } let mut has_primary = false; let mut available_monitors = Vec::with_capacity(resources.crtcs().len()); for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) { if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() { continue; } let is_primary = crtc.outputs[0] == primary; has_primary |= is_primary; let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary); available_monitors.extend(monitor); } // If we don't have a primary monitor, just pick one ourselves! if !has_primary { if let Some(ref mut fallback) = available_monitors.first_mut() { // Setting this here will come in handy if we ever add an `is_primary` method. fallback.primary = true; } } Ok(available_monitors) } pub fn available_monitors(&self) -> Result, X11Error> { let mut monitors_lock = self.monitor_handles.lock().unwrap(); match *monitors_lock { Some(ref monitors) => Ok(monitors.clone()), None => { let monitors = self.query_monitor_list()?; if !DISABLE_MONITOR_LIST_CACHING { *monitors_lock = Some(monitors.clone()); } Ok(monitors) } } } #[inline] pub fn primary_monitor(&self) -> Result { Ok(self .available_monitors()? .into_iter() .find(|monitor| monitor.primary) .unwrap_or_else(MonitorHandle::dummy)) } pub fn select_xrandr_input(&self, root: xproto::Window) -> Result { use randr::NotifyMask; // Get extension info. let info = self .xcb_connection() .extension_information(randr::X11_EXTENSION_NAME)? .ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?; // Select input data. let event_mask = NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE; self.xcb_connection().randr_select_input(root, event_mask)?; Ok(info.first_event) } } pub struct ScreenResources { /// List of attached modes. modes: Vec, /// List of attached CRTCs. crtcs: Vec, } impl ScreenResources { pub(crate) fn modes(&self) -> &[randr::ModeInfo] { &self.modes } pub(crate) fn crtcs(&self) -> &[randr::Crtc] { &self.crtcs } pub(crate) fn from_connection( conn: &impl x11rb::connection::Connection, root: &x11rb::protocol::xproto::Screen, (major_version, minor_version): (u32, u32), ) -> Result { if (major_version == 1 && minor_version >= 3) || major_version > 1 { let reply = conn .randr_get_screen_resources_current(root.root)? .reply()?; Ok(Self::from_get_screen_resources_current_reply(reply)) } else { let reply = conn.randr_get_screen_resources(root.root)?.reply()?; Ok(Self::from_get_screen_resources_reply(reply)) } } pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self { Self { modes: reply.modes, crtcs: reply.crtcs, } } pub(crate) fn from_get_screen_resources_current_reply( reply: randr::GetScreenResourcesCurrentReply, ) -> Self { Self { modes: reply.modes, crtcs: reply.crtcs, } } } winit-0.29.15/src/platform_impl/linux/x11/tests/xsettings.dat000064400000000000000000000152701046102023000222310ustar 000000000000000x6c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x0b,0x00,0x58,0x66,0x74,0x2f,0x48,0x69,0x6e,0x74,0x69,0x6e,0x67,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x47,0x74,0x6b,0x2f,0x44,0x69,0x61,0x6c,0x6f,0x67,0x73,0x55,0x73,0x65,0x48,0x65,0x61,0x64,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0c,0x00,0x47,0x74,0x6b,0x2f,0x46,0x6f,0x6e,0x74,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x4e,0x6f,0x74,0x6f,0x20,0x53,0x61,0x6e,0x73,0x20,0x39,0x00,0x01,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x4c,0x63,0x64,0x66,0x69,0x6c,0x74,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x6c,0x63,0x64,0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x4b,0x65,0x79,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x48,0x69,0x6e,0x74,0x53,0x74,0x79,0x6c,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,0x68,0x69,0x6e,0x74,0x73,0x6c,0x69,0x67,0x68,0x74,0x00,0x00,0x01,0x00,0x11,0x00,0x4e,0x65,0x74,0x2f,0x49,0x63,0x6f,0x6e,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x65,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x61,0x72,0x79,0x2d,0x78,0x66,0x63,0x65,0x2d,0x64,0x61,0x72,0x6b,0x00,0x00,0x0d,0x00,0x58,0x66,0x74,0x2f,0x41,0x6e,0x74,0x69,0x61,0x6c,0x69,0x61,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x08,0x00,0x58,0x66,0x74,0x2f,0x52,0x47,0x42,0x41,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x72,0x67,0x62,0x00,0x00,0x00,0x13,0x00,0x4e,0x65,0x74,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x42,0x6c,0x69,0x6e,0x6b,0x54,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0xb0,0x04,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x54,0x68,0x65,0x6d,0x65,0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x01,0x00,0x15,0x00,0x4e,0x65,0x74,0x2f,0x46,0x61,0x6c,0x6c,0x62,0x61,0x63,0x6b,0x49,0x63,0x6f,0x6e,0x54,0x68,0x65,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x67,0x6e,0x6f,0x6d,0x65,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x54,0x6f,0x6f,0x6c,0x62,0x61,0x72,0x53,0x74,0x79,0x6c,0x65,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x69,0x63,0x6f,0x6e,0x73,0x00,0x00,0x00,0x01,0x00,0x12,0x00,0x4e,0x65,0x74,0x2f,0x53,0x6f,0x75,0x6e,0x64,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x64,0x65,0x66,0x61,0x75,0x6c,0x74,0x00,0x00,0x00,0x15,0x00,0x4e,0x65,0x74,0x2f,0x45,0x6e,0x61,0x62,0x6c,0x65,0x45,0x76,0x65,0x6e,0x74,0x53,0x6f,0x75,0x6e,0x64,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x4e,0x65,0x74,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x42,0x6c,0x69,0x6e,0x6b,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x43,0x6f,0x6c,0x6f,0x72,0x50,0x61,0x6c,0x65,0x74,0x74,0x65,0x00,0x00,0x00,0x00,0x94,0x00,0x00,0x00,0x62,0x6c,0x61,0x63,0x6b,0x3a,0x77,0x68,0x69,0x74,0x65,0x3a,0x67,0x72,0x61,0x79,0x35,0x30,0x3a,0x72,0x65,0x64,0x3a,0x70,0x75,0x72,0x70,0x6c,0x65,0x3a,0x62,0x6c,0x75,0x65,0x3a,0x6c,0x69,0x67,0x68,0x74,0x20,0x62,0x6c,0x75,0x65,0x3a,0x67,0x72,0x65,0x65,0x6e,0x3a,0x79,0x65,0x6c,0x6c,0x6f,0x77,0x3a,0x6f,0x72,0x61,0x6e,0x67,0x65,0x3a,0x6c,0x61,0x76,0x65,0x6e,0x64,0x65,0x72,0x3a,0x62,0x72,0x6f,0x77,0x6e,0x3a,0x67,0x6f,0x6c,0x64,0x65,0x6e,0x72,0x6f,0x64,0x34,0x3a,0x64,0x6f,0x64,0x67,0x65,0x72,0x20,0x62,0x6c,0x75,0x65,0x3a,0x70,0x69,0x6e,0x6b,0x3a,0x6c,0x69,0x67,0x68,0x74,0x20,0x67,0x72,0x65,0x65,0x6e,0x3a,0x67,0x72,0x61,0x79,0x31,0x30,0x3a,0x67,0x72,0x61,0x79,0x33,0x30,0x3a,0x67,0x72,0x61,0x79,0x37,0x35,0x3a,0x67,0x72,0x61,0x79,0x39,0x30,0x00,0x00,0x13,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6f,0x75,0x62,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x54,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x61,0x6e,0x43,0x68,0x61,0x6e,0x67,0x65,0x41,0x63,0x63,0x65,0x6c,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x65,0x6e,0x75,0x42,0x61,0x72,0x41,0x63,0x63,0x65,0x6c,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x46,0x31,0x30,0x00,0x01,0x00,0x0d,0x00,0x4e,0x65,0x74,0x2f,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x47,0x72,0x65,0x79,0x62,0x69,0x72,0x64,0x01,0x00,0x17,0x00,0x47,0x74,0x6b,0x2f,0x54,0x69,0x74,0x6c,0x65,0x62,0x61,0x72,0x4d,0x69,0x64,0x64,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x6c,0x6f,0x77,0x65,0x72,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x47,0x74,0x6b,0x2f,0x42,0x75,0x74,0x74,0x6f,0x6e,0x49,0x6d,0x61,0x67,0x65,0x73,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6f,0x75,0x62,0x6c,0x65,0x43,0x6c,0x69,0x63,0x6b,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0x15,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x6f,0x6e,0x6f,0x73,0x70,0x61,0x63,0x65,0x46,0x6f,0x6e,0x74,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x4d,0x6f,0x6e,0x6f,0x73,0x70,0x61,0x63,0x65,0x20,0x31,0x30,0x00,0x00,0x07,0x00,0x58,0x66,0x74,0x2f,0x44,0x50,0x49,0x00,0x02,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x01,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x43,0x75,0x72,0x73,0x6f,0x72,0x54,0x68,0x65,0x6d,0x65,0x4e,0x61,0x6d,0x65,0x00,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x44,0x4d,0x5a,0x2d,0x57,0x68,0x69,0x74,0x65,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x47,0x74,0x6b,0x2f,0x54,0x6f,0x6f,0x6c,0x62,0x61,0x72,0x49,0x63,0x6f,0x6e,0x53,0x69,0x7a,0x65,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x14,0x00,0x4e,0x65,0x74,0x2f,0x44,0x6e,0x64,0x44,0x72,0x61,0x67,0x54,0x68,0x72,0x65,0x73,0x68,0x6f,0x6c,0x64,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x01,0x00,0x14,0x00,0x47,0x74,0x6b,0x2f,0x44,0x65,0x63,0x6f,0x72,0x61,0x74,0x69,0x6f,0x6e,0x4c,0x61,0x79,0x6f,0x75,0x74,0x00,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x6d,0x65,0x6e,0x75,0x3a,0x6d,0x69,0x6e,0x69,0x6d,0x69,0x7a,0x65,0x2c,0x6d,0x61,0x78,0x69,0x6d,0x69,0x7a,0x65,0x2c,0x63,0x6c,0x6f,0x73,0x65,0x00,0x00,0x1d,0x00,0x4e,0x65,0x74,0x2f,0x45,0x6e,0x61,0x62,0x6c,0x65,0x49,0x6e,0x70,0x75,0x74,0x46,0x65,0x65,0x64,0x62,0x61,0x63,0x6b,0x53,0x6f,0x75,0x6e,0x64,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x47,0x64,0x6b,0x2f,0x57,0x69,0x6e,0x64,0x6f,0x77,0x53,0x63,0x61,0x6c,0x69,0x6e,0x67,0x46,0x61,0x63,0x74,0x6f,0x72,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x0d,0x00,0x47,0x74,0x6b,0x2f,0x49,0x63,0x6f,0x6e,0x53,0x69,0x7a,0x65,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x67,0x74,0x6b,0x2d,0x62,0x75,0x74,0x74,0x6f,0x6e,0x3d,0x31,0x36,0x2c,0x31,0x36,0x00,0x00,0x0e,0x00,0x47,0x74,0x6b,0x2f,0x4d,0x65,0x6e,0x75,0x49,0x6d,0x61,0x67,0x65,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00 winit-0.29.15/src/platform_impl/linux/x11/util/client_msg.rs000064400000000000000000000017071046102023000220140ustar 00000000000000use super::*; use x11rb::x11_utils::Serialize; impl XConnection { pub fn send_client_msg( &self, window: xproto::Window, // The window this is "about"; not necessarily this window target_window: xproto::Window, // The window we're sending to message_type: xproto::Atom, event_mask: Option, data: impl Into, ) -> Result, X11Error> { let event = xproto::ClientMessageEvent { response_type: xproto::CLIENT_MESSAGE_EVENT, window, format: 32, data: data.into(), sequence: 0, type_: message_type, }; self.xcb_connection() .send_event( false, target_window, event_mask.unwrap_or(xproto::EventMask::NO_EVENT), event.serialize(), ) .map_err(Into::into) } } winit-0.29.15/src/platform_impl/linux/x11/util/cookie.rs000064400000000000000000000027541046102023000211440ustar 00000000000000use std::ffi::c_int; use std::sync::Arc; use x11_dl::xlib::{self, XEvent, XGenericEventCookie}; use crate::platform_impl::x11::XConnection; /// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. /// This is a wrapper to extract the cookie from a GenericEvent XEvent and release the cookie data /// once it has been processed pub struct GenericEventCookie { cookie: XGenericEventCookie, xconn: Arc, } impl GenericEventCookie { pub fn from_event(xconn: Arc, event: XEvent) -> Option { unsafe { let mut cookie: XGenericEventCookie = From::from(event); if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True { Some(GenericEventCookie { cookie, xconn }) } else { None } } } #[inline] pub fn extension(&self) -> u8 { self.cookie.extension as u8 } #[inline] pub fn evtype(&self) -> c_int { self.cookie.evtype } /// Borrow inner event data as `&T`. /// /// ## SAFETY /// /// The caller must ensure that the event has the `T` inside of it. #[inline] pub unsafe fn as_event(&self) -> &T { unsafe { &*(self.cookie.data as *const _) } } } impl Drop for GenericEventCookie { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); } } } winit-0.29.15/src/platform_impl/linux/x11/util/cursor.rs000064400000000000000000000050271046102023000212040ustar 00000000000000use std::ffi::CString; use std::iter; use x11rb::connection::Connection; use crate::window::CursorIcon; use super::*; impl XConnection { pub fn set_cursor_icon(&self, window: xproto::Window, cursor: Option) { let cursor = *self .cursor_cache .lock() .unwrap() .entry(cursor) .or_insert_with(|| self.get_cursor(cursor)); self.update_cursor(window, cursor) .expect("Failed to set cursor"); } fn create_empty_cursor(&self) -> ffi::Cursor { let data = 0; let pixmap = unsafe { let screen = (self.xlib.XDefaultScreen)(self.display); let window = (self.xlib.XRootWindow)(self.display, screen); (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) }; if pixmap == 0 { panic!("failed to allocate pixmap for cursor"); } unsafe { // We don't care about this color, since it only fills bytes // in the pixmap which are not 0 in the mask. let mut dummy_color = MaybeUninit::uninit(); let cursor = (self.xlib.XCreatePixmapCursor)( self.display, pixmap, pixmap, dummy_color.as_mut_ptr(), dummy_color.as_mut_ptr(), 0, 0, ); (self.xlib.XFreePixmap)(self.display, pixmap); cursor } } fn get_cursor(&self, cursor: Option) -> ffi::Cursor { let cursor = match cursor { Some(cursor) => cursor, None => return self.create_empty_cursor(), }; let mut xcursor = 0; for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) { let name = CString::new(name).unwrap(); xcursor = unsafe { (self.xcursor.XcursorLibraryLoadCursor)( self.display, name.as_ptr() as *const c_char, ) }; if xcursor != 0 { break; } } xcursor } fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> { self.xcb_connection() .change_window_attributes( window, &xproto::ChangeWindowAttributesAux::new().cursor(cursor as xproto::Cursor), )? .ignore_error(); self.xcb_connection().flush()?; Ok(()) } } winit-0.29.15/src/platform_impl/linux/x11/util/geometry.rs000064400000000000000000000276711046102023000215330ustar 00000000000000use std::cmp; use super::*; // Friendly neighborhood axis-aligned rectangle #[derive(Debug, Clone, PartialEq, Eq)] pub struct AaRect { x: i64, y: i64, width: i64, height: i64, } impl AaRect { pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self { let (x, y) = (x as i64, y as i64); let (width, height) = (width as i64, height as i64); AaRect { x, y, width, height, } } pub fn contains_point(&self, x: i64, y: i64) -> bool { x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height } pub fn get_overlapping_area(&self, other: &Self) -> i64 { let x_overlap = cmp::max( 0, cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x), ); let y_overlap = cmp::max( 0, cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y), ); x_overlap * y_overlap } } #[derive(Debug, Default)] pub struct Geometry { pub root: xproto::Window, // If you want positions relative to the root window, use translate_coords. // Note that the overwhelming majority of window managers are reparenting WMs, thus the window // ID we get from window creation is for a nested window used as the window's client area. If // you call get_geometry with that window ID, then you'll get the position of that client area // window relative to the parent it's nested in (the frame), which isn't helpful if you want // to know the frame position. pub x_rel_parent: c_int, pub y_rel_parent: c_int, // In that same case, this will give you client area size. pub width: c_uint, pub height: c_uint, // xmonad and dwm were the only WMs tested that use the border return at all. // The majority of WMs seem to simply fill it with 0 unconditionally. pub border: c_uint, pub depth: c_uint, } #[derive(Debug, Clone)] pub struct FrameExtents { pub left: u32, pub right: u32, pub top: u32, pub bottom: u32, } impl FrameExtents { pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self { FrameExtents { left, right, top, bottom, } } pub fn from_border(border: u32) -> Self { Self::new(border, border, border, border) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum FrameExtentsHeuristicPath { Supported, UnsupportedNested, UnsupportedBordered, } #[derive(Debug, Clone)] pub struct FrameExtentsHeuristic { pub frame_extents: FrameExtents, pub heuristic_path: FrameExtentsHeuristicPath, } impl FrameExtentsHeuristic { pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) { use self::FrameExtentsHeuristicPath::*; if self.heuristic_path != UnsupportedBordered { ( x - self.frame_extents.left as i32, y - self.frame_extents.top as i32, ) } else { (x, y) } } pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) { ( width.saturating_add( self.frame_extents .left .saturating_add(self.frame_extents.right) as _, ), height.saturating_add( self.frame_extents .top .saturating_add(self.frame_extents.bottom) as _, ), ) } } impl XConnection { // This is adequate for inner_position pub fn translate_coords( &self, window: xproto::Window, root: xproto::Window, ) -> Result { self.xcb_connection() .translate_coordinates(window, root, 0, 0)? .reply() .map_err(Into::into) } // This is adequate for inner_size pub fn get_geometry( &self, window: xproto::Window, ) -> Result { self.xcb_connection() .get_geometry(window)? .reply() .map_err(Into::into) } fn get_frame_extents(&self, window: xproto::Window) -> Option { let atoms = self.atoms(); let extents_atom = atoms[_NET_FRAME_EXTENTS]; if !hint_is_supported(extents_atom) { return None; } // Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to // be unsupported by many smaller WMs. let extents: Option> = self .get_property( window, extents_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), ) .ok(); extents.and_then(|extents| { if extents.len() >= 4 { Some(FrameExtents { left: extents[0], right: extents[1], top: extents[2], bottom: extents[3], }) } else { None } }) } pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option { let atoms = self.atoms(); let client_list_atom = atoms[_NET_CLIENT_LIST]; if !hint_is_supported(client_list_atom) { return None; } let client_list: Option> = self .get_property( root, client_list_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW), ) .ok(); client_list.map(|client_list| client_list.contains(&(window as xproto::Window))) } fn get_parent_window(&self, window: xproto::Window) -> Result { let parent = self.xcb_connection().query_tree(window)?.reply()?.parent; Ok(parent) } fn climb_hierarchy( &self, window: xproto::Window, root: xproto::Window, ) -> Result { let mut outer_window = window; loop { let candidate = self.get_parent_window(outer_window)?; if candidate == root { break; } outer_window = candidate; } Ok(outer_window) } pub fn get_frame_extents_heuristic( &self, window: xproto::Window, root: xproto::Window, ) -> FrameExtentsHeuristic { use self::FrameExtentsHeuristicPath::*; // Position relative to root window. // With rare exceptions, this is the position of a nested window. Cases where the window // isn't nested are outlined in the comments throghout this function, but in addition to // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self .translate_coords(window, root) .expect("Failed to translate window coordinates"); (coords.dst_y, coords.child) }; let (width, height, border) = { let inner_geometry = self .get_geometry(window) .expect("Failed to get inner window geometry"); ( inner_geometry.width, inner_geometry.height, inner_geometry.border_width, ) }; // The first condition is only false for un-nested windows, but isn't always false for // un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy: // when y is on the range [0, 2] and if the window has been unfocused since being // undecorated (or was undecorated upon construction), the first condition is true, // requiring us to rely on the second condition. let nested = !(window == child || self.is_top_level(child, root) == Some(true)); // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames. if let Some(mut frame_extents) = self.get_frame_extents(window) { // Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when // decorations are disabled, but since the window becomes un-nested, it's easy to // catch. if !nested { frame_extents = FrameExtents::new(0, 0, 0, 0); } // The difference between the nested window's position and the outermost window's // position is equivalent to the frame size. In most scenarios, this is equivalent to // manually climbing the hierarchy as is done in the case below. Here's a list of // known discrepancies: // * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in // addition to a 1px semi-transparent border. The margin can be easily observed by // using a screenshot tool to get a screenshot of a selected window, and is // presumably used for drawing drop shadows. Getting window geometry information // via hierarchy-climbing results in this margin being included in both the // position and outer size, so a window positioned at (0, 0) would be reported as // having a position (-10, -8). // * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px // on all sides, and there's no additional border. // * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root. // Without decorations, there's no difference. This is presumably related to // Enlightenment's fairly unique concept of window position; it interprets // positions given to XMoveWindow as a client area position rather than a position // of the overall window. FrameExtentsHeuristic { frame_extents, heuristic_path: Supported, } } else if nested { // If the position value we have is for a nested window used as the client area, we'll // just climb up the hierarchy and get the geometry of the outermost window we're // nested in. let outer_window = self .climb_hierarchy(window, root) .expect("Failed to climb window hierarchy"); let (outer_y, outer_width, outer_height) = { let outer_geometry = self .get_geometry(outer_window) .expect("Failed to get outer window geometry"); ( outer_geometry.y, outer_geometry.width, outer_geometry.height, ) }; // Since we have the geometry of the outermost window and the geometry of the client // area, we can figure out what's in between. let diff_x = outer_width.saturating_sub(width) as u32; let diff_y = outer_height.saturating_sub(height) as u32; let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32; let left = diff_x / 2; let right = left; let top = offset_y; let bottom = diff_y.saturating_sub(offset_y); let frame_extents = FrameExtents::new(left, right, top, bottom); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedNested, } } else { // This is the case for xmonad and dwm, AKA the only WMs tested that supplied a // border value. This is convenient, since we can use it to get an accurate frame. let frame_extents = FrameExtents::from_border(border.into()); FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedBordered, } } } } winit-0.29.15/src/platform_impl/linux/x11/util/hint.rs000064400000000000000000000155721046102023000206370ustar 00000000000000use std::sync::Arc; use super::*; #[derive(Debug)] #[allow(dead_code)] pub enum StateOperation { Remove = 0, // _NET_WM_STATE_REMOVE Add = 1, // _NET_WM_STATE_ADD Toggle = 2, // _NET_WM_STATE_TOGGLE } impl From for StateOperation { fn from(op: bool) -> Self { if op { StateOperation::Add } else { StateOperation::Remove } } } /// X window type. Maps directly to /// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html). #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum WindowType { /// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the /// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying /// root window clicks. Desktop, /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows. Dock, /// Toolbar windows. "Torn off" from the main application. Toolbar, /// Pinnable menu windows. "Torn off" from the main application. Menu, /// A small persistent utility window, such as a palette or toolbox. Utility, /// The window is a splash screen displayed as an application is starting up. Splash, /// This is a dialog window. Dialog, /// A dropdown menu that usually appears when the user clicks on an item in a menu bar. /// This property is typically used on override-redirect windows. DropdownMenu, /// A popup menu that usually appears when the user right clicks on an object. /// This property is typically used on override-redirect windows. PopupMenu, /// A tooltip window. Usually used to show additional information when hovering over an object with the cursor. /// This property is typically used on override-redirect windows. Tooltip, /// The window is a notification. /// This property is typically used on override-redirect windows. Notification, /// This should be used on the windows that are popped up by combo boxes. /// This property is typically used on override-redirect windows. Combo, /// This indicates the the window is being dragged. /// This property is typically used on override-redirect windows. Dnd, /// This is a normal, top-level window. #[default] Normal, } impl WindowType { pub(crate) fn as_atom(&self, xconn: &Arc) -> xproto::Atom { use self::WindowType::*; let atom_name = match *self { Desktop => _NET_WM_WINDOW_TYPE_DESKTOP, Dock => _NET_WM_WINDOW_TYPE_DOCK, Toolbar => _NET_WM_WINDOW_TYPE_TOOLBAR, Menu => _NET_WM_WINDOW_TYPE_MENU, Utility => _NET_WM_WINDOW_TYPE_UTILITY, Splash => _NET_WM_WINDOW_TYPE_SPLASH, Dialog => _NET_WM_WINDOW_TYPE_DIALOG, DropdownMenu => _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, PopupMenu => _NET_WM_WINDOW_TYPE_POPUP_MENU, Tooltip => _NET_WM_WINDOW_TYPE_TOOLTIP, Notification => _NET_WM_WINDOW_TYPE_NOTIFICATION, Combo => _NET_WM_WINDOW_TYPE_COMBO, Dnd => _NET_WM_WINDOW_TYPE_DND, Normal => _NET_WM_WINDOW_TYPE_NORMAL, }; let atoms = xconn.atoms(); atoms[atom_name] } } pub struct MotifHints { hints: MwmHints, } struct MwmHints { flags: u32, functions: u32, decorations: u32, input_mode: u32, status: u32, } #[allow(dead_code)] mod mwm { // Motif WM hints are obsolete, but still widely supported. // https://stackoverflow.com/a/1909708 pub const MWM_HINTS_FUNCTIONS: u32 = 1 << 0; pub const MWM_HINTS_DECORATIONS: u32 = 1 << 1; pub const MWM_FUNC_ALL: u32 = 1 << 0; pub const MWM_FUNC_RESIZE: u32 = 1 << 1; pub const MWM_FUNC_MOVE: u32 = 1 << 2; pub const MWM_FUNC_MINIMIZE: u32 = 1 << 3; pub const MWM_FUNC_MAXIMIZE: u32 = 1 << 4; pub const MWM_FUNC_CLOSE: u32 = 1 << 5; } impl MotifHints { pub fn new() -> MotifHints { MotifHints { hints: MwmHints { flags: 0, functions: 0, decorations: 0, input_mode: 0, status: 0, }, } } pub fn set_decorations(&mut self, decorations: bool) { self.hints.flags |= mwm::MWM_HINTS_DECORATIONS; self.hints.decorations = decorations as u32; } pub fn set_maximizable(&mut self, maximizable: bool) { if maximizable { self.add_func(mwm::MWM_FUNC_MAXIMIZE); } else { self.remove_func(mwm::MWM_FUNC_MAXIMIZE); } } fn add_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 { if self.hints.functions & mwm::MWM_FUNC_ALL != 0 { self.hints.functions &= !func; } else { self.hints.functions |= func; } } } fn remove_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 { self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS; self.hints.functions = mwm::MWM_FUNC_ALL; } if self.hints.functions & mwm::MWM_FUNC_ALL != 0 { self.hints.functions |= func; } else { self.hints.functions &= !func; } } } impl Default for MotifHints { fn default() -> Self { Self::new() } } impl XConnection { pub fn get_motif_hints(&self, window: xproto::Window) -> MotifHints { let atoms = self.atoms(); let motif_hints = atoms[_MOTIF_WM_HINTS]; let mut hints = MotifHints::new(); if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { hints.hints.flags = props.first().cloned().unwrap_or(0); hints.hints.functions = props.get(1).cloned().unwrap_or(0); hints.hints.decorations = props.get(2).cloned().unwrap_or(0); hints.hints.input_mode = props.get(3).cloned().unwrap_or(0); hints.hints.status = props.get(4).cloned().unwrap_or(0); } hints } #[allow(clippy::unnecessary_cast)] pub fn set_motif_hints( &self, window: xproto::Window, hints: &MotifHints, ) -> Result, X11Error> { let atoms = self.atoms(); let motif_hints = atoms[_MOTIF_WM_HINTS]; let hints_data: [u32; 5] = [ hints.hints.flags as u32, hints.hints.functions as u32, hints.hints.decorations as u32, hints.hints.input_mode as u32, hints.hints.status as u32, ]; self.change_property( window, motif_hints, motif_hints, xproto::PropMode::REPLACE, &hints_data, ) } } winit-0.29.15/src/platform_impl/linux/x11/util/icon.rs000064400000000000000000000022071046102023000206140ustar 00000000000000#![allow(clippy::assertions_on_constants)] use super::*; use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE}; impl Pixel { pub fn to_packed_argb(&self) -> Cardinal { let mut cardinal = 0; assert!(CARDINAL_SIZE >= PIXEL_SIZE); let as_bytes = &mut cardinal as *mut _ as *mut u8; unsafe { *as_bytes.offset(0) = self.b; *as_bytes.offset(1) = self.g; *as_bytes.offset(2) = self.r; *as_bytes.offset(3) = self.a; } cardinal } } impl RgbaIcon { pub(crate) fn to_cardinals(&self) -> Vec { assert_eq!(self.rgba.len() % PIXEL_SIZE, 0); let pixel_count = self.rgba.len() / PIXEL_SIZE; assert_eq!(pixel_count, (self.width * self.height) as usize); let mut data = Vec::with_capacity(pixel_count); data.push(self.width as Cardinal); data.push(self.height as Cardinal); let pixels = self.rgba.as_ptr() as *const Pixel; for pixel_index in 0..pixel_count { let pixel = unsafe { &*pixels.add(pixel_index) }; data.push(pixel.to_packed_argb()); } data } } winit-0.29.15/src/platform_impl/linux/x11/util/input.rs000064400000000000000000000066651046102023000210370ustar 00000000000000use std::{slice, str}; use x11rb::protocol::{ xinput::{self, ConnectionExt as _}, xkb, }; use super::*; pub const VIRTUAL_CORE_POINTER: u16 = 2; pub const VIRTUAL_CORE_KEYBOARD: u16 = 3; // A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to // re-allocate (and make another round-trip) in the *vast* majority of cases. // To test if `lookup_utf8` works correctly, set this to 1. const TEXT_BUFFER_SIZE: usize = 1024; impl XConnection { pub fn select_xinput_events( &self, window: xproto::Window, device_id: u16, mask: xinput::XIEventMask, ) -> Result, X11Error> { self.xcb_connection() .xinput_xi_select_events( window, &[xinput::EventMask { deviceid: device_id, mask: vec![mask], }], ) .map_err(Into::into) } pub fn select_xkb_events( &self, device_id: xkb::DeviceSpec, mask: xkb::EventType, ) -> Result { let mask = u16::from(mask) as _; let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) }; if status == ffi::True { self.flush_requests()?; Ok(true) } else { error!("Could not select XKB events: The XKB extension is not initialized!"); Ok(false) } } pub fn query_pointer( &self, window: xproto::Window, device_id: u16, ) -> Result { self.xcb_connection() .xinput_xi_query_pointer(window, device_id)? .reply() .map_err(Into::into) } fn lookup_utf8_inner( &self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent, buffer: *mut u8, size: usize, ) -> (ffi::KeySym, ffi::Status, c_int) { let mut keysym: ffi::KeySym = 0; let mut status: ffi::Status = 0; let count = unsafe { (self.xlib.Xutf8LookupString)( ic, key_event, buffer as *mut c_char, size as c_int, &mut keysym, &mut status, ) }; (keysym, status, count) } pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { // `assume_init` is safe here because the array consists of `MaybeUninit` values, // which do not require initialization. let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = unsafe { MaybeUninit::uninit().assume_init() }; // If the buffer overflows, we'll make a new one on the heap. let mut vec; let (_, status, count) = self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); let bytes = if status == ffi::XBufferOverflow { vec = Vec::with_capacity(count as usize); let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); debug_assert_eq!(count, new_count); unsafe { vec.set_len(count as usize) }; &vec[..count as usize] } else { unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } }; str::from_utf8(bytes).unwrap_or("").to_string() } } winit-0.29.15/src/platform_impl/linux/x11/util/keys.rs000064400000000000000000000032121046102023000206340ustar 00000000000000use std::{iter::Enumerate, slice::Iter}; use super::*; pub struct Keymap { keys: [u8; 32], } pub struct KeymapIter<'a> { iter: Enumerate>, index: usize, item: Option, } impl Keymap { pub fn iter(&self) -> KeymapIter<'_> { KeymapIter { iter: self.keys.iter().enumerate(), index: 0, item: None, } } } impl<'a> IntoIterator for &'a Keymap { type Item = ffi::KeyCode; type IntoIter = KeymapIter<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl Iterator for KeymapIter<'_> { type Item = ffi::KeyCode; fn next(&mut self) -> Option { if self.item.is_none() { for (index, &item) in self.iter.by_ref() { if item != 0 { self.index = index; self.item = Some(item); break; } } } self.item.take().map(|item| { debug_assert!(item != 0); let bit = first_bit(item); if item != bit { // Remove the first bit; save the rest for further iterations self.item = Some(item ^ bit); } let shift = bit.trailing_zeros() + (self.index * 8) as u32; shift as ffi::KeyCode }) } } impl XConnection { pub fn query_keymap(&self) -> Keymap { let mut keys = [0; 32]; unsafe { (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); } Keymap { keys } } } fn first_bit(b: u8) -> u8 { 1 << b.trailing_zeros() } winit-0.29.15/src/platform_impl/linux/x11/util/memory.rs000064400000000000000000000016271046102023000212010ustar 00000000000000use std::ops::{Deref, DerefMut}; use super::*; pub(crate) struct XSmartPointer<'a, T> { xconn: &'a XConnection, pub ptr: *mut T, } impl<'a, T> XSmartPointer<'a, T> { // You're responsible for only passing things to this that should be XFree'd. // Returns None if ptr is null. pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option { if !ptr.is_null() { Some(XSmartPointer { xconn, ptr }) } else { None } } } impl<'a, T> Deref for XSmartPointer<'a, T> { type Target = T; fn deref(&self) -> &T { unsafe { &*self.ptr } } } impl<'a, T> DerefMut for XSmartPointer<'a, T> { fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.ptr } } } impl<'a, T> Drop for XSmartPointer<'a, T> { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFree)(self.ptr as *mut _); } } } winit-0.29.15/src/platform_impl/linux/x11/util/mod.rs000064400000000000000000000042131046102023000204420ustar 00000000000000// Welcome to the util module, where we try to keep you from shooting yourself in the foot. // *results may vary use std::{ mem::{self, MaybeUninit}, ops::BitAnd, os::raw::*, }; mod client_msg; pub mod cookie; mod cursor; mod geometry; mod hint; mod icon; mod input; pub mod keys; pub(crate) mod memory; mod mouse; mod randr; mod window_property; mod wm; mod xmodmap; pub use self::{ geometry::*, hint::*, input::*, mouse::*, window_property::*, wm::*, xmodmap::ModifierKeymap, }; use super::{atoms::*, ffi, VoidCookie, X11Error, XConnection, XError}; use x11rb::protocol::xproto::{self, ConnectionExt as _}; pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); if *field != wrapped { *field = wrapped; true } else { false } } pub fn has_flag(bitset: T, flag: T) -> bool where T: Copy + PartialEq + BitAnd, { bitset & flag == flag } impl XConnection { // This is impoartant, so pay attention! // Xlib has an output buffer, and tries to hide the async nature of X from you. // This buffer contains the requests you make, and is flushed under various circumstances: // 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed" // 2. `XFlush` explicitly flushes // 3. `XSync` flushes and blocks until all requests are responded to // 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally. // When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits. // All util functions that abstract an async function will return a `Flusher`. pub fn flush_requests(&self) -> Result<(), XError> { unsafe { (self.xlib.XFlush)(self.display) }; //println!("XFlush"); // This isn't necessarily a useful time to check for errors (since our request hasn't // necessarily been processed yet) self.check_errors() } pub fn sync_with_server(&self) -> Result<(), XError> { unsafe { (self.xlib.XSync)(self.display, ffi::False) }; //println!("XSync"); self.check_errors() } } winit-0.29.15/src/platform_impl/linux/x11/util/modifiers.rs000064400000000000000000000126731046102023000216550ustar 00000000000000use std::{collections::HashMap, slice}; use super::*; use crate::event::{ElementState, ModifiersState}; // Offsets within XModifierKeymap to each set of keycodes. // We are only interested in Shift, Control, Alt, and Logo. // // There are 8 sets total. The order of keycode sets is: // Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 // // https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html const SHIFT_OFFSET: usize = 0; const CONTROL_OFFSET: usize = 2; const ALT_OFFSET: usize = 3; const LOGO_OFFSET: usize = 6; const NUM_MODS: usize = 8; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Modifier { Alt, Ctrl, Shift, Logo, } #[derive(Debug, Default)] pub(crate) struct ModifierKeymap { // Maps keycodes to modifiers keys: HashMap, } #[derive(Clone, Debug, Default)] pub(crate) struct ModifierKeyState { // Contains currently pressed modifier keys and their corresponding modifiers keys: HashMap, state: ModifiersState, } impl ModifierKeymap { pub fn new() -> ModifierKeymap { ModifierKeymap::default() } pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { self.keys.get(&keycode).cloned() } pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { { let keymap = xconn.xcb_connection().get_modifier_mapping().expect("get_modifier_mapping failed").reply().expect("get_modifier_mapping failed"); if keymap.is_null() { panic!("failed to allocate XModifierKeymap"); } self.reset_from_x_keymap(&*keymap); (xconn.xlib.XFreeModifiermap)(keymap); } } pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { let keys_per_mod = keymap.max_keypermod as usize; let keys = unsafe { slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) }; self.keys.clear(); self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); } fn read_x_keys( &mut self, keys: &[ffi::KeyCode], offset: usize, keys_per_mod: usize, modifier: Modifier, ) { let start = offset * keys_per_mod; let end = start + keys_per_mod; for &keycode in &keys[start..end] { if keycode != 0 { self.keys.insert(keycode, modifier); } } } } impl ModifierKeyState { pub fn update_keymap(&mut self, mods: &ModifierKeymap) { self.keys.retain(|k, v| { if let Some(m) = mods.get_modifier(*k) { *v = m; true } else { false } }); self.reset_state(); } pub fn update_state( &mut self, state: &ModifiersState, except: Option, ) -> Option { let mut new_state = *state; match except { Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()), Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()), Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()), Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()), None => (), } if self.state == new_state { None } else { self.keys.retain(|_k, v| get_modifier(&new_state, *v)); self.state = new_state; Some(new_state) } } pub fn modifiers(&self) -> ModifiersState { self.state } pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { match state { ElementState::Pressed => self.key_press(keycode, modifier), ElementState::Released => self.key_release(keycode), } } pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { self.keys.insert(keycode, modifier); set_modifier(&mut self.state, modifier, true); } pub fn key_release(&mut self, keycode: ffi::KeyCode) { if let Some(modifier) = self.keys.remove(&keycode) { if !self.keys.values().any(|&m| m == modifier) { set_modifier(&mut self.state, modifier, false); } } } fn reset_state(&mut self) { let mut new_state = ModifiersState::default(); for &m in self.keys.values() { set_modifier(&mut new_state, m, true); } self.state = new_state; } } fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool { match modifier { Modifier::Alt => state.alt(), Modifier::Ctrl => state.ctrl(), Modifier::Shift => state.shift(), Modifier::Logo => state.logo(), } } fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) { match modifier { Modifier::Alt => state.set(ModifiersState::ALT, value), Modifier::Ctrl => state.set(ModifiersState::CTRL, value), Modifier::Shift => state.set(ModifiersState::SHIFT, value), Modifier::Logo => state.set(ModifiersState::LOGO, value), } } winit-0.29.15/src/platform_impl/linux/x11/util/mouse.rs000064400000000000000000000021111046102023000210060ustar 00000000000000//! Utilities for handling mouse events. /// Recorded mouse delta designed to filter out noise. pub struct Delta { x: T, y: T, } impl Default for Delta { fn default() -> Self { Self { x: Default::default(), y: Default::default(), } } } impl Delta { pub(crate) fn set_x(&mut self, x: T) { self.x = x; } pub(crate) fn set_y(&mut self, y: T) { self.y = y; } } macro_rules! consume { ($this:expr, $ty:ty) => {{ let this = $this; let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) { (true, true) => return None, (false, true) => (this.x, 0.0), (true, false) => (0.0, this.y), (false, false) => (this.x, this.y), }; Some((x, y)) }}; } impl Delta { pub(crate) fn consume(self) -> Option<(f32, f32)> { consume!(self, f32) } } impl Delta { pub(crate) fn consume(self) -> Option<(f64, f64)> { consume!(self, f64) } } winit-0.29.15/src/platform_impl/linux/x11/util/randr.rs000064400000000000000000000144631046102023000210010ustar 00000000000000use std::{env, str, str::FromStr}; use super::*; use crate::platform_impl::platform::x11::monitor; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; use x11rb::protocol::randr::{self, ConnectionExt as _}; /// Represents values of `WINIT_HIDPI_FACTOR`. pub enum EnvVarDPI { Randr, Scale(f64), NotSet, } pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), (width_mm, height_mm): (u64, u64), ) -> f64 { // See http://xpra.org/trac/ticket/728 for more information. if width_mm == 0 || height_mm == 0 { warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); return 1.0; } let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); // Quantize 1/12 step size let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); assert!(validate_scale_factor(dpi_factor)); if dpi_factor <= 20. { dpi_factor } else { 1. } } impl XConnection { // Retrieve DPI from Xft.dpi property pub fn get_xft_dpi(&self) -> Option { // Try to get it from XSETTINGS first. if let Some(xsettings_screen) = self.xsettings_screen() { match self.xsettings_dpi(xsettings_screen) { Ok(Some(dpi)) => return Some(dpi), Ok(None) => {} Err(err) => { log::warn!("failed to fetch XSettings: {err}"); } } } self.database() .get_string("Xft.dpi", "") .and_then(|s| f64::from_str(s).ok()) } pub fn get_output_info( &self, resources: &monitor::ScreenResources, crtc: &randr::GetCrtcInfoReply, ) -> Option<(String, f64, Vec)> { let output_info = match self .xcb_connection() .randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME) .map_err(X11Error::from) .and_then(|r| r.reply().map_err(X11Error::from)) { Ok(output_info) => output_info, Err(err) => { warn!("Failed to get output info: {:?}", err); return None; } }; let bit_depth = self.default_root().root_depth; let output_modes = &output_info.modes; let resource_modes = resources.modes(); let modes = resource_modes .iter() // XRROutputInfo contains an array of mode ids that correspond to // modes in the array in XRRScreenResources .filter(|x| output_modes.iter().any(|id| x.id == *id)) .map(|mode| { VideoMode { size: (mode.width.into(), mode.height.into()), refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) .unwrap_or(0), bit_depth: bit_depth as u16, native_mode: mode.id, // This is populated in `MonitorHandle::video_modes` as the // video mode is returned to the user monitor: None, } }) .collect(); let name = match str::from_utf8(&output_info.name) { Ok(name) => name.to_owned(), Err(err) => { warn!("Failed to get output name: {:?}", err); return None; } }; // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); if deprecated_dpi_override.is_some() { warn!( "The WINIT_HIDPI_FACTOR environment variable is deprecated; use WINIT_X11_SCALE_FACTOR" ) } let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else( || EnvVarDPI::NotSet, |var| { if var.to_lowercase() == "randr" { EnvVarDPI::Randr } else if let Ok(dpi) = f64::from_str(&var) { EnvVarDPI::Scale(dpi) } else if var.is_empty() { EnvVarDPI::NotSet } else { panic!( "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{var}`" ); } }, ); let scale_factor = match dpi_env { EnvVarDPI::Randr => calc_dpi_factor( (crtc.width.into(), crtc.height.into()), (output_info.mm_width as _, output_info.mm_height as _), ), EnvVarDPI::Scale(dpi_override) => { if !validate_scale_factor(dpi_override) { panic!( "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{dpi_override}`", ); } dpi_override } EnvVarDPI::NotSet => { if let Some(dpi) = self.get_xft_dpi() { dpi / 96. } else { calc_dpi_factor( (crtc.width.into(), crtc.height.into()), (output_info.mm_width as _, output_info.mm_height as _), ) } } }; Some((name, scale_factor, modes)) } pub fn set_crtc_config( &self, crtc_id: randr::Crtc, mode_id: randr::Mode, ) -> Result<(), X11Error> { let crtc = self .xcb_connection() .randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)? .reply()?; self.xcb_connection() .randr_set_crtc_config( crtc_id, crtc.timestamp, x11rb::CURRENT_TIME, crtc.x, crtc.y, mode_id, crtc.rotation, &crtc.outputs, )? .reply() .map(|_| ()) .map_err(Into::into) } pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result { Ok(self .xcb_connection() .randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)? .reply()? .mode) } } winit-0.29.15/src/platform_impl/linux/x11/util/window_property.rs000064400000000000000000000135701046102023000231440ustar 00000000000000use std::error::Error; use std::fmt; use std::sync::Arc; use bytemuck::{NoUninit, Pod}; use x11rb::connection::Connection; use x11rb::errors::ReplyError; use super::*; pub const CARDINAL_SIZE: usize = mem::size_of::(); pub type Cardinal = u32; #[derive(Debug, Clone)] pub enum GetPropertyError { X11rbError(Arc), TypeMismatch(xproto::Atom), FormatMismatch(c_int), } impl GetPropertyError { pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool { if let GetPropertyError::TypeMismatch(actual_type) = *self { actual_type == t } else { false } } } impl> From for GetPropertyError { fn from(e: T) -> Self { Self::X11rbError(Arc::new(e.into())) } } impl fmt::Display for GetPropertyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GetPropertyError::X11rbError(err) => err.fmt(f), GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"), GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"), } } } impl Error for GetPropertyError {} // Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. // To test if `get_property` works correctly, set this to 1. const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone! impl XConnection { pub fn get_property( &self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, ) -> Result, GetPropertyError> { let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type); let mut data = vec![]; loop { if !iter.next_window(&mut data)? { break; } } Ok(data) } pub fn change_property<'a, T: NoUninit>( &'a self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, mode: xproto::PropMode, new_value: &[T], ) -> Result, X11Error> { assert!([1usize, 2, 4].contains(&mem::size_of::())); self.xcb_connection() .change_property( mode, window, property, property_type, (mem::size_of::() * 8) as u8, new_value .len() .try_into() .expect("too many items for propery"), bytemuck::cast_slice::(new_value), ) .map_err(Into::into) } } /// An iterator over the "windows" of the property that we are fetching. struct PropIterator<'a, C: ?Sized, T> { /// Handle to the connection. conn: &'a C, /// The window that we're fetching the property from. window: xproto::Window, /// The property that we're fetching. property: xproto::Atom, /// The type of the property that we're fetching. property_type: xproto::Atom, /// The offset of the next window, in 32-bit chunks. offset: u32, /// The format of the type. format: u8, /// Keep a reference to `T`. _phantom: std::marker::PhantomData, } impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> { /// Create a new property iterator. fn new( conn: &'a C, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, ) -> Self { let format = match mem::size_of::() { 1 => 8, 2 => 16, 4 => 32, _ => unreachable!(), }; Self { conn, window, property, property_type, offset: 0, format, _phantom: Default::default(), } } /// Get the next window and append it to `data`. /// /// Returns whether there are more windows to fetch. fn next_window(&mut self, data: &mut Vec) -> Result { // Send the request and wait for the reply. let reply = self .conn .get_property( false, self.window, self.property, self.property_type, self.offset, PROPERTY_BUFFER_SIZE, )? .reply()?; // Make sure that the reply is of the correct type. if reply.type_ != self.property_type { return Err(GetPropertyError::TypeMismatch(reply.type_)); } // Make sure that the reply is of the correct format. if reply.format != self.format { return Err(GetPropertyError::FormatMismatch(reply.format.into())); } // Append the data to the output. if mem::size_of::() == 1 && mem::align_of::() == 1 { // We can just do a bytewise append. data.extend_from_slice(bytemuck::cast_slice(&reply.value)); } else { // Rust's borrowing and types system makes this a bit tricky. // // We need to make sure that the data is properly aligned. Unfortunately the best // safe way to do this is to copy the data to another buffer and then append. // // TODO(notgull): It may be worth it to use `unsafe` to copy directly from // `reply.value` to `data`; check if this is faster. Use benchmarks! let old_len = data.len(); let added_len = reply.value.len() / mem::size_of::(); data.resize(old_len + added_len, T::zeroed()); bytemuck::cast_slice_mut::(&mut data[old_len..]).copy_from_slice(&reply.value); } // Check `bytes_after` to see if there are more windows to fetch. self.offset += PROPERTY_BUFFER_SIZE; Ok(reply.bytes_after != 0) } } winit-0.29.15/src/platform_impl/linux/x11/util/wm.rs000064400000000000000000000123461046102023000203140ustar 00000000000000use std::sync::Mutex; use once_cell::sync::Lazy; use super::*; // https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248 pub const MOVERESIZE_TOPLEFT: isize = 0; pub const MOVERESIZE_TOP: isize = 1; pub const MOVERESIZE_TOPRIGHT: isize = 2; pub const MOVERESIZE_RIGHT: isize = 3; pub const MOVERESIZE_BOTTOMRIGHT: isize = 4; pub const MOVERESIZE_BOTTOM: isize = 5; pub const MOVERESIZE_BOTTOMLEFT: isize = 6; pub const MOVERESIZE_LEFT: isize = 7; pub const MOVERESIZE_MOVE: isize = 8; // This info is global to the window manager. static SUPPORTED_HINTS: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(0))); static WM_NAME: Lazy>> = Lazy::new(|| Mutex::new(None)); pub fn hint_is_supported(hint: xproto::Atom) -> bool { (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint) } pub fn wm_name_is_one_of(names: &[&str]) -> bool { if let Some(ref name) = *WM_NAME.lock().unwrap() { names.contains(&name.as_str()) } else { false } } impl XConnection { pub fn update_cached_wm_info(&self, root: xproto::Window) { *SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root); *WM_NAME.lock().unwrap() = self.get_wm_name(root); } fn get_supported_hints(&self, root: xproto::Window) -> Vec { let atoms = self.atoms(); let supported_atom = atoms[_NET_SUPPORTED]; self.get_property( root, supported_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ) .unwrap_or_else(|_| Vec::with_capacity(0)) } #[allow(clippy::useless_conversion)] fn get_wm_name(&self, root: xproto::Window) -> Option { let atoms = self.atoms(); let check_atom = atoms[_NET_SUPPORTING_WM_CHECK]; let wm_name_atom = atoms[_NET_WM_NAME]; // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite // it working and being supported. This has been reported upstream, but due to the // inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK // regardless of whether or not the WM claims to support it. // // Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed // in 0.72. /*if !supported_hints.contains(&check_atom) { return None; }*/ // IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless // provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine. /*if !supported_hints.contains(&wm_name_atom) { return None; }*/ // Of the WMs tested, only xmonad and dwm fail to provide a WM name. // Querying this property on the root window will give us the ID of a child window created by // the WM. let root_window_wm_check = { let result = self.get_property::( root, check_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW), ); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; // Querying the same property on the child window we were given, we should get this child // window's ID again. let child_window_wm_check = { let result = self.get_property::( root_window_wm_check.into(), check_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW), ); let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned()); wm_check? }; // These values should be the same. if root_window_wm_check != child_window_wm_check { return None; } // All of that work gives us a window ID that we can get the WM name from. let wm_name = { let atoms = self.atoms(); let utf8_string_atom = atoms[UTF8_STRING]; let result = self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom); // IceWM requires this. IceWM was also the only WM tested that returns a null-terminated // string. For more fun trivia, IceWM is also unique in including version and uname // information in this string (this means you'll have to be careful if you want to match // against it, though). // The unofficial 1.4 fork of IceWM still includes the extra details, but properly // returns a UTF8 string that isn't null-terminated. let no_utf8 = if let Err(ref err) = result { err.is_actual_property_type(xproto::Atom::from(xproto::AtomEnum::STRING)) } else { false }; if no_utf8 { self.get_property( root_window_wm_check.into(), wm_name_atom, xproto::Atom::from(xproto::AtomEnum::STRING), ) } else { result } } .ok(); wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok()) } } winit-0.29.15/src/platform_impl/linux/x11/util/xmodmap.rs000064400000000000000000000031051046102023000213270ustar 00000000000000use std::collections::HashSet; use std::slice; use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap}; // Offsets within XModifierKeymap to each set of keycodes. // We are only interested in Shift, Control, Alt, and Logo. // // There are 8 sets total. The order of keycode sets is: // Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 // // https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html const NUM_MODS: usize = 8; /// Track which keys are modifiers, so we can properly replay them when they were filtered. #[derive(Debug, Default)] pub struct ModifierKeymap { // Maps keycodes to modifiers modifers: HashSet, } impl ModifierKeymap { pub fn new() -> ModifierKeymap { ModifierKeymap::default() } pub fn is_modifier(&self, keycode: XKeyCode) -> bool { self.modifers.contains(&keycode) } pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) { unsafe { let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); if keymap.is_null() { return; } self.reset_from_x_keymap(&*keymap); (xconn.xlib.XFreeModifiermap)(keymap); } } fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) { let keys_per_mod = keymap.max_keypermod as usize; let keys = unsafe { slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) }; self.modifers.clear(); for key in keys { self.modifers.insert(*key); } } } winit-0.29.15/src/platform_impl/linux/x11/window.rs000064400000000000000000002163121046102023000202220ustar 00000000000000use std::{ cmp, env, ffi::CString, mem::replace, os::raw::*, path::Path, sync::{Arc, Mutex, MutexGuard}, }; use x11rb::{ connection::Connection, properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}, protocol::{ randr, shape::SK, xfixes::{ConnectionExt, RegionWrapper}, xinput, xproto::{self, ConnectionExt as _, Rectangle}, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::{Event, InnerSizeWriter, WindowEvent}, event_loop::AsyncRequestSerial, platform_impl::{ x11::{ atoms::*, xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, }, Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformIcon, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }, }; use super::{ ffi, util, CookieResultExt, EventLoopWindowTarget, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection, }; #[derive(Debug)] pub struct SharedState { pub cursor_pos: Option<(f64, f64)>, pub size: Option<(u32, u32)>, pub position: Option<(i32, i32)>, pub inner_position: Option<(i32, i32)>, pub inner_position_rel_parent: Option<(i32, i32)>, pub is_resizable: bool, pub is_decorated: bool, pub last_monitor: X11MonitorHandle, pub dpi_adjusted: Option<(u32, u32)>, pub(crate) fullscreen: Option, // Set when application calls `set_fullscreen` when window is not visible pub(crate) desired_fullscreen: Option>, // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, // Used to restore video mode after exiting fullscreen pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>, pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, pub resize_increments: Option, pub base_size: Option, pub visibility: Visibility, pub has_focus: bool, // Use `Option` to not apply hittest logic when it was never requested. pub cursor_hittest: Option, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Visibility { No, Yes, // Waiting for VisibilityNotify YesWait, } impl SharedState { fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex { let visibility = if window_attributes.visible { Visibility::YesWait } else { Visibility::No }; Mutex::new(SharedState { last_monitor, visibility, is_resizable: window_attributes.resizable, is_decorated: window_attributes.decorations, cursor_pos: None, size: None, position: None, inner_position: None, inner_position_rel_parent: None, dpi_adjusted: None, fullscreen: None, desired_fullscreen: None, restore_position: None, desktop_video_mode: None, frame_extents: None, min_inner_size: None, max_inner_size: None, resize_increments: None, base_size: None, has_focus: false, cursor_hittest: None, }) } } unsafe impl Send for UnownedWindow {} unsafe impl Sync for UnownedWindow {} pub struct UnownedWindow { pub(crate) xconn: Arc, // never changes xwindow: xproto::Window, // never changes #[allow(dead_code)] visual: u32, // never changes root: xproto::Window, // never changes #[allow(dead_code)] screen_id: i32, // never changes cursor: Mutex, cursor_grabbed_mode: Mutex, #[allow(clippy::mutex_atomic)] cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, redraw_sender: WakeSender, activation_sender: WakeSender, } macro_rules! leap { ($e:expr) => { match $e { Ok(x) => x, Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))), } }; } impl UnownedWindow { #[allow(clippy::unnecessary_cast)] pub(crate) fn new( event_loop: &EventLoopWindowTarget, window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let xconn = &event_loop.xconn; let atoms = xconn.atoms(); #[cfg(feature = "rwh_06")] let root = match window_attrs.parent_window.0 { Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window, Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(), Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"), None => event_loop.root, }; #[cfg(not(feature = "rwh_06"))] let root = event_loop.root; let mut monitors = leap!(xconn.available_monitors()); let guessed_monitor = if monitors.is_empty() { X11MonitorHandle::dummy() } else { xconn .query_pointer(root, util::VIRTUAL_CORE_POINTER) .ok() .and_then(|pointer_state| { let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64); for i in 0..monitors.len() { if monitors[i].rect.contains_point(x, y) { return Some(monitors.swap_remove(i)); } } None }) .unwrap_or_else(|| monitors.swap_remove(0)) }; let scale_factor = guessed_monitor.scale_factor(); info!("Guessed window scale factor: {}", scale_factor); let max_inner_size: Option<(u32, u32)> = window_attrs .max_inner_size .map(|size| size.to_physical::(scale_factor).into()); let min_inner_size: Option<(u32, u32)> = window_attrs .min_inner_size .map(|size| size.to_physical::(scale_factor).into()); let position = window_attrs .position .map(|position| position.to_physical::(scale_factor)); let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions: (u32, u32) = window_attrs .inner_size .map(|size| size.to_physical::(scale_factor)) .or_else(|| Some((800, 600).into())) .map(Into::into) .unwrap(); if let Some(max) = max_inner_size { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } if let Some(min) = min_inner_size { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } debug!( "Calculated physical dimensions: {}x{}", dimensions.0, dimensions.1 ); dimensions }; let screen_id = match pl_attribs.x11.screen_id { Some(id) => id, None => xconn.default_screen_index() as c_int, }; // An iterator over all of the visuals combined with their depths. let mut all_visuals = xconn .xcb_connection() .setup() .roots .iter() .flat_map(|root| &root.allowed_depths) .flat_map(|depth| { depth .visuals .iter() .map(move |visual| (visual, depth.depth)) }); // creating let (visualtype, depth, require_colormap) = match pl_attribs.x11.visual_id { Some(vi) => { // Find this specific visual. let (visualtype, depth) = all_visuals .find(|(visual, _)| visual.visual_id == vi) .ok_or_else(|| os_error!(OsError::XError(X11Error::NoSuchVisual(vi).into())))?; (Some(visualtype), depth, true) } None if window_attrs.transparent => { // Find a suitable visual, true color with 32 bits of depth. all_visuals .find_map(|(visual, depth)| { (depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR) .then_some((Some(visual), depth, true)) }) .unwrap_or_else(|| { debug!("Could not set transparency, because XMatchVisualInfo returned zero for the required parameters"); (None as _, x11rb::COPY_FROM_PARENT as _, false) }) } _ => (None, x11rb::COPY_FROM_PARENT as _, false), }; let mut visual = visualtype.map_or(x11rb::COPY_FROM_PARENT, |v| v.visual_id); let window_attributes = { use xproto::EventMask; let mut aux = xproto::CreateWindowAux::new(); let event_mask = EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::VISIBILITY_CHANGE | EventMask::KEY_PRESS | EventMask::KEY_RELEASE | EventMask::KEYMAP_STATE | EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE | EventMask::POINTER_MOTION | EventMask::PROPERTY_CHANGE; aux = aux.event_mask(event_mask).border_pixel(0); if pl_attribs.x11.override_redirect { aux = aux.override_redirect(true as u32); } // Add a colormap if needed. let colormap_visual = match pl_attribs.x11.visual_id { Some(vi) => Some(vi), None if require_colormap => Some(visual), _ => None, }; if let Some(visual) = colormap_visual { let colormap = leap!(xconn.xcb_connection().generate_id()); leap!(xconn.xcb_connection().create_colormap( xproto::ColormapAlloc::NONE, colormap, root, visual, )); aux = aux.colormap(colormap); } else { aux = aux.colormap(0); } aux }; // Figure out the window's parent. let parent = pl_attribs.x11.embed_window.unwrap_or(root); // finally creating the window let xwindow = { let (x, y) = position.map_or((0, 0), Into::into); let wid = leap!(xconn.xcb_connection().generate_id()); let result = xconn.xcb_connection().create_window( depth, wid, parent, x, y, dimensions.0.try_into().unwrap(), dimensions.1.try_into().unwrap(), 0, xproto::WindowClass::INPUT_OUTPUT, visual, &window_attributes, ); leap!(leap!(result).check()); wid }; // The COPY_FROM_PARENT is a special value for the visual used to copy // the visual from the parent window, thus we have to query the visual // we've got when we built the window above. if visual == x11rb::COPY_FROM_PARENT { visual = leap!(leap!(xconn .xcb_connection() .get_window_attributes(xwindow as xproto::Window)) .reply()) .visual; } #[allow(clippy::mutex_atomic)] let mut window = UnownedWindow { xconn: Arc::clone(xconn), xwindow: xwindow as xproto::Window, visual, root, screen_id, cursor: Default::default(), cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, &window_attrs), redraw_sender: event_loop.redraw_sender.clone(), activation_sender: event_loop.activation_sender.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window // title to determine placement/etc., so doing this after mapping would cause the WM to // act on the wrong title state. leap!(window.set_title_inner(&window_attrs.title)).ignore_error(); leap!(window.set_decorations_inner(window_attrs.decorations)).ignore_error(); if let Some(theme) = window_attrs.preferred_theme { leap!(window.set_theme_inner(Some(theme))).ignore_error(); } // Embed the window if needed. if pl_attribs.x11.embed_window.is_some() { window.embed_window()?; } { // Enable drag and drop (TODO: extend API to make this toggleable) { let dnd_aware_atom = atoms[XdndAware]; let version = &[5u32]; // Latest version; hasn't changed since 2002 leap!(xconn.change_property( window.xwindow, dnd_aware_atom, u32::from(xproto::AtomEnum::ATOM), xproto::PropMode::REPLACE, version, )) .ignore_error(); } // WM_CLASS must be set *before* mapping the window, as per ICCCM! { let (instance, class) = if let Some(name) = pl_attribs.name { (name.instance, name.general) } else { let class = env::args_os() .next() .as_ref() // Default to the name of the binary (via argv[0]) .and_then(|path| Path::new(path).file_name()) .and_then(|bin_name| bin_name.to_str()) .map(|bin_name| bin_name.to_owned()) .unwrap_or_else(|| window_attrs.title.clone()); // This environment variable is extraordinarily unlikely to actually be used... let instance = env::var("RESOURCE_NAME") .ok() .unwrap_or_else(|| class.clone()); (instance, class) }; let class = format!("{instance}\0{class}\0"); leap!(xconn.change_property( window.xwindow, xproto::Atom::from(xproto::AtomEnum::WM_CLASS), xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, class.as_bytes(), )) .ignore_error(); } if let Some(flusher) = leap!(window.set_pid()) { flusher.ignore_error() } leap!(window.set_window_types(pl_attribs.x11.x11_window_types)).ignore_error(); // Set size hints. let mut min_inner_size = window_attrs .min_inner_size .map(|size| size.to_physical::(scale_factor)); let mut max_inner_size = window_attrs .max_inner_size .map(|size| size.to_physical::(scale_factor)); if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); } else { max_inner_size = Some(dimensions.into()); min_inner_size = Some(dimensions.into()); } } let shared_state = window.shared_state.get_mut().unwrap(); shared_state.min_inner_size = min_inner_size.map(Into::into); shared_state.max_inner_size = max_inner_size.map(Into::into); shared_state.resize_increments = window_attrs.resize_increments; shared_state.base_size = pl_attribs.x11.base_size; let normal_hints = WmSizeHints { position: position.map(|PhysicalPosition { x, y }| { (WmSizeHintsSpecification::UserSpecified, x, y) }), size: Some(( WmSizeHintsSpecification::UserSpecified, cast_dimension_to_hint(dimensions.0), cast_dimension_to_hint(dimensions.1), )), max_size: max_inner_size.map(cast_physical_size_to_hint), min_size: min_inner_size.map(cast_physical_size_to_hint), size_increment: window_attrs .resize_increments .map(|size| cast_size_to_hint(size, scale_factor)), base_size: pl_attribs .x11 .base_size .map(|size| cast_size_to_hint(size, scale_factor)), aspect: None, win_gravity: None, }; leap!(leap!(normal_hints.set( xconn.xcb_connection(), window.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )) .check()); // Set window icons if let Some(icon) = window_attrs.window_icon { leap!(window.set_icon_inner(icon.inner)).ignore_error(); } // Opt into handling window close let result = xconn.xcb_connection().change_property( xproto::PropMode::REPLACE, window.xwindow, atoms[WM_PROTOCOLS], xproto::AtomEnum::ATOM, 32, 2, bytemuck::cast_slice::(&[ atoms[WM_DELETE_WINDOW], atoms[_NET_WM_PING], ]), ); leap!(result).ignore_error(); // Set visibility (map window) if window_attrs.visible { leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); leap!(xconn.xcb_connection().configure_window( xwindow, &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE) )) .ignore_error(); } // Attempt to make keyboard input repeat detectable unsafe { let mut supported_ptr = ffi::False; (xconn.xlib.XkbSetDetectableAutoRepeat)( xconn.display, ffi::True, &mut supported_ptr, ); if supported_ptr == ffi::False { return Err(os_error!(OsError::Misc( "`XkbSetDetectableAutoRepeat` failed" ))); } } // Select XInput2 events let mask = xinput::XIEventMask::MOTION | xinput::XIEventMask::BUTTON_PRESS | xinput::XIEventMask::BUTTON_RELEASE | xinput::XIEventMask::ENTER | xinput::XIEventMask::LEAVE | xinput::XIEventMask::FOCUS_IN | xinput::XIEventMask::FOCUS_OUT | xinput::XIEventMask::TOUCH_BEGIN | xinput::XIEventMask::TOUCH_UPDATE | xinput::XIEventMask::TOUCH_END; leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) .ignore_error(); // Try to create input context for the window. if let Some(ime) = event_loop.ime.as_ref() { let result = ime .borrow_mut() .create_context(window.xwindow as ffi::Window, false); leap!(result); } // These properties must be set after mapping if window_attrs.maximized { leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error(); } if window_attrs.fullscreen.0.is_some() { if let Some(flusher) = leap!(window .set_fullscreen_inner(window_attrs.fullscreen.0.clone().map(Into::into))) { flusher.ignore_error() } if let Some(PhysicalPosition { x, y }) = position { let shared_state = window.shared_state.get_mut().unwrap(); shared_state.restore_position = Some((x, y)); } } leap!(window.set_window_level_inner(window_attrs.window_level)).ignore_error(); } // Remove the startup notification if we have one. if let Some(startup) = pl_attribs.activation_token.as_ref() { leap!(xconn.remove_activation_token(xwindow, &startup._token)); } // We never want to give the user a broken window, since by then, it's too late to handle. let window = leap!(xconn.sync_with_server().map(|_| window)); Ok(window) } /// Embed this window into a parent window. pub(super) fn embed_window(&self) -> Result<(), RootOsError> { let atoms = self.xconn.atoms(); leap!(leap!(self.xconn.change_property( self.xwindow, atoms[_XEMBED], atoms[_XEMBED], xproto::PropMode::REPLACE, &[0u32, 1u32], )) .check()); Ok(()) } pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> { self.shared_state.lock().unwrap() } fn set_pid(&self) -> Result>, X11Error> { let atoms = self.xconn.atoms(); let pid_atom = atoms[_NET_WM_PID]; let client_machine_atom = atoms[WM_CLIENT_MACHINE]; // Get the hostname and the PID. let uname = rustix::system::uname(); let pid = rustix::process::getpid(); self.xconn .change_property( self.xwindow, pid_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, &[pid.as_raw_nonzero().get() as util::Cardinal], )? .ignore_error(); let flusher = self.xconn.change_property( self.xwindow, client_machine_atom, xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, uname.nodename().to_bytes(), ); flusher.map(Some) } fn set_window_types( &self, window_types: Vec, ) -> Result, X11Error> { let atoms = self.xconn.atoms(); let hint_atom = atoms[_NET_WM_WINDOW_TYPE]; let atoms: Vec<_> = window_types .iter() .map(|t| t.as_atom(&self.xconn)) .collect(); self.xconn.change_property( self.xwindow, hint_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), xproto::PropMode::REPLACE, &atoms, ) } pub fn set_theme_inner(&self, theme: Option) -> Result, X11Error> { let atoms = self.xconn.atoms(); let hint_atom = atoms[_GTK_THEME_VARIANT]; let utf8_atom = atoms[UTF8_STRING]; let variant = match theme { Some(Theme::Dark) => "dark", Some(Theme::Light) => "light", None => "dark", }; let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte"); self.xconn.change_property( self.xwindow, hint_atom, utf8_atom, xproto::PropMode::REPLACE, variant.as_bytes(), ) } #[inline] pub fn set_theme(&self, theme: Option) { self.set_theme_inner(theme) .expect("Failed to change window theme") .ignore_error(); self.xconn .flush_requests() .expect("Failed to change window theme"); } fn set_netwm( &self, operation: util::StateOperation, properties: (u32, u32, u32, u32), ) -> Result, X11Error> { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; self.xconn.send_client_msg( self.xwindow, self.root, state_atom, Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY), [ operation as u32, properties.0, properties.1, properties.2, properties.3, ], ) } fn set_fullscreen_hint(&self, fullscreen: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let fullscreen_atom = atoms[_NET_WM_STATE_FULLSCREEN]; let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0)); if fullscreen { // Ensure that the fullscreen window receives input focus to prevent // locking up the user's display. self.xconn .xcb_connection() .set_input_focus( xproto::InputFocus::PARENT, self.xwindow, x11rb::CURRENT_TIME, )? .ignore_error(); } flusher } fn set_fullscreen_inner( &self, fullscreen: Option, ) -> Result>, X11Error> { let mut shared_state_lock = self.shared_state_lock(); match shared_state_lock.visibility { // Setting fullscreen on a window that is not visible will generate an error. Visibility::No | Visibility::YesWait => { shared_state_lock.desired_fullscreen = Some(fullscreen); return Ok(None); } Visibility::Yes => (), } let old_fullscreen = shared_state_lock.fullscreen.clone(); if old_fullscreen == fullscreen { return Ok(None); } shared_state_lock.fullscreen = fullscreen.clone(); match (&old_fullscreen, &fullscreen) { // Store the desktop video mode before entering exclusive // fullscreen, so we can restore it upon exit, as XRandR does not // provide a mechanism to set this per app-session or restore this // to the desktop video mode as macOS and Windows do (&None, &Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode)))) | ( &Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode))), ) => { let monitor = video_mode.monitor.as_ref().unwrap(); shared_state_lock.desktop_video_mode = Some(( monitor.id, self.xconn .get_crtc_mode(monitor.id) .expect("Failed to get desktop video mode"), )); } // Restore desktop video mode upon exiting exclusive fullscreen (&Some(Fullscreen::Exclusive(_)), &None) | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap(); self.xconn .set_crtc_config(monitor_id, mode_id) .expect("failed to restore desktop video mode"); } _ => (), } drop(shared_state_lock); match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); let mut shared_state_lock = self.shared_state_lock(); if let Some(position) = shared_state_lock.restore_position.take() { drop(shared_state_lock); self.set_position_inner(position.0, position.1) .expect_then_ignore_error("Failed to restore window position"); } flusher.map(Some) } Some(fullscreen) => { let (video_mode, monitor) = match fullscreen { Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode)) => { (Some(video_mode), video_mode.monitor.clone().unwrap()) } Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => { (None, monitor) } Fullscreen::Borderless(None) => { (None, self.shared_state_lock().last_monitor.clone()) } #[cfg(wayland_platform)] _ => unreachable!(), }; // Don't set fullscreen on an invalid dummy monitor handle if monitor.is_dummy() { return Ok(None); } if let Some(video_mode) = video_mode { // FIXME: this is actually not correct if we're setting the // video mode to a resolution higher than the current // desktop resolution, because XRandR does not automatically // reposition the monitors to the right and below this // monitor. // // What ends up happening is we will get the fullscreen // window showing up on those monitors as well, because // their virtual position now overlaps with the monitor that // we just made larger.. // // It'd be quite a bit of work to handle this correctly (and // nobody else seems to bother doing this correctly either), // so we're just leaving this broken. Fixing this would // involve storing all CRTCs upon entering fullscreen, // restoring them upon exit, and after entering fullscreen, // repositioning displays to the right and below this // display. I think there would still be edge cases that are // difficult or impossible to handle correctly, e.g. what if // a new monitor was plugged in while in fullscreen? // // I think we might just want to disallow setting the video // mode higher than the current desktop video mode (I'm sure // this will make someone unhappy, but it's very unusual for // games to want to do this anyway). self.xconn .set_crtc_config(monitor.id, video_mode.native_mode) .expect("failed to set video mode"); } let window_position = self.outer_position_physical(); self.shared_state_lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); self.set_position_inner(monitor_origin.0, monitor_origin.1) .expect_then_ignore_error("Failed to set window position"); self.set_fullscreen_hint(true).map(Some) } } } #[inline] pub(crate) fn fullscreen(&self) -> Option { let shared_state = self.shared_state_lock(); shared_state .desired_fullscreen .clone() .unwrap_or_else(|| shared_state.fullscreen.clone()) } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { if let Some(flusher) = self .set_fullscreen_inner(fullscreen) .expect("Failed to change window fullscreen state") { flusher .check() .expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } } // Called by EventProcessor when a VisibilityNotify event is received pub(crate) fn visibility_notify(&self) { let mut shared_state = self.shared_state_lock(); match shared_state.visibility { Visibility::No => self .xconn .xcb_connection() .unmap_window(self.xwindow) .expect_then_ignore_error("Failed to unmap window"), Visibility::Yes => (), Visibility::YesWait => { shared_state.visibility = Visibility::Yes; if let Some(fullscreen) = shared_state.desired_fullscreen.take() { drop(shared_state); self.set_fullscreen(fullscreen); } } } } pub fn current_monitor(&self) -> Option { Some(self.shared_state_lock().last_monitor.clone()) } pub fn available_monitors(&self) -> Vec { self.xconn .available_monitors() .expect("Failed to get available monitors") } pub fn primary_monitor(&self) -> Option { Some( self.xconn .primary_monitor() .expect("Failed to get primary monitor"), ) } #[inline] pub fn is_minimized(&self) -> Option { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; let state = self.xconn.get_property( self.xwindow, state_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ); let hidden_atom = atoms[_NET_WM_STATE_HIDDEN]; Some(match state { Ok(atoms) => atoms .iter() .any(|atom: &xproto::Atom| *atom as xproto::Atom == hidden_atom), _ => false, }) } /// Refresh the API for the given monitor. #[inline] pub(super) fn refresh_dpi_for_monitor( &self, new_monitor: &X11MonitorHandle, maybe_prev_scale_factor: Option, mut callback: impl FnMut(Event), ) { // Check if the self is on this monitor let monitor = self.shared_state_lock().last_monitor.clone(); if monitor.name == new_monitor.name { let (width, height) = self.inner_size_physical(); let (new_width, new_height) = self.adjust_for_dpi( // If we couldn't determine the previous scale // factor (e.g., because all monitors were closed // before), just pick whatever the current monitor // has set as a baseline. maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), new_monitor.scale_factor, width, height, &self.shared_state_lock(), ); let window_id = crate::window::WindowId(self.id()); let old_inner_size = PhysicalSize::new(width, height); let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); callback(Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor: new_monitor.scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), }, }); let new_inner_size = *inner_size.lock().unwrap(); drop(inner_size); if new_inner_size != old_inner_size { let (new_width, new_height) = new_inner_size.into(); self.request_inner_size_physical(new_width, new_height); } } } fn set_minimized_inner(&self, minimized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); if minimized { let root_window = self.xconn.default_root().root; self.xconn.send_client_msg( self.xwindow, root_window, atoms[WM_CHANGE_STATE], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [3u32, 0, 0, 0, 0], ) } else { self.xconn.send_client_msg( self.xwindow, self.root, atoms[_NET_ACTIVE_WINDOW], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [1, x11rb::CURRENT_TIME, 0, 0, 0], ) } } #[inline] pub fn set_minimized(&self, minimized: bool) { self.set_minimized_inner(minimized) .expect_then_ignore_error("Failed to change window minimization"); self.xconn .flush_requests() .expect("Failed to change window minimization"); } #[inline] pub fn is_maximized(&self) -> bool { let atoms = self.xconn.atoms(); let state_atom = atoms[_NET_WM_STATE]; let state = self.xconn.get_property( self.xwindow, state_atom, xproto::Atom::from(xproto::AtomEnum::ATOM), ); let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; match state { Ok(atoms) => { let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom); let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom); horz_maximized && vert_maximized } _ => false, } } fn set_maximized_inner(&self, maximized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0)) } #[inline] pub fn set_maximized(&self, maximized: bool) { self.set_maximized_inner(maximized) .expect_then_ignore_error("Failed to change window maximization"); self.xconn .flush_requests() .expect("Failed to change window maximization"); self.invalidate_cached_frame_extents(); } fn set_title_inner(&self, title: &str) -> Result, X11Error> { let atoms = self.xconn.atoms(); let title = CString::new(title).expect("Window title contained null byte"); self.xconn .change_property( self.xwindow, xproto::Atom::from(xproto::AtomEnum::WM_NAME), xproto::Atom::from(xproto::AtomEnum::STRING), xproto::PropMode::REPLACE, title.as_bytes(), )? .ignore_error(); self.xconn.change_property( self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING], xproto::PropMode::REPLACE, title.as_bytes(), ) } #[inline] pub fn set_title(&self, title: &str) { self.set_title_inner(title) .expect_then_ignore_error("Failed to set window title"); self.xconn .flush_requests() .expect("Failed to set window title"); } #[inline] pub fn set_transparent(&self, _transparent: bool) {} #[inline] pub fn set_blur(&self, _blur: bool) {} fn set_decorations_inner(&self, decorations: bool) -> Result, X11Error> { self.shared_state_lock().is_decorated = decorations; let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_decorations(decorations); self.xconn.set_motif_hints(self.xwindow, &hints) } #[inline] pub fn set_decorations(&self, decorations: bool) { self.set_decorations_inner(decorations) .expect_then_ignore_error("Failed to set decoration state"); self.xconn .flush_requests() .expect("Failed to set decoration state"); self.invalidate_cached_frame_extents(); } #[inline] pub fn is_decorated(&self) -> bool { self.shared_state_lock().is_decorated } fn set_maximizable_inner(&self, maximizable: bool) -> Result, X11Error> { let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_maximizable(maximizable); self.xconn.set_motif_hints(self.xwindow, &hints) } fn toggle_atom(&self, atom_name: AtomName, enable: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); let atom = atoms[atom_name]; self.set_netwm(enable.into(), (atom, 0, 0, 0)) } fn set_window_level_inner(&self, level: WindowLevel) -> Result, X11Error> { self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)? .ignore_error(); self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom) } #[inline] pub fn set_window_level(&self, level: WindowLevel) { self.set_window_level_inner(level) .expect_then_ignore_error("Failed to set window-level state"); self.xconn .flush_requests() .expect("Failed to set window-level state"); } fn set_icon_inner(&self, icon: PlatformIcon) -> Result, X11Error> { let atoms = self.xconn.atoms(); let icon_atom = atoms[_NET_WM_ICON]; let data = icon.to_cardinals(); self.xconn.change_property( self.xwindow, icon_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, data.as_slice(), ) } fn unset_icon_inner(&self) -> Result, X11Error> { let atoms = self.xconn.atoms(); let icon_atom = atoms[_NET_WM_ICON]; let empty_data: [util::Cardinal; 0] = []; self.xconn.change_property( self.xwindow, icon_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL), xproto::PropMode::REPLACE, &empty_data, ) } #[inline] pub(crate) fn set_window_icon(&self, icon: Option) { match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), } .expect_then_ignore_error("Failed to set icons"); self.xconn.flush_requests().expect("Failed to set icons"); } #[inline] pub fn set_visible(&self, visible: bool) { let mut shared_state = self.shared_state_lock(); match (visible, shared_state.visibility) { (true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => { return } _ => (), } if visible { self.xconn .xcb_connection() .map_window(self.xwindow) .expect_then_ignore_error("Failed to call `xcb_map_window`"); self.xconn .xcb_connection() .configure_window( self.xwindow, &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE), ) .expect_then_ignore_error("Failed to call `xcb_configure_window`"); self.xconn .flush_requests() .expect("Failed to call XMapRaised"); shared_state.visibility = Visibility::YesWait; } else { self.xconn .xcb_connection() .unmap_window(self.xwindow) .expect_then_ignore_error("Failed to call `xcb_unmap_window`"); self.xconn .flush_requests() .expect("Failed to call XUnmapWindow"); shared_state.visibility = Visibility::No; } } #[inline] pub fn is_visible(&self) -> Option { Some(self.shared_state_lock().visibility == Visibility::Yes) } fn update_cached_frame_extents(&self) { let extents = self .xconn .get_frame_extents_heuristic(self.xwindow, self.root); self.shared_state_lock().frame_extents = Some(extents); } pub(crate) fn invalidate_cached_frame_extents(&self) { self.shared_state_lock().frame_extents.take(); } pub(crate) fn outer_position_physical(&self) -> (i32, i32) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (x, y) = self.inner_position_physical(); extents.inner_pos_to_outer(x, y) } else { self.update_cached_frame_extents(); self.outer_position_physical() } } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (x, y) = self.inner_position_physical(); Ok(extents.inner_pos_to_outer(x, y).into()) } else { self.update_cached_frame_extents(); self.outer_position() } } pub(crate) fn inner_position_physical(&self) -> (i32, i32) { // This should be okay to unwrap since the only error XTranslateCoordinates can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn .translate_coords(self.xwindow, self.root) .map(|coords| (coords.dst_x.into(), coords.dst_y.into())) .unwrap() } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { Ok(self.inner_position_physical().into()) } pub(crate) fn set_position_inner( &self, mut x: i32, mut y: i32, ) -> Result, X11Error> { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { x += cast_dimension_to_hint(extents.frame_extents.left); y += cast_dimension_to_hint(extents.frame_extents.top); } else { self.update_cached_frame_extents(); return self.set_position_inner(x, y); } } self.xconn .xcb_connection() .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y)) .map_err(Into::into) } pub(crate) fn set_position_physical(&self, x: i32, y: i32) { self.set_position_inner(x, y) .expect_then_ignore_error("Failed to call `XMoveWindow`"); } #[inline] pub fn set_outer_position(&self, position: Position) { let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_position_physical(x, y); } pub(crate) fn inner_size_physical(&self) -> (u32, u32) { // This should be okay to unwrap since the only error XGetGeometry can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn .get_geometry(self.xwindow) .map(|geo| (geo.width.into(), geo.height.into())) .unwrap() } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.inner_size_physical().into() } #[inline] pub fn outer_size(&self) -> PhysicalSize { let extents = self.shared_state_lock().frame_extents.clone(); if let Some(extents) = extents { let (width, height) = self.inner_size_physical(); extents.inner_size_to_outer(width, height).into() } else { self.update_cached_frame_extents(); self.outer_size() } } pub(crate) fn request_inner_size_physical(&self, width: u32, height: u32) { self.xconn .xcb_connection() .configure_window( self.xwindow, &xproto::ConfigureWindowAux::new() .width(width) .height(height), ) .expect_then_ignore_error("Failed to call `xcb_configure_window`"); self.xconn .flush_requests() .expect("Failed to call XResizeWindow"); // cursor_hittest needs to be reapplied after each window resize. if self.shared_state_lock().cursor_hittest.unwrap_or(false) { let _ = self.set_cursor_hittest(true); } } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let size = size.to_physical::(scale_factor).into(); if !self.shared_state_lock().is_resizable { self.update_normal_hints(|normal_hints| { normal_hints.min_size = Some(size); normal_hints.max_size = Some(size); }) .expect("Failed to call `XSetWMNormalHints`"); } self.request_inner_size_physical(size.0 as u32, size.1 as u32); None } fn update_normal_hints(&self, callback: F) -> Result<(), X11Error> where F: FnOnce(&mut WmSizeHints), { let mut normal_hints = WmSizeHints::get( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )? .reply()? .unwrap_or_default(); callback(&mut normal_hints); normal_hints .set( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, )? .ignore_error(); Ok(()) } pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| { normal_hints.min_size = dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { self.shared_state_lock().min_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_min_inner_size_physical(physical_dimensions); } pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { self.update_normal_hints(|normal_hints| { normal_hints.max_size = dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { self.shared_state_lock().max_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_max_inner_size_physical(physical_dimensions); } #[inline] pub fn resize_increments(&self) -> Option> { WmSizeHints::get( self.xconn.xcb_connection(), self.xwindow as xproto::Window, xproto::AtomEnum::WM_NORMAL_HINTS, ) .ok() .and_then(|cookie| cookie.reply().ok()) .flatten() .and_then(|hints| hints.size_increment) .map(|(width, height)| (width as u32, height as u32).into()) } #[inline] pub fn set_resize_increments(&self, increments: Option) { self.shared_state_lock().resize_increments = increments; let physical_increments = increments.map(|increments| cast_size_to_hint(increments, self.scale_factor())); self.update_normal_hints(|hints| hints.size_increment = physical_increments) .expect("Failed to call `XSetWMNormalHints`"); } pub(crate) fn adjust_for_dpi( &self, old_scale_factor: f64, new_scale_factor: f64, width: u32, height: u32, shared_state: &SharedState, ) -> (u32, u32) { let scale_factor = new_scale_factor / old_scale_factor; self.update_normal_hints(|normal_hints| { let dpi_adjuster = |size: Size| -> (i32, i32) { cast_size_to_hint(size, scale_factor) }; let max_size = shared_state.max_inner_size.map(dpi_adjuster); let min_size = shared_state.min_inner_size.map(dpi_adjuster); let resize_increments = shared_state.resize_increments.map(dpi_adjuster); let base_size = shared_state.base_size.map(dpi_adjuster); normal_hints.max_size = max_size; normal_hints.min_size = min_size; normal_hints.size_increment = resize_increments; normal_hints.base_size = base_size; }) .expect("Failed to update normal hints"); let new_width = (width as f64 * scale_factor).round() as u32; let new_height = (height as f64 * scale_factor).round() as u32; (new_width, new_height) } pub fn set_resizable(&self, resizable: bool) { if util::wm_name_is_one_of(&["Xfwm4"]) { // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected. // This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose // the lesser of two evils and do nothing. warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); return; } let (min_size, max_size) = if resizable { let shared_state_lock = self.shared_state_lock(); ( shared_state_lock.min_inner_size, shared_state_lock.max_inner_size, ) } else { let window_size = Some(Size::from(self.inner_size())); (window_size, window_size) }; self.shared_state_lock().is_resizable = resizable; self.set_maximizable_inner(resizable) .expect_then_ignore_error("Failed to call `XSetWMNormalHints`"); let scale_factor = self.scale_factor(); let min_inner_size = min_size.map(|size| cast_size_to_hint(size, scale_factor)); let max_inner_size = max_size.map(|size| cast_size_to_hint(size, scale_factor)); self.update_normal_hints(|normal_hints| { normal_hints.min_size = min_inner_size; normal_hints.max_size = max_inner_size; }) .expect("Failed to call `XSetWMNormalHints`"); } #[inline] pub fn is_resizable(&self) -> bool { self.shared_state_lock().is_resizable } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } #[allow(dead_code)] #[inline] pub fn xlib_display(&self) -> *mut c_void { self.xconn.display as _ } #[allow(dead_code)] #[inline] pub fn xlib_window(&self) -> c_ulong { self.xwindow as ffi::Window } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { let old_cursor = replace(&mut *self.cursor.lock().unwrap(), cursor); #[allow(clippy::mutex_atomic)] if cursor != old_cursor && *self.cursor_visible.lock().unwrap() { self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); } } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); if mode == *grabbed_lock { return Ok(()); } // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. // Therefore, this is common to both codepaths. self.xconn .xcb_connection() .ungrab_pointer(x11rb::CURRENT_TIME) .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`"); let result = match mode { CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) }), CursorGrabMode::Confined => { let result = { self.xconn .xcb_connection() .grab_pointer( true as _, self.xwindow, xproto::EventMask::BUTTON_PRESS | xproto::EventMask::BUTTON_RELEASE | xproto::EventMask::ENTER_WINDOW | xproto::EventMask::LEAVE_WINDOW | xproto::EventMask::POINTER_MOTION | xproto::EventMask::POINTER_MOTION_HINT | xproto::EventMask::BUTTON1_MOTION | xproto::EventMask::BUTTON2_MOTION | xproto::EventMask::BUTTON3_MOTION | xproto::EventMask::BUTTON4_MOTION | xproto::EventMask::BUTTON5_MOTION | xproto::EventMask::KEYMAP_STATE, xproto::GrabMode::ASYNC, xproto::GrabMode::ASYNC, self.xwindow, 0u32, x11rb::CURRENT_TIME, ) .expect("Failed to call `grab_pointer`") .reply() .expect("Failed to receive reply from `grab_pointer`") }; match result.status { xproto::GrabStatus::SUCCESS => Ok(()), xproto::GrabStatus::ALREADY_GRABBED => { Err("Cursor could not be confined: already confined by another client") } xproto::GrabStatus::INVALID_TIME => { Err("Cursor could not be confined: invalid time") } xproto::GrabStatus::NOT_VIEWABLE => { Err("Cursor could not be confined: confine location not viewable") } xproto::GrabStatus::FROZEN => { Err("Cursor could not be confined: frozen by another client") } _ => unreachable!(), } .map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err)))) } CursorGrabMode::Locked => { return Err(ExternalError::NotSupported(NotSupportedError::new())); } }; if result.is_ok() { *grabbed_lock = mode; } result } #[inline] pub fn set_cursor_visible(&self, visible: bool) { #[allow(clippy::mutex_atomic)] let mut visible_lock = self.cursor_visible.lock().unwrap(); if visible == *visible_lock { return; } let cursor = if visible { Some(*self.cursor.lock().unwrap()) } else { None }; *visible_lock = visible; drop(visible_lock); self.xconn.set_cursor_icon(self.xwindow, cursor); } #[inline] pub fn scale_factor(&self) -> f64 { self.shared_state_lock().last_monitor.scale_factor } pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { { self.xconn .xcb_connection() .warp_pointer(x11rb::NONE, self.xwindow, 0, 0, 0, 0, x as _, y as _) .map_err(|e| { ExternalError::Os(os_error!(OsError::XError(X11Error::from(e).into()))) })?; self.xconn.flush_requests().map_err(|e| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(e).into()))) }) } } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_cursor_position_physical(x, y) } #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let mut rectangles: Vec = Vec::new(); if hittest { let size = self.inner_size(); rectangles.push(Rectangle { x: 0, y: 0, width: size.width as u16, height: size.height as u16, }) } let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles) .map_err(|_e| ExternalError::Ignored)?; self.xconn .xcb_connection() .xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region()) .map_err(|_e| ExternalError::Ignored)?; self.shared_state_lock().cursor_hittest = Some(hittest); Ok(()) } /// Moves the window while it is being dragged. pub fn drag_window(&self) -> Result<(), ExternalError> { self.drag_initiate(util::MOVERESIZE_MOVE) } #[inline] pub fn show_window_menu(&self, _position: Position) {} /// Resizes the window while it is being dragged. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.drag_initiate(match direction { ResizeDirection::East => util::MOVERESIZE_RIGHT, ResizeDirection::North => util::MOVERESIZE_TOP, ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT, ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT, ResizeDirection::South => util::MOVERESIZE_BOTTOM, ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT, ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT, ResizeDirection::West => util::MOVERESIZE_LEFT, }) } /// Initiates a drag operation while the left mouse button is pressed. fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> { let pointer = self .xconn .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; let window = self.inner_position().map_err(ExternalError::NotSupported)?; let atoms = self.xconn.atoms(); let message = atoms[_NET_WM_MOVERESIZE]; // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` // if the cursor isn't currently grabbed let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); self.xconn .xcb_connection() .ungrab_pointer(x11rb::CURRENT_TIME) .map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::from(err).into()))) })? .ignore_error(); self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) })?; *grabbed_lock = CursorGrabMode::None; // we keep the lock until we are done self.xconn .send_client_msg( self.xwindow, self.root, message, Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [ (window.x as u32 + xinput_fp1616_to_float(pointer.win_x) as u32), (window.y as u32 + xinput_fp1616_to_float(pointer.win_y) as u32), action.try_into().unwrap(), 1, // Button 1 1, ], ) .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) }) } #[inline] pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) { let (x, y) = spot.to_physical::(self.scale_factor()).into(); let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position( self.xwindow as ffi::Window, x, y, )); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let _ = self .ime_sender .lock() .unwrap() .send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed)); } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) { let atoms = self.xconn.atoms(); let state_atom = atoms[WM_STATE]; let state_type_atom = atoms[CARD32]; let is_minimized = if let Ok(state) = self.xconn .get_property::(self.xwindow, state_atom, state_type_atom) { state.contains(&super::ICONIC_STATE) } else { false }; let is_visible = match self.shared_state_lock().visibility { Visibility::Yes => true, Visibility::YesWait | Visibility::No => false, }; if is_visible && !is_minimized { self.xconn .send_client_msg( self.xwindow, self.root, atoms[_NET_ACTIVE_WINDOW], Some( xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY, ), [1, x11rb::CURRENT_TIME, 0, 0, 0], ) .expect_then_ignore_error("Failed to send client message"); if let Err(e) = self.xconn.flush_requests() { log::error!( "`flush` returned an error when focusing the window. Error was: {}", e ); } } } #[inline] pub fn request_user_attention(&self, request_type: Option) { let mut wm_hints = WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window) .ok() .and_then(|cookie| cookie.reply().ok()) .flatten() .unwrap_or_default(); wm_hints.urgent = request_type.is_some(); wm_hints .set(self.xconn.xcb_connection(), self.xwindow as xproto::Window) .expect_then_ignore_error("Failed to set WM hints"); } #[inline] pub(crate) fn generate_activation_token(&self) -> Result { // Get the title from the WM_NAME property. let atoms = self.xconn.atoms(); let title = { let title_bytes = self .xconn .get_property(self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING]) .expect("Failed to get title"); String::from_utf8(title_bytes).expect("Bad title") }; // Get the activation token and then put it in the event queue. let token = self.xconn.request_activation_token(&title)?; Ok(token) } #[inline] pub fn request_activation_token(&self) -> Result { let serial = AsyncRequestSerial::get(); self.activation_sender .send((self.id(), serial)) .expect("activation token channel should never be closed"); Ok(serial) } #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow as _) } #[inline] pub fn request_redraw(&self) { self.redraw_sender .send(WindowId(self.xwindow as _)) .unwrap(); } #[inline] pub fn pre_present_notify(&self) { // TODO timer } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::XlibHandle::empty(); window_handle.display = self.xlib_display(); window_handle.window = self.xlib_window(); window_handle.visual_id = self.visual as c_ulong; rwh_04::RawWindowHandle::Xlib(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::XlibWindowHandle::empty(); window_handle.window = self.xlib_window(); window_handle.visual_id = self.visual as c_ulong; window_handle.into() } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { let mut display_handle = rwh_05::XlibDisplayHandle::empty(); display_handle.display = self.xlib_display(); display_handle.screen = self.screen_id; display_handle.into() } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window()); window_handle.visual_id = self.visual as c_ulong; Ok(window_handle.into()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::XlibDisplayHandle::new( Some( std::ptr::NonNull::new(self.xlib_display()) .expect("display pointer should never be null"), ), self.screen_id, ) .into()) } #[inline] pub fn theme(&self) -> Option { None } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn has_focus(&self) -> bool { self.shared_state_lock().has_focus } pub fn title(&self) -> String { String::new() } } /// Cast a dimension value into a hinted dimension for `WmSizeHints`, clamping if too large. fn cast_dimension_to_hint(val: u32) -> i32 { val.try_into().unwrap_or(i32::MAX) } /// Use the above strategy to cast a physical size into a hinted size. fn cast_physical_size_to_hint(size: PhysicalSize) -> (i32, i32) { let PhysicalSize { width, height } = size; ( cast_dimension_to_hint(width), cast_dimension_to_hint(height), ) } /// Use the above strategy to cast a size into a hinted size. fn cast_size_to_hint(size: Size, scale_factor: f64) -> (i32, i32) { match size { Size::Physical(size) => cast_physical_size_to_hint(size), Size::Logical(size) => size.to_physical::(scale_factor).into(), } } winit-0.29.15/src/platform_impl/linux/x11/xdisplay.rs000064400000000000000000000260221046102023000205450ustar 00000000000000use std::{ collections::HashMap, error::Error, fmt, ptr, sync::{ atomic::{AtomicU32, Ordering}, Arc, Mutex, RwLock, RwLockReadGuard, }, }; use crate::window::CursorIcon; use super::{atoms::Atoms, ffi, monitor::MonitorHandle}; use x11rb::{ connection::Connection, protocol::{ randr::ConnectionExt as _, xproto::{self, ConnectionExt}, }, resource_manager, xcb_ffi::XCBConnection, }; /// A connection to an X server. pub struct XConnection { pub xlib: ffi::Xlib, pub xcursor: ffi::Xcursor, // TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together // for some reason. pub xinput2: ffi::XInput2, pub display: *mut ffi::Display, /// The manager for the XCB connection. /// /// The `Option` ensures that we can drop it before we close the `Display`. xcb: Option, /// The atoms used by `winit`. /// /// This is a large structure, so I've elected to Box it to make accessing the fields of /// this struct easier. Feel free to unbox it if you like kicking puppies. atoms: Box, /// The index of the default screen. default_screen: usize, /// The last timestamp received by this connection. timestamp: AtomicU32, /// List of monitor handles. pub monitor_handles: Mutex>>, /// The resource database. database: RwLock, /// RandR version. randr_version: (u32, u32), /// Atom for the XSettings screen. xsettings_screen: Option, pub latest_error: Mutex>, pub cursor_cache: Mutex, ffi::Cursor>>, } unsafe impl Send for XConnection {} unsafe impl Sync for XConnection {} pub type XErrorHandler = Option std::os::raw::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 xlib_xcb = ffi::Xlib_xcb::open()?; let xinput2 = ffi::XInput2::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 }; // Open the x11rb XCB connection. let xcb = { // Get a pointer to the underlying XCB connection let xcb_connection = unsafe { (xlib_xcb.XGetXCBConnection)(display as *mut ffi::Display) }; assert!(!xcb_connection.is_null()); // Wrap the XCB connection in an x11rb XCB connection let conn = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection.cast(), false) }; conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))? }; // Get the default screen. let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize; // Load the database. let database = resource_manager::new_from_default(&xcb) .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; // Load the RandR version. let randr_version = xcb .randr_query_version(1, 3) .expect("failed to request XRandR version") .reply() .expect("failed to query XRandR version"); let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen); if xsettings_screen.is_none() { log::warn!("error setting XSETTINGS; Xft options won't reload automatically") } // Fetch atoms. let atoms = Atoms::new(&xcb) .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))? .reply() .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; Ok(XConnection { xlib, xcursor, xinput2, display, xcb: Some(xcb), atoms: Box::new(atoms), default_screen, timestamp: AtomicU32::new(0), latest_error: Mutex::new(None), monitor_handles: Mutex::new(None), database: RwLock::new(database), cursor_cache: Default::default(), randr_version: (randr_version.major_version, randr_version.minor_version), xsettings_screen, }) } fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option { // Fetch the _XSETTINGS_S[screen number] atom. let xsettings_screen = xcb .intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes()) .ok()? .reply() .ok()? .atom; // Get PropertyNotify events from the XSETTINGS window. // TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window // in order to accomodate for a changed window here. let selector_window = xcb .get_selection_owner(xsettings_screen) .ok()? .reply() .ok()? .owner; xcb.change_window_attributes( selector_window, &xproto::ChangeWindowAttributesAux::new() .event_mask(xproto::EventMask::PROPERTY_CHANGE), ) .ok()? .check() .ok()?; Some(xsettings_screen) } /// 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().unwrap().take(); if let Some(error) = error { Err(error) } else { Ok(()) } } #[inline] pub fn randr_version(&self) -> (u32, u32) { self.randr_version } /// Get the underlying XCB connection. #[inline] pub fn xcb_connection(&self) -> &XCBConnection { self.xcb .as_ref() .expect("xcb_connection somehow called after drop?") } /// Get the list of atoms. #[inline] pub fn atoms(&self) -> &Atoms { &self.atoms } /// Get the index of the default screen. #[inline] pub fn default_screen_index(&self) -> usize { self.default_screen } /// Get the default screen. #[inline] pub fn default_root(&self) -> &xproto::Screen { &self.xcb_connection().setup().roots[self.default_screen] } /// Get the resource database. #[inline] pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> { self.database.read().unwrap_or_else(|e| e.into_inner()) } /// Reload the resource database. #[inline] pub fn reload_database(&self) -> Result<(), super::X11Error> { let database = resource_manager::new_from_default(self.xcb_connection())?; *self.database.write().unwrap_or_else(|e| e.into_inner()) = database; Ok(()) } /// Get the latest timestamp. #[inline] pub fn timestamp(&self) -> u32 { self.timestamp.load(Ordering::Relaxed) } /// Set the last witnessed timestamp. #[inline] pub fn set_timestamp(&self, timestamp: u32) { // Store the timestamp in the slot if it's greater than the last one. let mut last_timestamp = self.timestamp.load(Ordering::Relaxed); loop { let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32); if wrapping_sub(timestamp, last_timestamp) <= 0 { break; } match self.timestamp.compare_exchange( last_timestamp, timestamp, Ordering::Relaxed, Ordering::Relaxed, ) { Ok(_) => break, Err(x) => last_timestamp = x, } } } /// Get the atom for Xsettings. #[inline] pub fn xsettings_screen(&self) -> Option { self.xsettings_screen } } 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) { self.xcb = None; 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. /// We encountered an error while converting the connection to XCB. XcbConversionError(Arc), } 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", XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB", } } } impl Error for XNotSupported { #[inline] fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { XNotSupported::LibraryOpenError(ref err) => Some(err), XNotSupported::XcbConversionError(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()) } } /// A newtype wrapper around a `ConnectError` that can't be accessed by downstream libraries. /// /// Without this, `x11rb` would become a public dependency. #[derive(Debug)] struct WrapConnectError(x11rb::rust_connection::ConnectError); impl fmt::Display for WrapConnectError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl Error for WrapConnectError { // We can't implement `source()` here or otherwise risk exposing `x11rb`. } winit-0.29.15/src/platform_impl/linux/x11/xsettings.rs000064400000000000000000000220011046102023000207310ustar 00000000000000//! Parser for the xsettings data format. //! //! Some of this code is referenced from [here]. //! //! [here]: https://github.com/derat/xsettingsd use std::iter; use std::num::NonZeroUsize; use x11rb::protocol::xproto::{self, ConnectionExt}; use super::{atoms::*, XConnection}; type Result = core::result::Result; const DPI_NAME: &[u8] = b"Xft/DPI"; const DPI_MULTIPLIER: f64 = 1024.0; const LITTLE_ENDIAN: u8 = b'l'; const BIG_ENDIAN: u8 = b'B'; impl XConnection { /// Get the DPI from XSettings. pub(crate) fn xsettings_dpi( &self, xsettings_screen: xproto::Atom, ) -> core::result::Result, super::X11Error> { let atoms = self.atoms(); // Get the current owner of the screen's settings. let owner = self .xcb_connection() .get_selection_owner(xsettings_screen)? .reply()?; // Read the _XSETTINGS_SETTINGS property. let data: Vec = self.get_property( owner.owner, atoms[_XSETTINGS_SETTINGS], atoms[_XSETTINGS_SETTINGS], )?; // Parse the property. let dpi_setting = read_settings(&data)? .find(|res| res.as_ref().map_or(true, |s| s.name == DPI_NAME)) .transpose()?; if let Some(dpi_setting) = dpi_setting { let base_dpi = match dpi_setting.data { SettingData::Integer(dpi) => dpi as f64, SettingData::String(_) => { return Err(ParserError::BadType(SettingType::String).into()) } SettingData::Color(_) => { return Err(ParserError::BadType(SettingType::Color).into()) } }; Ok(Some(base_dpi / DPI_MULTIPLIER)) } else { Ok(None) } } } /// Read over the settings in the block of data. fn read_settings(data: &[u8]) -> Result>> + '_> { // Create a parser. This automatically parses the first 8 bytes for metadata. let mut parser = Parser::new(data)?; // Read the total number of settings. let total_settings = parser.i32()?; // Iterate over the settings. let iter = iter::repeat_with(move || Setting::parse(&mut parser)).take(total_settings as usize); Ok(iter) } /// A setting in the settings list. struct Setting<'a> { /// The name of the setting. name: &'a [u8], /// The data contained in the setting. data: SettingData<'a>, } /// The data contained in a setting. enum SettingData<'a> { Integer(i32), String(#[allow(dead_code)] &'a [u8]), Color(#[allow(dead_code)] [i16; 4]), } impl<'a> Setting<'a> { /// Parse a new `SettingData`. fn parse(parser: &mut Parser<'a>) -> Result { // Read the type. let ty: SettingType = parser.i8()?.try_into()?; // Read another byte of padding. parser.advance(1)?; // Read the name of the setting. let name_len = parser.i16()?; let name = parser.advance(name_len as usize)?; parser.pad(name.len(), 4)?; // Ignore the serial number. parser.advance(4)?; let data = match ty { SettingType::Integer => { // Read a 32-bit integer. SettingData::Integer(parser.i32()?) } SettingType::String => { // Read the data. let data_len = parser.i32()?; let data = parser.advance(data_len as usize)?; parser.pad(data.len(), 4)?; SettingData::String(data) } SettingType::Color => { // Read i16's of color. let (red, blue, green, alpha) = (parser.i16()?, parser.i16()?, parser.i16()?, parser.i16()?); SettingData::Color([red, blue, green, alpha]) } }; Ok(Setting { name, data }) } } #[derive(Debug)] pub enum SettingType { Integer = 0, String = 1, Color = 2, } impl TryFrom for SettingType { type Error = ParserError; fn try_from(value: i8) -> Result { Ok(match value { 0 => Self::Integer, 1 => Self::String, 2 => Self::Color, x => return Err(ParserError::InvalidType(x)), }) } } /// Parser for the incoming byte stream. struct Parser<'a> { bytes: &'a [u8], endianness: Endianness, } impl<'a> Parser<'a> { /// Create a new parser. fn new(bytes: &'a [u8]) -> Result { let (endianness, bytes) = bytes .split_first() .ok_or_else(|| ParserError::ran_out(1, 0))?; let endianness = match *endianness { BIG_ENDIAN => Endianness::Big, LITTLE_ENDIAN => Endianness::Little, _ => Endianness::native(), }; Ok(Self { // Ignore three bytes of padding and the four-byte serial. bytes: bytes .get(7..) .ok_or_else(|| ParserError::ran_out(7, bytes.len()))?, endianness, }) } /// Get a slice of bytes. fn advance(&mut self, n: usize) -> Result<&'a [u8]> { if n == 0 { return Ok(&[]); } if n > self.bytes.len() { Err(ParserError::ran_out(n, self.bytes.len())) } else { let (part, rem) = self.bytes.split_at(n); self.bytes = rem; Ok(part) } } /// Skip some padding. fn pad(&mut self, size: usize, pad: usize) -> Result<()> { let advance = (pad - (size % pad)) % pad; self.advance(advance)?; Ok(()) } /// Get a single byte. fn i8(&mut self) -> Result { self.advance(1).map(|s| s[0] as i8) } /// Get two bytes. fn i16(&mut self) -> Result { self.advance(2).map(|s| { let bytes: &[u8; 2] = s.try_into().unwrap(); match self.endianness { Endianness::Big => i16::from_be_bytes(*bytes), Endianness::Little => i16::from_le_bytes(*bytes), } }) } /// Get four bytes. fn i32(&mut self) -> Result { self.advance(4).map(|s| { let bytes: &[u8; 4] = s.try_into().unwrap(); match self.endianness { Endianness::Big => i32::from_be_bytes(*bytes), Endianness::Little => i32::from_le_bytes(*bytes), } }) } } /// Endianness of the incoming data. enum Endianness { Little, Big, } impl Endianness { #[cfg(target_endian = "little")] fn native() -> Self { Endianness::Little } #[cfg(target_endian = "big")] fn native() -> Self { Endianness::Big } } /// Parser errors. #[derive(Debug)] pub enum ParserError { /// Ran out of bytes. NoMoreBytes { expected: NonZeroUsize, found: usize, }, /// Invalid type. InvalidType(i8), /// Bad setting type. BadType(SettingType), } impl ParserError { fn ran_out(expected: usize, found: usize) -> ParserError { let expected = NonZeroUsize::new(expected).unwrap(); Self::NoMoreBytes { expected, found } } } #[cfg(test)] mod tests { //! Tests for the XSETTINGS parser. use super::*; const XSETTINGS: &str = include_str!("tests/xsettings.dat"); #[test] fn empty() { let err = match read_settings(&[]) { Ok(_) => panic!(), Err(err) => err, }; match err { ParserError::NoMoreBytes { expected, found } => { assert_eq!(expected.get(), 1); assert_eq!(found, 0); } _ => panic!(), } } #[test] fn parse_xsettings() { let data = XSETTINGS .trim() .split(',') .map(|tok| { let val = tok.strip_prefix("0x").unwrap(); u8::from_str_radix(val, 16).unwrap() }) .collect::>(); let settings = read_settings(&data) .unwrap() .collect::>>() .unwrap(); let dpi = settings.iter().find(|s| s.name == b"Xft/DPI").unwrap(); assert_int(&dpi.data, 96 * 1024); let hinting = settings.iter().find(|s| s.name == b"Xft/Hinting").unwrap(); assert_int(&hinting.data, 1); let rgba = settings.iter().find(|s| s.name == b"Xft/RGBA").unwrap(); assert_string(&rgba.data, "rgb"); let lcd = settings .iter() .find(|s| s.name == b"Xft/Lcdfilter") .unwrap(); assert_string(&lcd.data, "lcddefault"); } fn assert_string(dat: &SettingData<'_>, s: &str) { match dat { SettingData::String(left) => assert_eq!(*left, s.as_bytes()), _ => panic!("invalid data type"), } } fn assert_int(dat: &SettingData<'_>, i: i32) { match dat { SettingData::Integer(left) => assert_eq!(*left, i), _ => panic!("invalid data type"), } } } winit-0.29.15/src/platform_impl/macos/app.rs000064400000000000000000000067311046102023000170270ustar 00000000000000#![allow(clippy::unnecessary_cast)] use icrate::Foundation::NSObject; use objc2::{declare_class, msg_send, mutability, ClassType}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use super::{app_state::AppState, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; declare_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(super) struct WinitApplication; unsafe impl ClassType for WinitApplication { #[inherits(NSResponder, NSObject)] type Super = NSApplication; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitApplication"; } unsafe impl WinitApplication { // 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) #[method(sendEvent:)] fn send_event(&self, event: &NSEvent) { // 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.type_(); let modifier_flags = event.modifierFlags(); if event_type == NSEventType::NSKeyUp && modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask) { if let Some(key_window) = self.keyWindow() { unsafe { key_window.sendEvent(event) }; } } else { maybe_dispatch_device_event(event); unsafe { msg_send![super(self), sendEvent: event] } } } } ); fn maybe_dispatch_device_event(event: &NSEvent) { let event_type = event.type_(); match event_type { NSEventType::NSMouseMoved | NSEventType::NSLeftMouseDragged | NSEventType::NSOtherMouseDragged | NSEventType::NSRightMouseDragged => { let delta_x = event.deltaX() as f64; let delta_y = event.deltaY() as f64; if delta_x != 0.0 { queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x, }); } if delta_y != 0.0 { queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y, }) } if delta_x != 0.0 || delta_y != 0.0 { queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y), }); } } NSEventType::NSLeftMouseDown | NSEventType::NSRightMouseDown | NSEventType::NSOtherMouseDown => { queue_device_event(DeviceEvent::Button { button: event.buttonNumber() as u32, state: ElementState::Pressed, }); } NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => { queue_device_event(DeviceEvent::Button { button: event.buttonNumber() as u32, state: ElementState::Released, }); } _ => (), } } fn queue_device_event(event: DeviceEvent) { let event = Event::DeviceEvent { device_id: DEVICE_ID, event, }; AppState::queue_event(event); } winit-0.29.15/src/platform_impl/macos/app_delegate.rs000064400000000000000000000052461046102023000206610ustar 00000000000000use std::ptr::NonNull; use icrate::Foundation::NSObject; use objc2::declare::{IvarBool, IvarEncode}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType}; use super::app_state::AppState; use super::appkit::NSApplicationActivationPolicy; declare_class!( #[derive(Debug)] pub(super) struct ApplicationDelegate { activation_policy: IvarEncode, default_menu: IvarBool<"_default_menu">, activate_ignoring_other_apps: IvarBool<"_activate_ignoring_other_apps">, } mod ivars; unsafe impl ClassType for ApplicationDelegate { type Super = NSObject; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitApplicationDelegate"; } unsafe impl ApplicationDelegate { #[method(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)] unsafe fn init( this: *mut Self, activation_policy: NSApplicationActivationPolicy, default_menu: bool, activate_ignoring_other_apps: bool, ) -> Option> { let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; this.map(|this| { *this.activation_policy = activation_policy; *this.default_menu = default_menu; *this.activate_ignoring_other_apps = activate_ignoring_other_apps; NonNull::from(this) }) } #[method(applicationDidFinishLaunching:)] fn did_finish_launching(&self, _sender: Option<&AnyObject>) { trace_scope!("applicationDidFinishLaunching:"); AppState::launched( *self.activation_policy, *self.default_menu, *self.activate_ignoring_other_apps, ); } #[method(applicationWillTerminate:)] fn will_terminate(&self, _sender: Option<&AnyObject>) { trace_scope!("applicationWillTerminate:"); // TODO: Notify every window that it will be destroyed, like done in iOS? AppState::internal_exit(); } } ); impl ApplicationDelegate { pub(super) fn new( activation_policy: NSApplicationActivationPolicy, default_menu: bool, activate_ignoring_other_apps: bool, ) -> Id { unsafe { msg_send_id![ Self::alloc(), initWithActivationPolicy: activation_policy, defaultMenu: default_menu, activateIgnoringOtherApps: activate_ignoring_other_apps, ] } } } winit-0.29.15/src/platform_impl/macos/app_state.rs000064400000000000000000000577271046102023000202420ustar 00000000000000use std::{ cell::{RefCell, RefMut}, collections::VecDeque, fmt::{self, Debug}, mem, rc::{Rc, Weak}, sync::{ atomic::{AtomicBool, Ordering}, mpsc, Arc, Mutex, MutexGuard, }, time::Instant, }; use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp}; use icrate::Foundation::{is_main_thread, NSSize}; use objc2::rc::{autoreleasepool, Id}; use once_cell::sync::Lazy; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent}; use super::{ event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow, }; use crate::{ dpi::PhysicalSize, event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, window::WindowId, }; static HANDLER: Lazy = Lazy::new(Default::default); impl Event { fn userify(self) -> Event { self.map_nonuser_event() // `Never` can't be constructed, so the `UserEvent` variant can't // be present here. .unwrap_or_else(|_| unreachable!()) } } pub trait EventHandler: Debug { // Not sure probably it should accept Event<'static, Never> fn handle_nonuser_event(&mut self, event: Event); fn handle_user_events(&mut self); } pub(crate) type Callback = RefCell, &RootWindowTarget)>; struct EventLoopHandler { callback: Weak>, window_target: Rc>, receiver: Rc>, } impl EventLoopHandler { fn with_callback(&mut self, f: F) where F: FnOnce(&mut EventLoopHandler, RefMut<'_, dyn FnMut(Event, &RootWindowTarget)>), { // The `NSApp` and our `HANDLER` are global state and so it's possible that // we could get a delegate callback after the application has exit an // `EventLoop`. If the loop has been exit then our weak `self.callback` // will fail to upgrade. // // We don't want to panic or output any verbose logging if we fail to // upgrade the weak reference since it might be valid that the application // re-starts the `NSApp` after exiting a Winit `EventLoop` if let Some(callback) = self.callback.upgrade() { let callback = callback.borrow_mut(); (f)(self, callback); } } } 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) { self.with_callback(|this, mut callback| { (callback)(event.userify(), &this.window_target); }); } fn handle_user_events(&mut self) { self.with_callback(|this, mut callback| { for event in this.receiver.try_iter() { (callback)(Event::UserEvent(event), &this.window_target); } }); } } #[derive(Debug)] enum EventWrapper { StaticEvent(Event), ScaleFactorChanged { window: Id, suggested_size: PhysicalSize, scale_factor: f64, }, } #[derive(Default)] struct Handler { stop_app_on_launch: AtomicBool, stop_app_before_wait: AtomicBool, stop_app_after_wait: AtomicBool, stop_app_on_redraw: AtomicBool, launched: AtomicBool, running: AtomicBool, in_callback: AtomicBool, control_flow: Mutex, exit: AtomicBool, start_time: Mutex>, callback: Mutex>>, pending_events: Mutex>, pending_redraw: Mutex>, wait_timeout: 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(&self) -> MutexGuard<'_, Vec> { self.pending_redraw.lock().unwrap() } fn waker(&self) -> MutexGuard<'_, EventLoopWaker> { self.waker.lock().unwrap() } /// `true` after `ApplicationDelegate::applicationDidFinishLaunching` called /// /// NB: This is global / `NSApp` state and since the app will only be launched /// once but an `EventLoop` may be run more than once then only the first /// `EventLoop` will observe the `NSApp` before it is launched. fn is_launched(&self) -> bool { self.launched.load(Ordering::Acquire) } /// Set via `ApplicationDelegate::applicationDidFinishLaunching` fn set_launched(&self) { self.launched.store(true, Ordering::Release); } /// `true` if an `EventLoop` is currently running /// /// NB: This is global / `NSApp` state and may persist beyond the lifetime of /// a running `EventLoop`. /// /// # Caveat /// This is only intended to be called from the main thread fn is_running(&self) -> bool { self.running.load(Ordering::Relaxed) } /// Set when an `EventLoop` starts running, after the `NSApp` is launched /// /// # Caveat /// This is only intended to be called from the main thread fn set_running(&self) { self.running.store(true, Ordering::Relaxed); } /// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits /// /// Since an `EventLoop` may be run more than once we need make sure to reset the /// `control_flow` state back to `Poll` each time the loop exits. /// /// Note: that if the `NSApp` has been launched then that state is preserved, and we won't /// need to re-launch the app if subsequent EventLoops are run. /// /// # Caveat /// This is only intended to be called from the main thread fn internal_exit(&self) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interiour mutability // // XXX: As an aside; having each individual bit of state for `Handler` be atomic or wrapped in a // `Mutex` for the sake of interior mutability seems a bit odd, and also a potential foot // gun in case the state is unwittingly accessed across threads because the fine-grained locking // wouldn't ensure that there's interior consistency. // // Maybe the whole thing should just be put in a static `Mutex<>` to make it clear // the we can mutate more than one peice of state while maintaining consistency. (though it // looks like there have been recuring re-entrancy issues with callback handling that might // make that awkward) self.running.store(false, Ordering::Relaxed); self.set_stop_app_on_redraw_requested(false); self.set_stop_app_before_wait(false); self.set_stop_app_after_wait(false); self.set_wait_timeout(None); } pub fn exit(&self) { self.exit.store(true, Ordering::Relaxed) } pub fn clear_exit(&self) { self.exit.store(false, Ordering::Relaxed) } pub fn exiting(&self) -> bool { self.exit.load(Ordering::Relaxed) } pub fn request_stop_app_on_launch(&self) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_on_launch.store(true, Ordering::Relaxed); } pub fn should_stop_app_on_launch(&self) -> bool { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_on_launch.load(Ordering::Relaxed) } pub fn set_stop_app_before_wait(&self, stop_before_wait: bool) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_before_wait .store(stop_before_wait, Ordering::Relaxed); } pub fn should_stop_app_before_wait(&self) -> bool { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_before_wait.load(Ordering::Relaxed) } pub fn set_stop_app_after_wait(&self, stop_after_wait: bool) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_after_wait .store(stop_after_wait, Ordering::Relaxed); } pub fn set_wait_timeout(&self, new_timeout: Option) { let mut timeout = self.wait_timeout.lock().unwrap(); *timeout = new_timeout; } pub fn wait_timeout(&self) -> Option { *self.wait_timeout.lock().unwrap() } pub fn should_stop_app_after_wait(&self) -> bool { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_after_wait.load(Ordering::Relaxed) } pub fn set_stop_app_on_redraw_requested(&self, stop_on_redraw: bool) { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_on_redraw .store(stop_on_redraw, Ordering::Relaxed); } pub fn should_stop_app_on_redraw_requested(&self) -> bool { // Relaxed ordering because we don't actually have multiple threads involved, we just want // interior mutability self.stop_app_on_redraw.load(Ordering::Relaxed) } fn set_control_flow(&self, new_control_flow: ControlFlow) { *self.control_flow.lock().unwrap() = new_control_flow } fn control_flow(&self) -> ControlFlow { *self.control_flow.lock().unwrap() } 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 have_callback(&self) -> bool { self.callback.lock().unwrap().is_some() } fn handle_nonuser_event(&self, event: Event) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { callback.handle_nonuser_event(event) } } fn handle_user_events(&self) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { callback.handle_user_events(); } } fn handle_scale_factor_changed_event( &self, window: &WinitWindow, suggested_size: PhysicalSize, scale_factor: f64, ) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { let new_inner_size = Arc::new(Mutex::new(suggested_size)); let scale_factor_changed_event = Event::WindowEvent { window_id: WindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }; callback.handle_nonuser_event(scale_factor_changed_event); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let logical_size = physical_size.to_logical(scale_factor); let size = NSSize::new(logical_size.width, logical_size.height); window.setContentSize(size); let resized_event = Event::WindowEvent { window_id: WindowId(window.id()), event: WindowEvent::Resized(physical_size), }; callback.handle_nonuser_event(resized_event); } } } pub(crate) enum AppState {} impl AppState { /// Associate the application's event callback with the (global static) Handler state /// /// # Safety /// This is ignoring the lifetime of the application callback (which may not be 'static) /// and can lead to undefined behaviour if the callback is not cleared before the end of /// its real lifetime. /// /// All public APIs that take an event callback (`run`, `run_on_demand`, /// `pump_events`) _must_ pair a call to `set_callback` with /// a call to `clear_callback` before returning to avoid undefined behaviour. pub unsafe fn set_callback( callback: Weak>, window_target: Rc>, receiver: Rc>, ) { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { callback, window_target, receiver, })); } pub fn clear_callback() { HANDLER.callback.lock().unwrap().take(); } pub fn is_launched() -> bool { HANDLER.is_launched() } pub fn is_running() -> bool { HANDLER.is_running() } // If `pump_events` is called to progress the event loop then we bootstrap the event // loop via `[NSApp run]` but will use `CFRunLoopRunInMode` for subsequent calls to // `pump_events` pub fn request_stop_on_launch() { HANDLER.request_stop_app_on_launch(); } pub fn set_stop_app_before_wait(stop_before_wait: bool) { HANDLER.set_stop_app_before_wait(stop_before_wait); } pub fn set_stop_app_after_wait(stop_after_wait: bool) { HANDLER.set_stop_app_after_wait(stop_after_wait); } pub fn set_wait_timeout(timeout: Option) { HANDLER.set_wait_timeout(timeout); } pub fn set_stop_app_on_redraw_requested(stop_on_redraw: bool) { HANDLER.set_stop_app_on_redraw_requested(stop_on_redraw); } pub fn set_control_flow(control_flow: ControlFlow) { HANDLER.set_control_flow(control_flow) } pub fn control_flow() -> ControlFlow { HANDLER.control_flow() } pub fn internal_exit() { HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(Event::LoopExiting); HANDLER.set_in_callback(false); HANDLER.internal_exit(); Self::clear_callback(); } pub fn exit() { HANDLER.exit() } pub fn clear_exit() { HANDLER.clear_exit() } pub fn exiting() -> bool { HANDLER.exiting() } pub fn dispatch_init_events() { HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); // NB: For consistency all platforms must emit a 'resumed' event even though macOS // applications don't themselves have a formal suspend/resume lifecycle. HANDLER.handle_nonuser_event(Event::Resumed); HANDLER.set_in_callback(false); } pub fn start_running() { debug_assert!(HANDLER.is_launched()); HANDLER.set_running(); Self::dispatch_init_events() } pub fn launched( activation_policy: NSApplicationActivationPolicy, create_default_menu: bool, activate_ignoring_other_apps: bool, ) { let app = NSApp(); // We need to delay setting the activation policy and activating the app // until `applicationDidFinishLaunching` has been called. Otherwise the // menu bar is initially unresponsive on macOS 10.15. app.setActivationPolicy(activation_policy); window_activation_hack(&app); app.activateIgnoringOtherApps(activate_ignoring_other_apps); HANDLER.set_launched(); HANDLER.waker().start(); if create_default_menu { // The menubar initialization should be before the `NewEvents` event, to allow // overriding of the default menu even if it's created menu::initialize(); } Self::start_running(); // If the `NSApp` is being launched via `EventLoop::pump_events()` then we'll // want to stop the app once it is launched (and return to the external loop) // // In this case we still want to consider Winit's `EventLoop` to be "running", // so we call `start_running()` above. if HANDLER.should_stop_app_on_launch() { // Note: the original idea had been to only stop the underlying `RunLoop` // for the app but that didn't work as expected (`[NSApp run]` effectively // ignored the attempt to stop the RunLoop and re-started it.). So we // return from `pump_events` by stopping the `NSApp` Self::stop(); } } // Called by RunLoopObserver after finishing waiting for new events 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.get_in_callback() || !HANDLER.have_callback() || !HANDLER.is_running() { return; } if HANDLER.should_stop_app_after_wait() { Self::stop(); } let start = HANDLER.get_start_time().unwrap(); let cause = match HANDLER.control_flow() { 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), } } } }; HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(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) { // Redraw request might come out of order from the OS. // -> Don't go back into the callback when our callstack originates from there if !HANDLER.in_callback.swap(true, Ordering::AcqRel) { HANDLER.handle_nonuser_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }); HANDLER.set_in_callback(false); // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events // as a way to ensure that `pump_events` can't block an external loop indefinitely if HANDLER.should_stop_app_on_redraw_requested() { AppState::stop(); } } } pub fn queue_event(event: Event) { if !is_main_thread() { panic!("Event queued from different thread: {event:#?}"); } HANDLER.events().push_back(EventWrapper::StaticEvent(event)); } pub fn queue_static_scale_factor_changed_event( window: Id, suggested_size: PhysicalSize, scale_factor: f64, ) { HANDLER .events() .push_back(EventWrapper::ScaleFactorChanged { window, suggested_size, scale_factor, }); } pub fn stop() { let app = NSApp(); autoreleasepool(|_| { app.stop(None); // To stop event loop immediately, we need to post some event here. app.postEvent_atStart(&NSEvent::dummy(), true); }); } // Called by RunLoopObserver before waiting for new events 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 // XXX: how does it make sense that `get_in_callback()` can ever return `true` here if we're // about to return to the `CFRunLoop` to poll for new events? if panic_info.is_panicking() || HANDLER.get_in_callback() || !HANDLER.have_callback() || !HANDLER.is_running() { return; } HANDLER.set_in_callback(true); HANDLER.handle_user_events(); for event in HANDLER.take_events() { match event { EventWrapper::StaticEvent(event) => { HANDLER.handle_nonuser_event(event); } EventWrapper::ScaleFactorChanged { window, suggested_size, scale_factor, } => { HANDLER.handle_scale_factor_changed_event( &window, suggested_size, scale_factor, ); } } } for window_id in HANDLER.should_redraw() { HANDLER.handle_nonuser_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }); } HANDLER.handle_nonuser_event(Event::AboutToWait); HANDLER.set_in_callback(false); if HANDLER.exiting() { Self::stop(); } if HANDLER.should_stop_app_before_wait() { Self::stop(); } HANDLER.update_start_time(); let wait_timeout = HANDLER.wait_timeout(); // configured by pump_events let app_timeout = match HANDLER.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Instant::now()), ControlFlow::WaitUntil(instant) => Some(instant), }; HANDLER .waker() .start_at(min_timeout(wait_timeout, app_timeout)); } } /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| { b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) }) } /// 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. fn window_activation_hack(app: &NSApplication) { // TODO: Proper ordering of the windows app.windows().into_iter().for_each(|window| { // Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::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 window.isVisible() { trace!("Activating visible window"); window.makeKeyAndOrderFront(None); } else { trace!("Skipping activating invisible window"); } }) } winit-0.29.15/src/platform_impl/macos/appkit/appearance.rs000064400000000000000000000014271046102023000216330ustar 00000000000000use icrate::Foundation::{NSArray, NSObject, NSString}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSAppearance; unsafe impl ClassType for NSAppearance { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); type NSAppearanceName = NSString; extern_methods!( unsafe impl NSAppearance { #[method_id(appearanceNamed:)] pub fn appearanceNamed(name: &NSAppearanceName) -> Id; #[method_id(bestMatchFromAppearancesWithNames:)] pub fn bestMatchFromAppearancesWithNames( &self, appearances: &NSArray, ) -> Id; } ); winit-0.29.15/src/platform_impl/macos/appkit/application.rs000064400000000000000000000117571046102023000220460ustar 00000000000000use icrate::Foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use objc2::{Encode, Encoding}; use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSApplication; unsafe impl ClassType for NSApplication { #[inherits(NSObject)] type Super = NSResponder; type Mutability = mutability::InteriorMutable; } ); pub(crate) fn NSApp() -> Id { // TODO: Only allow access from main thread NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() }) } extern_methods!( unsafe impl NSApplication { /// This can only be called on the main thread since it may initialize /// the application and since it's parameters may be changed by the main /// thread at any time (hence it is only safe to access on the main thread). pub fn shared(_mtm: MainThreadMarker) -> Id { let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] }; // SAFETY: `sharedApplication` always initializes the app if it isn't already unsafe { app.unwrap_unchecked() } } #[method_id(currentEvent)] pub fn currentEvent(&self) -> Option>; #[method(postEvent:atStart:)] pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool); #[method(presentationOptions)] pub fn presentationOptions(&self) -> NSApplicationPresentationOptions; #[method_id(windows)] pub fn windows(&self) -> Id>; #[method_id(keyWindow)] pub fn keyWindow(&self) -> Option>; // TODO: NSApplicationDelegate #[method(setDelegate:)] pub fn setDelegate(&self, delegate: &AnyObject); #[method(setPresentationOptions:)] pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions); #[method(hide:)] pub fn hide(&self, sender: Option<&AnyObject>); #[method(orderFrontCharacterPalette:)] #[allow(dead_code)] pub fn orderFrontCharacterPalette(&self, sender: Option<&AnyObject>); #[method(hideOtherApplications:)] pub fn hideOtherApplications(&self, sender: Option<&AnyObject>); #[method(stop:)] pub fn stop(&self, sender: Option<&AnyObject>); #[method(activateIgnoringOtherApps:)] pub fn activateIgnoringOtherApps(&self, ignore: bool); #[method(requestUserAttention:)] pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger; #[method(setActivationPolicy:)] pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool; #[method(setMainMenu:)] pub fn setMainMenu(&self, menu: &NSMenu); #[method_id(effectiveAppearance)] pub fn effectiveAppearance(&self) -> Id; #[method(setAppearance:)] pub fn setAppearance(&self, appearance: Option<&NSAppearance>); #[method(run)] pub unsafe fn run(&self); } ); #[allow(dead_code)] #[repr(isize)] // NSInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSApplicationActivationPolicy { NSApplicationActivationPolicyRegular = 0, NSApplicationActivationPolicyAccessory = 1, NSApplicationActivationPolicyProhibited = 2, NSApplicationActivationPolicyERROR = -1, } unsafe impl Encode for NSApplicationActivationPolicy { const ENCODING: Encoding = NSInteger::ENCODING; } bitflags! { #[derive(Debug, Clone, Copy)] pub struct NSApplicationPresentationOptions: NSUInteger { const NSApplicationPresentationDefault = 0; const NSApplicationPresentationAutoHideDock = 1 << 0; const NSApplicationPresentationHideDock = 1 << 1; const NSApplicationPresentationAutoHideMenuBar = 1 << 2; const NSApplicationPresentationHideMenuBar = 1 << 3; const NSApplicationPresentationDisableAppleMenu = 1 << 4; const NSApplicationPresentationDisableProcessSwitching = 1 << 5; const NSApplicationPresentationDisableForceQuit = 1 << 6; const NSApplicationPresentationDisableSessionTermination = 1 << 7; const NSApplicationPresentationDisableHideApplication = 1 << 8; const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9; const NSApplicationPresentationFullScreen = 1 << 10; const NSApplicationPresentationAutoHideToolbar = 1 << 11; } } unsafe impl Encode for NSApplicationPresentationOptions { const ENCODING: Encoding = NSUInteger::ENCODING; } #[repr(usize)] // NSUInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSRequestUserAttentionType { NSCriticalRequest = 0, NSInformationalRequest = 10, } unsafe impl Encode for NSRequestUserAttentionType { const ENCODING: Encoding = NSUInteger::ENCODING; } winit-0.29.15/src/platform_impl/macos/appkit/button.rs000064400000000000000000000006311046102023000210430ustar 00000000000000use icrate::Foundation::NSObject; use objc2::{extern_class, mutability, ClassType}; use super::{NSControl, NSResponder, NSView}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSButton; unsafe impl ClassType for NSButton { #[inherits(NSView, NSResponder, NSObject)] type Super = NSControl; type Mutability = mutability::InteriorMutable; } ); winit-0.29.15/src/platform_impl/macos/appkit/color.rs000064400000000000000000000014241046102023000206470ustar 00000000000000use icrate::Foundation::NSObject; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( /// An object that stores color data and sometimes opacity (alpha value). /// /// #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSColor; unsafe impl ClassType for NSColor { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // SAFETY: Documentation clearly states: // > Color objects are immutable and thread-safe unsafe impl Send for NSColor {} unsafe impl Sync for NSColor {} extern_methods!( unsafe impl NSColor { #[method_id(clearColor)] pub fn clear() -> Id; } ); winit-0.29.15/src/platform_impl/macos/appkit/control.rs000064400000000000000000000011431046102023000212070ustar 00000000000000use icrate::Foundation::NSObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::{NSResponder, NSView}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSControl; unsafe impl ClassType for NSControl { #[inherits(NSResponder, NSObject)] type Super = NSView; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSControl { #[method(setEnabled:)] pub fn setEnabled(&self, enabled: bool); #[method(isEnabled)] pub fn isEnabled(&self) -> bool; } ); winit-0.29.15/src/platform_impl/macos/appkit/cursor.rs000064400000000000000000000226751046102023000210610ustar 00000000000000use once_cell::sync::Lazy; use icrate::ns_string; use icrate::Foundation::{ NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSString, }; use objc2::rc::{DefaultId, Id}; use objc2::runtime::Sel; use objc2::{extern_class, extern_methods, msg_send_id, mutability, sel, ClassType}; use super::NSImage; use crate::window::CursorIcon; extern_class!( /// #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSCursor; unsafe impl ClassType for NSCursor { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // SAFETY: NSCursor is immutable, stated here: // https://developer.apple.com/documentation/appkit/nscursor/1527062-image?language=objc unsafe impl Send for NSCursor {} unsafe impl Sync for NSCursor {} macro_rules! def_cursor { {$( $(#[$($m:meta)*])* pub fn $name:ident(); )*} => {$( $(#[$($m)*])* pub fn $name() -> Id { unsafe { msg_send_id![Self::class(), $name] } } )*}; } macro_rules! def_undocumented_cursor { {$( $(#[$($m:meta)*])* pub fn $name:ident(); )*} => {$( $(#[$($m)*])* pub fn $name() -> Id { unsafe { Self::from_selector(sel!($name)).unwrap_or_else(|| Default::default()) } } )*}; } extern_methods!( /// Documented cursors unsafe impl NSCursor { def_cursor!( pub fn arrowCursor(); pub fn pointingHandCursor(); pub fn openHandCursor(); pub fn closedHandCursor(); pub fn IBeamCursor(); pub fn IBeamCursorForVerticalLayout(); pub fn dragCopyCursor(); pub fn dragLinkCursor(); pub fn operationNotAllowedCursor(); pub fn contextualMenuCursor(); pub fn crosshairCursor(); pub fn resizeRightCursor(); pub fn resizeUpCursor(); pub fn resizeLeftCursor(); pub fn resizeDownCursor(); pub fn resizeLeftRightCursor(); pub fn resizeUpDownCursor(); ); // Creating cursors should be thread-safe, though using them for anything probably isn't. pub fn new(image: &NSImage, hotSpot: NSPoint) -> Id { unsafe { msg_send_id![Self::alloc(), initWithImage: image, hotSpot: hotSpot] } } pub fn invisible() -> 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, ]; static CURSOR: Lazy> = Lazy::new(|| { // TODO: Consider using `dataWithBytesNoCopy:` let data = NSData::with_bytes(CURSOR_BYTES); let image = NSImage::new_with_data(&data); NSCursor::new(&image, NSPoint::new(0.0, 0.0)) }); CURSOR.clone() } } /// Undocumented cursors unsafe impl NSCursor { #[method(respondsToSelector:)] fn class_responds_to(sel: Sel) -> bool; #[method_id(performSelector:)] unsafe fn from_selector_unchecked(sel: Sel) -> Id; unsafe fn from_selector(sel: Sel) -> Option> { if Self::class_responds_to(sel) { Some(unsafe { Self::from_selector_unchecked(sel) }) } else { warn!("Cursor `{:?}` appears to be invalid", sel); None } } def_undocumented_cursor!( // Undocumented cursors: https://stackoverflow.com/a/46635398/5435443 pub fn _helpCursor(); pub fn _zoomInCursor(); pub fn _zoomOutCursor(); pub fn _windowResizeNorthEastCursor(); pub fn _windowResizeNorthWestCursor(); pub fn _windowResizeSouthEastCursor(); pub fn _windowResizeSouthWestCursor(); pub fn _windowResizeNorthEastSouthWestCursor(); pub fn _windowResizeNorthWestSouthEastCursor(); // While these two are available, the former just loads a white arrow, // and the latter loads an ugly deflated beachball! // pub fn _moveCursor(); // pub fn _waitCursor(); // An even more undocumented cursor... // https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349 pub fn busyButClickableCursor(); ); } /// Webkit cursors unsafe impl NSCursor { // Note that loading `busybutclickable` with this code won't animate // the frames; instead you'll just get them all in a column. unsafe fn load_webkit_cursor(name: &NSString) -> Id { // Snatch a cursor from WebKit; They fit the style of the native // cursors, and will seem completely standard to macOS users. // // https://stackoverflow.com/a/21786835/5435443 let root = ns_string!("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"); let cursor_path = root.stringByAppendingPathComponent(name); let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf")); let image = NSImage::new_by_referencing_file(&pdf_path); // TODO: Handle PLists better let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist")); let info: Id> = unsafe { msg_send_id![ >::class(), dictionaryWithContentsOfFile: &*info_path, ] }; let mut x = 0.0; if let Some(n) = info.get(&*ns_string!("hotx")) { if n.is_kind_of::() { let ptr: *const NSObject = n; let ptr: *const NSNumber = ptr.cast(); x = unsafe { &*ptr }.as_cgfloat() } } let mut y = 0.0; if let Some(n) = info.get(&*ns_string!("hotx")) { if n.is_kind_of::() { let ptr: *const NSObject = n; let ptr: *const NSNumber = ptr.cast(); y = unsafe { &*ptr }.as_cgfloat() } } let hotspot = NSPoint::new(x, y); Self::new(&image, hotspot) } pub fn moveCursor() -> Id { unsafe { Self::load_webkit_cursor(ns_string!("move")) } } pub fn cellCursor() -> Id { unsafe { Self::load_webkit_cursor(ns_string!("cell")) } } } ); impl NSCursor { pub fn from_icon(icon: CursorIcon) -> Id { match icon { CursorIcon::Default => Default::default(), CursorIcon::Pointer => Self::pointingHandCursor(), CursorIcon::Grab => Self::openHandCursor(), CursorIcon::Grabbing => Self::closedHandCursor(), CursorIcon::Text => Self::IBeamCursor(), CursorIcon::VerticalText => Self::IBeamCursorForVerticalLayout(), CursorIcon::Copy => Self::dragCopyCursor(), CursorIcon::Alias => Self::dragLinkCursor(), CursorIcon::NotAllowed | CursorIcon::NoDrop => Self::operationNotAllowedCursor(), CursorIcon::ContextMenu => Self::contextualMenuCursor(), CursorIcon::Crosshair => Self::crosshairCursor(), CursorIcon::EResize => Self::resizeRightCursor(), CursorIcon::NResize => Self::resizeUpCursor(), CursorIcon::WResize => Self::resizeLeftCursor(), CursorIcon::SResize => Self::resizeDownCursor(), CursorIcon::EwResize | CursorIcon::ColResize => Self::resizeLeftRightCursor(), CursorIcon::NsResize | CursorIcon::RowResize => Self::resizeUpDownCursor(), CursorIcon::Help => Self::_helpCursor(), CursorIcon::ZoomIn => Self::_zoomInCursor(), CursorIcon::ZoomOut => Self::_zoomOutCursor(), CursorIcon::NeResize => Self::_windowResizeNorthEastCursor(), CursorIcon::NwResize => Self::_windowResizeNorthWestCursor(), CursorIcon::SeResize => Self::_windowResizeSouthEastCursor(), CursorIcon::SwResize => Self::_windowResizeSouthWestCursor(), CursorIcon::NeswResize => Self::_windowResizeNorthEastSouthWestCursor(), CursorIcon::NwseResize => Self::_windowResizeNorthWestSouthEastCursor(), // This is the wrong semantics for `Wait`, but it's the same as // what's used in Safari and Chrome. CursorIcon::Wait | CursorIcon::Progress => Self::busyButClickableCursor(), CursorIcon::Move | CursorIcon::AllScroll => Self::moveCursor(), CursorIcon::Cell => Self::cellCursor(), _ => Default::default(), } } } impl DefaultId for NSCursor { fn default_id() -> Id { Self::arrowCursor() } } winit-0.29.15/src/platform_impl/macos/appkit/event.rs000064400000000000000000000232361046102023000206570ustar 00000000000000use std::os::raw::c_ushort; use icrate::Foundation::{ CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger, }; use objc2::encode::{Encode, Encoding}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSEvent; unsafe impl ClassType for NSEvent { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // > Safely handled only on the same thread, whether that be the main thread // > or a secondary thread; otherwise you run the risk of having events get // > out of sequence. // // extern_methods!( unsafe impl NSEvent { #[method_id( otherEventWithType: location: modifierFlags: timestamp: windowNumber: context: subtype: data1: data2: )] unsafe fn otherEventWithType( type_: NSEventType, location: NSPoint, flags: NSEventModifierFlags, time: NSTimeInterval, window_num: NSInteger, context: Option<&NSObject>, // NSGraphicsContext subtype: NSEventSubtype, data1: NSInteger, data2: NSInteger, ) -> Id; pub fn dummy() -> Id { unsafe { Self::otherEventWithType( NSEventType::NSApplicationDefined, NSPoint::new(0.0, 0.0), NSEventModifierFlags::empty(), 0.0, 0, None, NSEventSubtype::NSWindowExposedEventType, 0, 0, ) } } #[method_id( keyEventWithType: location: modifierFlags: timestamp: windowNumber: context: characters: charactersIgnoringModifiers: isARepeat: keyCode: )] pub fn keyEventWithType( type_: NSEventType, location: NSPoint, modifier_flags: NSEventModifierFlags, timestamp: NSTimeInterval, window_num: NSInteger, context: Option<&NSObject>, characters: &NSString, characters_ignoring_modifiers: &NSString, is_a_repeat: bool, scancode: c_ushort, ) -> Id; #[method(locationInWindow)] pub fn locationInWindow(&self) -> NSPoint; // TODO: MainThreadMarker #[method(pressedMouseButtons)] pub fn pressedMouseButtons() -> NSUInteger; #[method(modifierFlags)] pub fn modifierFlags(&self) -> NSEventModifierFlags; #[method(type)] pub fn type_(&self) -> NSEventType; #[method(keyCode)] pub fn key_code(&self) -> c_ushort; #[method(magnification)] pub fn magnification(&self) -> CGFloat; #[method(phase)] pub fn phase(&self) -> NSEventPhase; #[method(momentumPhase)] pub fn momentumPhase(&self) -> NSEventPhase; #[method(deltaX)] pub fn deltaX(&self) -> CGFloat; #[method(deltaY)] pub fn deltaY(&self) -> CGFloat; #[method(buttonNumber)] pub fn buttonNumber(&self) -> NSInteger; #[method(scrollingDeltaX)] pub fn scrollingDeltaX(&self) -> CGFloat; #[method(scrollingDeltaY)] pub fn scrollingDeltaY(&self) -> CGFloat; #[method(hasPreciseScrollingDeltas)] pub fn hasPreciseScrollingDeltas(&self) -> bool; #[method(rotation)] pub fn rotation(&self) -> f32; #[method(pressure)] pub fn pressure(&self) -> f32; #[method(stage)] pub fn stage(&self) -> NSInteger; #[method(isARepeat)] pub fn is_a_repeat(&self) -> bool; #[method(windowNumber)] pub fn window_number(&self) -> NSInteger; #[method(timestamp)] pub fn timestamp(&self) -> NSTimeInterval; #[method_id(characters)] pub fn characters(&self) -> Option>; #[method_id(charactersIgnoringModifiers)] pub fn charactersIgnoringModifiers(&self) -> Option>; pub fn lshift_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICELSHIFTKEYMASK != 0 } pub fn rshift_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICERSHIFTKEYMASK != 0 } pub fn lctrl_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICELCTLKEYMASK != 0 } pub fn rctrl_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICERCTLKEYMASK != 0 } pub fn lalt_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICELALTKEYMASK != 0 } pub fn ralt_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICERALTKEYMASK != 0 } pub fn lcmd_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICELCMDKEYMASK != 0 } pub fn rcmd_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICERCMDKEYMASK != 0 } } ); unsafe impl NSCopying for NSEvent {} // The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259 const NX_DEVICELCTLKEYMASK: u32 = 0x00000001; const NX_DEVICELSHIFTKEYMASK: u32 = 0x00000002; const NX_DEVICERSHIFTKEYMASK: u32 = 0x00000004; const NX_DEVICELCMDKEYMASK: u32 = 0x00000008; const NX_DEVICERCMDKEYMASK: u32 = 0x00000010; const NX_DEVICELALTKEYMASK: u32 = 0x00000020; const NX_DEVICERALTKEYMASK: u32 = 0x00000040; const NX_DEVICERCTLKEYMASK: u32 = 0x00002000; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NSEventModifierFlags: NSUInteger { const NSAlphaShiftKeyMask = 1 << 16; const NSShiftKeyMask = 1 << 17; const NSControlKeyMask = 1 << 18; const NSAlternateKeyMask = 1 << 19; const NSCommandKeyMask = 1 << 20; const NSNumericPadKeyMask = 1 << 21; const NSHelpKeyMask = 1 << 22; const NSFunctionKeyMask = 1 << 23; const NSDeviceIndependentModifierFlagsMask = 0xffff0000; } } unsafe impl Encode for NSEventModifierFlags { const ENCODING: Encoding = NSUInteger::ENCODING; } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct NSEventPhase: NSUInteger { const NSEventPhaseNone = 0; const NSEventPhaseBegan = 0x1 << 0; const NSEventPhaseStationary = 0x1 << 1; const NSEventPhaseChanged = 0x1 << 2; const NSEventPhaseEnded = 0x1 << 3; const NSEventPhaseCancelled = 0x1 << 4; const NSEventPhaseMayBegin = 0x1 << 5; } } unsafe impl Encode for NSEventPhase { const ENCODING: Encoding = NSUInteger::ENCODING; } #[allow(dead_code)] #[repr(i16)] // short #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSEventSubtype { // TODO: Not sure what these values are // NSMouseEventSubtype = NX_SUBTYPE_DEFAULT, // NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT, // NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY // NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH, NSWindowExposedEventType = 0, NSApplicationActivatedEventType = 1, NSApplicationDeactivatedEventType = 2, NSWindowMovedEventType = 4, NSScreenChangedEventType = 8, NSAWTEventType = 16, } unsafe impl Encode for NSEventSubtype { const ENCODING: Encoding = i16::ENCODING; } #[allow(dead_code)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(usize)] // NSUInteger pub enum NSEventType { NSLeftMouseDown = 1, NSLeftMouseUp = 2, NSRightMouseDown = 3, NSRightMouseUp = 4, NSMouseMoved = 5, NSLeftMouseDragged = 6, NSRightMouseDragged = 7, NSMouseEntered = 8, NSMouseExited = 9, NSKeyDown = 10, NSKeyUp = 11, NSFlagsChanged = 12, NSAppKitDefined = 13, NSSystemDefined = 14, NSApplicationDefined = 15, NSPeriodic = 16, NSCursorUpdate = 17, NSScrollWheel = 22, NSTabletPoint = 23, NSTabletProximity = 24, NSOtherMouseDown = 25, NSOtherMouseUp = 26, NSOtherMouseDragged = 27, NSEventTypeGesture = 29, NSEventTypeMagnify = 30, NSEventTypeSwipe = 31, NSEventTypeRotate = 18, NSEventTypeBeginGesture = 19, NSEventTypeEndGesture = 20, NSEventTypePressure = 34, } unsafe impl Encode for NSEventType { const ENCODING: Encoding = NSUInteger::ENCODING; } winit-0.29.15/src/platform_impl/macos/appkit/image.rs000064400000000000000000000024111046102023000206100ustar 00000000000000use icrate::Foundation::{NSData, NSObject, NSString}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; extern_class!( // TODO: Can this be mutable? #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSImage; unsafe impl ClassType for NSImage { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // Documented Thread-Unsafe, but: // > One thread can create an NSImage object, draw to the image buffer, // > and pass it off to the main thread for drawing. The underlying image // > cache is shared among all threads. // // // So really only unsafe to mutate on several threads. unsafe impl Send for NSImage {} unsafe impl Sync for NSImage {} extern_methods!( unsafe impl NSImage { pub fn new_by_referencing_file(path: &NSString) -> Id { unsafe { msg_send_id![Self::alloc(), initByReferencingFile: path] } } pub fn new_with_data(data: &NSData) -> Id { unsafe { msg_send_id![Self::alloc(), initWithData: data] } } } ); winit-0.29.15/src/platform_impl/macos/appkit/menu.rs000064400000000000000000000010551046102023000204750ustar 00000000000000use icrate::Foundation::NSObject; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::NSMenuItem; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSMenu; unsafe impl ClassType for NSMenu { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSMenu { #[method_id(new)] pub fn new() -> Id; #[method(addItem:)] pub fn addItem(&self, item: &NSMenuItem); } ); winit-0.29.15/src/platform_impl/macos/appkit/menu_item.rs000064400000000000000000000022751046102023000215200ustar 00000000000000use icrate::Foundation::{NSObject, NSString}; use objc2::rc::Id; use objc2::runtime::Sel; use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType}; use super::{NSEventModifierFlags, NSMenu}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSMenuItem; unsafe impl ClassType for NSMenuItem { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSMenuItem { #[method_id(new)] pub fn new() -> Id; pub fn newWithTitle(title: &NSString, action: Sel, key_equivalent: &NSString) -> Id { unsafe { msg_send_id![ Self::alloc(), initWithTitle: title, action: action, keyEquivalent: key_equivalent, ] } } #[method_id(separatorItem)] pub fn separatorItem() -> Id; #[method(setKeyEquivalentModifierMask:)] pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags); #[method(setSubmenu:)] pub fn setSubmenu(&self, submenu: &NSMenu); } ); winit-0.29.15/src/platform_impl/macos/appkit/mod.rs000064400000000000000000000042041046102023000203070ustar 00000000000000//! Safe bindings for the AppKit framework. //! //! These are split out from the rest of `winit` to make safety easier to review. //! In the future, these should probably live in another crate like `cacao`. //! //! TODO: Main thread safety. // Objective-C methods have different conventions, and it's much easier to // understand if we just use the same names #![allow(non_snake_case)] #![allow(clippy::too_many_arguments)] #![allow(clippy::enum_variant_names)] #![allow(non_upper_case_globals)] mod appearance; mod application; mod button; mod color; mod control; mod cursor; mod event; mod image; mod menu; mod menu_item; mod pasteboard; mod responder; mod screen; mod tab_group; mod text_input_client; mod text_input_context; mod version; mod view; mod window; pub(crate) use self::appearance::NSAppearance; pub(crate) use self::application::{ NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions, NSRequestUserAttentionType, }; pub(crate) use self::button::NSButton; pub(crate) use self::color::NSColor; pub(crate) use self::control::NSControl; pub(crate) use self::cursor::NSCursor; #[allow(unused_imports)] pub(crate) use self::event::{ NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType, }; pub(crate) use self::image::NSImage; pub(crate) use self::menu::NSMenu; pub(crate) use self::menu_item::NSMenuItem; pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType}; pub(crate) use self::responder::NSResponder; #[allow(unused_imports)] pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen}; pub(crate) use self::tab_group::NSWindowTabGroup; pub(crate) use self::text_input_client::NSTextInputClient; pub(crate) use self::text_input_context::NSTextInputContext; pub(crate) use self::version::NSAppKitVersion; pub(crate) use self::view::{NSTrackingRectTag, NSView}; pub(crate) use self::window::{ NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, }; #[link(name = "AppKit", kind = "framework")] extern "C" {} winit-0.29.15/src/platform_impl/macos/appkit/pasteboard.rs000064400000000000000000000012401046102023000216510ustar 00000000000000use icrate::Foundation::{NSObject, NSString}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSPasteboard; unsafe impl ClassType for NSPasteboard { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSPasteboard { #[method_id(propertyListForType:)] pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id; } ); pub type NSPasteboardType = NSString; extern "C" { pub static NSFilenamesPboardType: &'static NSPasteboardType; } winit-0.29.15/src/platform_impl/macos/appkit/responder.rs000064400000000000000000000010741046102023000215330ustar 00000000000000use icrate::Foundation::{NSArray, NSObject}; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::NSEvent; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub struct NSResponder; unsafe impl ClassType for NSResponder { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // Documented as "Thread-Unsafe". extern_methods!( unsafe impl NSResponder { #[method(interpretKeyEvents:)] pub(crate) unsafe fn interpretKeyEvents(&self, events: &NSArray); } ); winit-0.29.15/src/platform_impl/macos/appkit/screen.rs000064400000000000000000000042631046102023000210140ustar 00000000000000use icrate::ns_string; use icrate::Foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSScreen; unsafe impl ClassType for NSScreen { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); // TODO: Main thread marker! extern_methods!( unsafe impl NSScreen { /// The application object must have been created. #[method_id(mainScreen)] pub fn main() -> Option>; /// The application object must have been created. #[method_id(screens)] pub fn screens() -> Id>; #[method(frame)] pub fn frame(&self) -> NSRect; #[method(visibleFrame)] pub fn visibleFrame(&self) -> NSRect; #[method_id(deviceDescription)] pub fn deviceDescription(&self) -> Id>; pub fn display_id(&self) -> u32 { let key = ns_string!("NSScreenNumber"); objc2::rc::autoreleasepool(|_| { let device_description = self.deviceDescription(); // Retrieve the CGDirectDisplayID associated with this screen // // SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed // to be an NSNumber. See documentation for `deviceDescription` for details: // let obj = device_description .get(key) .expect("failed getting screen display id from device description"); let obj: *const AnyObject = obj; let obj: *const NSNumber = obj.cast(); let obj: &NSNumber = unsafe { &*obj }; obj.as_u32() }) } #[method(backingScaleFactor)] pub fn backingScaleFactor(&self) -> CGFloat; } ); pub type NSDeviceDescriptionKey = NSString; winit-0.29.15/src/platform_impl/macos/appkit/tab_group.rs000064400000000000000000000014461046102023000215170ustar 00000000000000use icrate::Foundation::{NSArray, NSObject}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::NSWindow; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSWindowTabGroup; unsafe impl ClassType for NSWindowTabGroup { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSWindowTabGroup { #[method(selectNextTab)] pub fn selectNextTab(&self); #[method(selectPreviousTab)] pub fn selectPreviousTab(&self); #[method_id(windows)] pub fn tabbedWindows(&self) -> Option>>; #[method(setSelectedWindow:)] pub fn setSelectedWindow(&self, window: &NSWindow); } ); winit-0.29.15/src/platform_impl/macos/appkit/text_input_client.rs000064400000000000000000000003141046102023000232670ustar 00000000000000use objc2::{extern_protocol, ProtocolType}; extern_protocol!( pub(crate) unsafe trait NSTextInputClient { // TODO: Methods } unsafe impl ProtocolType for dyn NSTextInputClient {} ); winit-0.29.15/src/platform_impl/macos/appkit/text_input_context.rs000064400000000000000000000015121046102023000234760ustar 00000000000000use icrate::Foundation::{NSObject, NSString}; use objc2::rc::Id; use objc2::{extern_class, extern_methods, mutability, ClassType}; type NSTextInputSourceIdentifier = NSString; extern_class!( /// Main-Thread-Only! #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSTextInputContext; unsafe impl ClassType for NSTextInputContext { type Super = NSObject; type Mutability = mutability::InteriorMutable; } ); extern_methods!( unsafe impl NSTextInputContext { #[method(invalidateCharacterCoordinates)] pub fn invalidateCharacterCoordinates(&self); #[method(discardMarkedText)] pub fn discardMarkedText(&self); #[method_id(selectedKeyboardInputSource)] pub fn selectedKeyboardInputSource(&self) -> Option>; } ); winit-0.29.15/src/platform_impl/macos/appkit/version.rs000064400000000000000000000060371046102023000212230ustar 00000000000000#[repr(transparent)] #[derive(PartialEq, PartialOrd, Debug, Clone, Copy)] pub struct NSAppKitVersion(f64); #[allow(dead_code)] #[allow(non_upper_case_globals)] impl NSAppKitVersion { pub fn current() -> Self { extern "C" { static NSAppKitVersionNumber: NSAppKitVersion; } unsafe { NSAppKitVersionNumber } } pub fn floor(self) -> Self { Self(self.0.floor()) } pub const NSAppKitVersionNumber10_0: Self = Self(577.0); pub const NSAppKitVersionNumber10_1: Self = Self(620.0); pub const NSAppKitVersionNumber10_2: Self = Self(663.0); pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6); pub const NSAppKitVersionNumber10_3: Self = Self(743.0); pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14); pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2); pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24); pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33); pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36); pub const NSAppKitVersionNumber10_4: Self = Self(824.0); pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1); pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23); pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33); pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41); pub const NSAppKitVersionNumber10_5: Self = Self(949.0); pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27); pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33); pub const NSAppKitVersionNumber10_6: Self = Self(1038.0); pub const NSAppKitVersionNumber10_7: Self = Self(1138.0); pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23); pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32); pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47); pub const NSAppKitVersionNumber10_8: Self = Self(1187.0); pub const NSAppKitVersionNumber10_9: Self = Self(1265.0); pub const NSAppKitVersionNumber10_10: Self = Self(1343.0); pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0); pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0); pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0); pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0); pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0); pub const NSAppKitVersionNumber10_11: Self = Self(1404.0); pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13); pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34); pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34); pub const NSAppKitVersionNumber10_12: Self = Self(1504.0); pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60); pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76); pub const NSAppKitVersionNumber10_13: Self = Self(1561.0); pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1); pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2); pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4); } winit-0.29.15/src/platform_impl/macos/appkit/view.rs000064400000000000000000000066121046102023000205070ustar 00000000000000use std::ffi::c_void; use std::num::NonZeroIsize; use std::ptr; use icrate::Foundation::{NSObject, NSPoint, NSRect}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::{NSCursor, NSResponder, NSTextInputContext, NSWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] pub(crate) struct NSView; unsafe impl ClassType for NSView { #[inherits(NSObject)] type Super = NSResponder; type Mutability = mutability::InteriorMutable; } ); // Documented as "Main Thread Only". // > generally thread safe, although operations on views such as creating, // > resizing, and moving should happen on the main thread. // // // > If you want to use a thread to draw to a view, bracket all drawing code // > between the lockFocusIfCanDraw and unlockFocus methods of NSView. // extern_methods!( /// Getter methods unsafe impl NSView { #[method(frame)] pub fn frame(&self) -> NSRect; #[method(bounds)] pub fn bounds(&self) -> NSRect; #[method_id(inputContext)] pub fn inputContext( &self, // _mtm: MainThreadMarker, ) -> Option>; #[method(hasMarkedText)] pub fn hasMarkedText(&self) -> bool; #[method(convertPoint:fromView:)] pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint; #[method_id(window)] pub fn window(&self) -> Option>; } unsafe impl NSView { #[method(setWantsBestResolutionOpenGLSurface:)] pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool); #[method(setWantsLayer:)] pub fn setWantsLayer(&self, wants_layer: bool); #[method(setPostsFrameChangedNotifications:)] pub fn setPostsFrameChangedNotifications(&self, value: bool); #[method(removeTrackingRect:)] pub fn removeTrackingRect(&self, tag: NSTrackingRectTag); #[method(addTrackingRect:owner:userData:assumeInside:)] unsafe fn inner_addTrackingRect( &self, rect: NSRect, owner: &AnyObject, user_data: *mut c_void, assume_inside: bool, ) -> Option; pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag { // SAFETY: The user data is NULL, so it is valid unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) } .expect("failed creating tracking rect") } #[method(addCursorRect:cursor:)] // NSCursor safe to take by shared reference since it is already immutable pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor); #[method(setHidden:)] pub fn setHidden(&self, hidden: bool); } ); /// pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero! winit-0.29.15/src/platform_impl/macos/appkit/window.rs000064400000000000000000000353451046102023000210510ustar 00000000000000use icrate::Foundation::{ CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger, }; use objc2::encode::{Encode, Encoding}; use objc2::rc::Id; use objc2::runtime::AnyObject; use objc2::{extern_class, extern_methods, mutability, ClassType}; use super::{ NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView, NSWindowTabGroup, }; extern_class!( /// Main-Thread-Only! #[derive(Debug, PartialEq, Eq, Hash)] pub struct NSWindow; unsafe impl ClassType for NSWindow { #[inherits(NSObject)] type Super = NSResponder; type Mutability = mutability::InteriorMutable; } ); // Documented as "Main Thread Only", but: // > Thread safe in that you can create and manage them on a secondary thread. // // // // So could in theory be `Send`, and perhaps also `Sync` - but we would like // interior mutability on windows, since that's just much easier, and in that // case, they can't be! extern_methods!( unsafe impl NSWindow { #[method(frame)] pub(crate) fn frame(&self) -> NSRect; #[method(windowNumber)] pub(crate) fn windowNumber(&self) -> NSInteger; #[method(backingScaleFactor)] pub(crate) fn backingScaleFactor(&self) -> CGFloat; #[method_id(contentView)] pub(crate) fn contentView(&self) -> Id; #[method(setContentView:)] pub(crate) fn setContentView(&self, view: &NSView); #[method(setInitialFirstResponder:)] pub(crate) fn setInitialFirstResponder(&self, view: &NSView); #[method(makeFirstResponder:)] #[must_use] pub(crate) fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool; #[method(contentRectForFrameRect:)] pub(crate) fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect; #[method_id(screen)] pub(crate) fn screen(&self) -> Option>; #[method(setContentSize:)] pub(crate) fn setContentSize(&self, contentSize: NSSize); #[method(setFrameTopLeftPoint:)] pub(crate) fn setFrameTopLeftPoint(&self, point: NSPoint); #[method(setMinSize:)] pub(crate) fn setMinSize(&self, minSize: NSSize); #[method(setMaxSize:)] pub(crate) fn setMaxSize(&self, maxSize: NSSize); #[method(setResizeIncrements:)] pub(crate) fn setResizeIncrements(&self, increments: NSSize); #[method(contentResizeIncrements)] pub(crate) fn contentResizeIncrements(&self) -> NSSize; #[method(setContentResizeIncrements:)] pub(crate) fn setContentResizeIncrements(&self, increments: NSSize); #[method(setFrame:display:)] pub(crate) fn setFrame_display(&self, frameRect: NSRect, flag: bool); #[method(setMovable:)] pub(crate) fn setMovable(&self, movable: bool); #[method(setSharingType:)] pub(crate) fn setSharingType(&self, sharingType: NSWindowSharingType); #[method(setTabbingMode:)] pub(crate) fn setTabbingMode(&self, tabbingMode: NSWindowTabbingMode); #[method(setOpaque:)] pub(crate) fn setOpaque(&self, opaque: bool); #[method(hasShadow)] pub(crate) fn hasShadow(&self) -> bool; #[method(setHasShadow:)] pub(crate) fn setHasShadow(&self, has_shadow: bool); #[method(setIgnoresMouseEvents:)] pub(crate) fn setIgnoresMouseEvents(&self, ignores: bool); #[method(setBackgroundColor:)] pub(crate) fn setBackgroundColor(&self, color: &NSColor); #[method(styleMask)] pub(crate) fn styleMask(&self) -> NSWindowStyleMask; #[method(setStyleMask:)] pub(crate) fn setStyleMask(&self, mask: NSWindowStyleMask); #[method(registerForDraggedTypes:)] pub(crate) fn registerForDraggedTypes(&self, types: &NSArray); #[method(makeKeyAndOrderFront:)] pub(crate) fn makeKeyAndOrderFront(&self, sender: Option<&AnyObject>); #[method(orderFront:)] pub(crate) fn orderFront(&self, sender: Option<&AnyObject>); #[method(miniaturize:)] pub(crate) fn miniaturize(&self, sender: Option<&AnyObject>); #[method(deminiaturize:)] pub(crate) fn deminiaturize(&self, sender: Option<&AnyObject>); #[method(toggleFullScreen:)] pub(crate) fn toggleFullScreen(&self, sender: Option<&AnyObject>); #[method(orderOut:)] pub(crate) fn orderOut(&self, sender: Option<&AnyObject>); #[method(zoom:)] pub(crate) fn zoom(&self, sender: Option<&AnyObject>); #[method(selectNextKeyView:)] pub(crate) fn selectNextKeyView(&self, sender: Option<&AnyObject>); #[method(selectPreviousKeyView:)] pub(crate) fn selectPreviousKeyView(&self, sender: Option<&AnyObject>); #[method_id(firstResponder)] pub(crate) fn firstResponder(&self) -> Option>; #[method_id(standardWindowButton:)] pub(crate) fn standardWindowButton(&self, kind: NSWindowButton) -> Option>; #[method(setTitle:)] pub(crate) fn setTitle(&self, title: &NSString); #[method_id(title)] pub(crate) fn title_(&self) -> Id; #[method(setReleasedWhenClosed:)] pub(crate) fn setReleasedWhenClosed(&self, val: bool); #[method(setAcceptsMouseMovedEvents:)] pub(crate) fn setAcceptsMouseMovedEvents(&self, val: bool); #[method(setTitlebarAppearsTransparent:)] pub(crate) fn setTitlebarAppearsTransparent(&self, val: bool); #[method(setTitleVisibility:)] pub(crate) fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility); #[method(setMovableByWindowBackground:)] pub(crate) fn setMovableByWindowBackground(&self, val: bool); #[method(setLevel:)] pub(crate) fn setLevel(&self, level: NSWindowLevel); #[method(setAllowsAutomaticWindowTabbing:)] pub(crate) fn setAllowsAutomaticWindowTabbing(val: bool); #[method(setTabbingIdentifier:)] pub(crate) fn setTabbingIdentifier(&self, identifier: &NSString); #[method(setDocumentEdited:)] pub(crate) fn setDocumentEdited(&self, val: bool); #[method(occlusionState)] pub(crate) fn occlusionState(&self) -> NSWindowOcclusionState; #[method(center)] pub(crate) fn center(&self); #[method(isResizable)] pub(crate) fn isResizable(&self) -> bool; #[method(isMiniaturizable)] pub(crate) fn isMiniaturizable(&self) -> bool; #[method(hasCloseBox)] pub(crate) fn hasCloseBox(&self) -> bool; #[method(isMiniaturized)] pub(crate) fn isMiniaturized(&self) -> bool; #[method(isVisible)] pub(crate) fn isVisible(&self) -> bool; #[method(isKeyWindow)] pub(crate) fn isKeyWindow(&self) -> bool; #[method(isZoomed)] pub(crate) fn isZoomed(&self) -> bool; #[method(allowsAutomaticWindowTabbing)] pub(crate) fn allowsAutomaticWindowTabbing() -> bool; #[method(selectNextTab)] pub(crate) fn selectNextTab(&self); #[method_id(tabbingIdentifier)] pub(crate) fn tabbingIdentifier(&self) -> Id; #[method_id(tabGroup)] pub(crate) fn tabGroup(&self) -> Option>; #[method(isDocumentEdited)] pub(crate) fn isDocumentEdited(&self) -> bool; #[method(close)] pub(crate) fn close(&self); #[method(performWindowDragWithEvent:)] // TODO: Can this actually accept NULL? pub(crate) fn performWindowDragWithEvent(&self, event: Option<&NSEvent>); #[method(invalidateCursorRectsForView:)] pub(crate) fn invalidateCursorRectsForView(&self, view: &NSView); #[method(setDelegate:)] pub(crate) fn setDelegate(&self, delegate: Option<&NSObject>); #[method(sendEvent:)] pub(crate) unsafe fn sendEvent(&self, event: &NSEvent); #[method(addChildWindow:ordered:)] pub(crate) unsafe fn addChildWindow(&self, child: &NSWindow, ordered: NSWindowOrderingMode); } ); #[allow(dead_code)] #[repr(isize)] // NSInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSWindowTitleVisibility { #[doc(alias = "NSWindowTitleVisible")] Visible = 0, #[doc(alias = "NSWindowTitleHidden")] Hidden = 1, } unsafe impl Encode for NSWindowTitleVisibility { const ENCODING: Encoding = NSInteger::ENCODING; } #[allow(dead_code)] #[repr(usize)] // NSUInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSWindowButton { #[doc(alias = "NSWindowCloseButton")] Close = 0, #[doc(alias = "NSWindowMiniaturizeButton")] Miniaturize = 1, #[doc(alias = "NSWindowZoomButton")] Zoom = 2, #[doc(alias = "NSWindowToolbarButton")] Toolbar = 3, #[doc(alias = "NSWindowDocumentIconButton")] DocumentIcon = 4, #[doc(alias = "NSWindowDocumentVersionsButton")] DocumentVersions = 6, #[doc(alias = "NSWindowFullScreenButton")] #[deprecated = "Deprecated since macOS 10.12"] FullScreen = 7, } unsafe impl Encode for NSWindowButton { const ENCODING: Encoding = NSUInteger::ENCODING; } // CGWindowLevel.h // // Note: There are two different things at play in this header: // `CGWindowLevel` and `CGWindowLevelKey`. // // It seems like there was a push towards using "key" values instead of the // raw window level values, and then you were supposed to use // `CGWindowLevelForKey` to get the actual level. // // But the values that `NSWindowLevel` has are compiled in, and as such has // to remain ABI compatible, so they're safe for us to hardcode as well. #[allow(dead_code)] mod window_level { const kCGNumReservedWindowLevels: i32 = 16; const kCGNumReservedBaseWindowLevels: i32 = 5; pub const kCGBaseWindowLevel: i32 = i32::MIN; pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels; pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels; pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20; pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20; pub const kCGBackstopMenuLevel: i32 = -20; pub const kCGNormalWindowLevel: i32 = 0; pub const kCGFloatingWindowLevel: i32 = 3; pub const kCGTornOffMenuWindowLevel: i32 = 3; pub const kCGModalPanelWindowLevel: i32 = 8; pub const kCGUtilityWindowLevel: i32 = 19; pub const kCGDockWindowLevel: i32 = 20; pub const kCGMainMenuWindowLevel: i32 = 24; pub const kCGStatusWindowLevel: i32 = 25; pub const kCGPopUpMenuWindowLevel: i32 = 101; pub const kCGOverlayWindowLevel: i32 = 102; pub const kCGHelpWindowLevel: i32 = 200; pub const kCGDraggingWindowLevel: i32 = 500; pub const kCGScreenSaverWindowLevel: i32 = 1000; pub const kCGAssistiveTechHighWindowLevel: i32 = 1500; pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1; } use window_level::*; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct NSWindowLevel(pub NSInteger); #[allow(dead_code)] impl NSWindowLevel { #[doc(alias = "BelowNormalWindowLevel")] pub const BELOW_NORMAL: Self = Self((kCGNormalWindowLevel - 1) as _); #[doc(alias = "NSNormalWindowLevel")] pub const Normal: Self = Self(kCGNormalWindowLevel as _); #[doc(alias = "NSFloatingWindowLevel")] pub const Floating: Self = Self(kCGFloatingWindowLevel as _); #[doc(alias = "NSTornOffMenuWindowLevel")] pub const TornOffMenu: Self = Self(kCGTornOffMenuWindowLevel as _); #[doc(alias = "NSModalPanelWindowLevel")] pub const ModalPanel: Self = Self(kCGModalPanelWindowLevel as _); #[doc(alias = "NSMainMenuWindowLevel")] pub const MainMenu: Self = Self(kCGMainMenuWindowLevel as _); #[doc(alias = "NSStatusWindowLevel")] pub const Status: Self = Self(kCGStatusWindowLevel as _); #[doc(alias = "NSPopUpMenuWindowLevel")] pub const PopUpMenu: Self = Self(kCGPopUpMenuWindowLevel as _); #[doc(alias = "NSScreenSaverWindowLevel")] pub const ScreenSaver: Self = Self(kCGScreenSaverWindowLevel as _); } unsafe impl Encode for NSWindowLevel { const ENCODING: Encoding = NSInteger::ENCODING; } bitflags! { #[derive(Clone, Copy)] pub struct NSWindowOcclusionState: NSUInteger { const NSWindowOcclusionStateVisible = 1 << 1; } } unsafe impl Encode for NSWindowOcclusionState { const ENCODING: Encoding = NSUInteger::ENCODING; } bitflags! { #[derive(Debug, Clone, Copy)] pub struct NSWindowStyleMask: NSUInteger { const NSBorderlessWindowMask = 0; const NSTitledWindowMask = 1 << 0; const NSClosableWindowMask = 1 << 1; const NSMiniaturizableWindowMask = 1 << 2; const NSResizableWindowMask = 1 << 3; const NSTexturedBackgroundWindowMask = 1 << 8; const NSUnifiedTitleAndToolbarWindowMask = 1 << 12; const NSFullScreenWindowMask = 1 << 14; const NSFullSizeContentViewWindowMask = 1 << 15; } } unsafe impl Encode for NSWindowStyleMask { const ENCODING: Encoding = NSUInteger::ENCODING; } #[allow(dead_code)] #[repr(usize)] // NSUInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSBackingStoreType { NSBackingStoreRetained = 0, NSBackingStoreNonretained = 1, NSBackingStoreBuffered = 2, } unsafe impl Encode for NSBackingStoreType { const ENCODING: Encoding = NSUInteger::ENCODING; } #[allow(dead_code)] #[repr(usize)] // NSUInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSWindowSharingType { NSWindowSharingNone = 0, NSWindowSharingReadOnly = 1, NSWindowSharingReadWrite = 2, } unsafe impl Encode for NSWindowSharingType { const ENCODING: Encoding = NSUInteger::ENCODING; } #[allow(dead_code)] #[repr(isize)] // NSInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSWindowOrderingMode { NSWindowAbove = 1, NSWindowBelow = -1, NSWindowOut = 0, } unsafe impl Encode for NSWindowOrderingMode { const ENCODING: Encoding = NSInteger::ENCODING; } #[allow(dead_code)] #[repr(isize)] // NSInteger #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum NSWindowTabbingMode { NSWindowTabbingModeAutomatic = 0, NSWindowTabbingModeDisallowed = 2, NSWindowTabbingModePreferred = 1, } unsafe impl Encode for NSWindowTabbingMode { const ENCODING: Encoding = NSInteger::ENCODING; } winit-0.29.15/src/platform_impl/macos/event.rs000064400000000000000000000561561046102023000173760ustar 00000000000000use std::ffi::c_void; use core_foundation::{ base::CFRelease, data::{CFDataGetBytePtr, CFDataRef}, }; use icrate::Foundation::MainThreadMarker; use smol_str::SmolStr; use super::appkit::{NSEvent, NSEventModifierFlags}; use crate::{ event::{ElementState, KeyEvent, Modifiers}, keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }, platform::{ modifier_supplement::KeyEventExtModifierSupplement, scancode::PhysicalKeyExtScancode, }, platform_impl::platform::ffi, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifiers: Option, pub key_without_modifiers: Key, } impl KeyEventExtModifierSupplement for KeyEvent { fn text_with_all_modifiers(&self) -> Option<&str> { self.platform_specific .text_with_all_modifiers .as_ref() .map(|s| s.as_str()) } fn key_without_modifiers(&self) -> Key { self.platform_specific.key_without_modifiers.clone() } } /// Ignores ALL modifiers. pub fn get_modifierless_char(scancode: u16) -> Key { 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(NativeKey::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(NativeKey::MacOS(scancode)); } layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; } let keyboard_type = MainThreadMarker::run_on_main(|_mtm| 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(NativeKey::MacOS(scancode)); } if result_len == 0 { // This is fine - not all keys have text representation. // For instance, users that have mapped the `Fn` key to toggle // keyboard layouts will hit this code path. return Key::Unidentified(NativeKey::MacOS(scancode)); } let chars = String::from_utf16_lossy(&string[0..result_len as usize]); Key::Character(SmolStr::new(chars)) } // Ignores all modifiers except for SHIFT (yes, even ALT is ignored). fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { let string = ns_event .charactersIgnoringModifiers() .map(|s| s.to_string()) .unwrap_or_default(); if string.is_empty() { // Probably a dead key let first_char = modifierless_chars.chars().next(); return Key::Dead(first_char); } Key::Character(SmolStr::new(string)) } /// Create `KeyEvent` for the given `NSEvent`. /// /// This function shouldn't be called when the IME input is in process. pub(crate) fn create_key_event( ns_event: &NSEvent, is_press: bool, is_repeat: bool, key_override: Option, ) -> KeyEvent { use ElementState::{Pressed, Released}; let state = if is_press { Pressed } else { Released }; let scancode = ns_event.key_code(); let mut physical_key = key_override.unwrap_or_else(|| PhysicalKey::from_scancode(scancode as u32)); // NOTE: The logical key should heed both SHIFT and ALT if possible. // For instance: // * Pressing the A key: logical key should be "a" // * Pressing SHIFT A: logical key should be "A" // * Pressing CTRL SHIFT A: logical key should also be "A" // This is not easy to tease out of `NSEvent`, but we do our best. let text_with_all_modifiers: Option = if key_override.is_some() { None } else { let characters = ns_event .characters() .map(|s| s.to_string()) .unwrap_or_default(); if characters.is_empty() { None } else { if matches!(physical_key, PhysicalKey::Unidentified(_)) { // The key may be one of the funky function keys physical_key = extra_function_key_to_code(scancode, &characters); } Some(SmolStr::new(characters)) } }; let key_from_code = code_to_key(physical_key, scancode); let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) { // `get_modifierless_char/key_without_modifiers` ignores ALL modifiers. let key_without_modifiers = get_modifierless_char(scancode); let modifiers = NSEvent::modifierFlags(ns_event); let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); let has_cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask); let logical_key = match text_with_all_modifiers.as_ref() { // Only checking for ctrl and cmd here, not checking for alt because we DO want to // include its effect in the key. For example if -on the Germay layout- one // presses alt+8, the logical key should be "{" // Also not checking if this is a release event because then this issue would // still affect the key release. Some(text) if !has_ctrl && !has_cmd => { // Character heeding both SHIFT and ALT. Key::Character(text.clone()) } _ => match key_without_modifiers.as_ref() { // Character heeding just SHIFT, ignoring ALT. Key::Character(ch) => get_logical_key_char(ns_event, ch), // Character ignoring ALL modifiers. _ => key_without_modifiers.clone(), }, }; (logical_key, key_without_modifiers) } else { (key_from_code.clone(), key_from_code) }; let text = if is_press { logical_key.to_text().map(SmolStr::new) } else { None }; let location = code_to_location(physical_key); KeyEvent { location, logical_key, physical_key, repeat: is_repeat, state, text, platform_specific: KeyEventExtra { key_without_modifiers, text_with_all_modifiers, }, } } pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()), }; Key::Named(match code { KeyCode::Enter => NamedKey::Enter, KeyCode::Tab => NamedKey::Tab, KeyCode::Space => NamedKey::Space, KeyCode::Backspace => NamedKey::Backspace, KeyCode::Escape => NamedKey::Escape, KeyCode::SuperRight => NamedKey::Super, KeyCode::SuperLeft => NamedKey::Super, KeyCode::ShiftLeft => NamedKey::Shift, KeyCode::AltLeft => NamedKey::Alt, KeyCode::ControlLeft => NamedKey::Control, KeyCode::ShiftRight => NamedKey::Shift, KeyCode::AltRight => NamedKey::Alt, KeyCode::ControlRight => NamedKey::Control, KeyCode::NumLock => NamedKey::NumLock, KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp, KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown, // Other numpad keys all generate text on macOS (if I understand correctly) KeyCode::NumpadEnter => NamedKey::Enter, KeyCode::F1 => NamedKey::F1, KeyCode::F2 => NamedKey::F2, KeyCode::F3 => NamedKey::F3, KeyCode::F4 => NamedKey::F4, KeyCode::F5 => NamedKey::F5, KeyCode::F6 => NamedKey::F6, KeyCode::F7 => NamedKey::F7, KeyCode::F8 => NamedKey::F8, KeyCode::F9 => NamedKey::F9, KeyCode::F10 => NamedKey::F10, KeyCode::F11 => NamedKey::F11, KeyCode::F12 => NamedKey::F12, KeyCode::F13 => NamedKey::F13, KeyCode::F14 => NamedKey::F14, KeyCode::F15 => NamedKey::F15, KeyCode::F16 => NamedKey::F16, KeyCode::F17 => NamedKey::F17, KeyCode::F18 => NamedKey::F18, KeyCode::F19 => NamedKey::F19, KeyCode::F20 => NamedKey::F20, KeyCode::Insert => NamedKey::Insert, KeyCode::Home => NamedKey::Home, KeyCode::PageUp => NamedKey::PageUp, KeyCode::Delete => NamedKey::Delete, KeyCode::End => NamedKey::End, KeyCode::PageDown => NamedKey::PageDown, KeyCode::ArrowLeft => NamedKey::ArrowLeft, KeyCode::ArrowRight => NamedKey::ArrowRight, KeyCode::ArrowDown => NamedKey::ArrowDown, KeyCode::ArrowUp => NamedKey::ArrowUp, _ => return Key::Unidentified(NativeKey::MacOS(scancode)), }) } pub fn code_to_location(key: PhysicalKey) -> KeyLocation { let code = match key { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(_) => return KeyLocation::Standard, }; 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) -> PhysicalKey { if let Some(ch) = string.encode_utf16().next() { match ch { 0xf718 => PhysicalKey::Code(KeyCode::F21), 0xf719 => PhysicalKey::Code(KeyCode::F22), 0xf71a => PhysicalKey::Code(KeyCode::F23), 0xf71b => PhysicalKey::Code(KeyCode::F24), _ => PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)), } } else { PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode)) } } pub(super) fn event_mods(event: &NSEvent) -> Modifiers { let flags = event.modifierFlags(); let mut state = ModifiersState::empty(); let mut pressed_mods = ModifiersKeys::empty(); state.set( ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSShiftKeyMask), ); pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed()); pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed()); state.set( ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::NSControlKeyMask), ); pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed()); pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed()); state.set( ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSAlternateKeyMask), ); pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed()); pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed()); state.set( ModifiersState::SUPER, flags.contains(NSEventModifierFlags::NSCommandKeyMask), ); pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed()); pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed()); Modifiers { state, pressed_mods, } } impl PhysicalKeyExtScancode for PhysicalKey { fn to_scancode(self) -> Option { let code = match self { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(_) => return None, }; 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, } } fn from_scancode(scancode: u32) -> PhysicalKey { PhysicalKey::Code(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, 0x4a => 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, _ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)), }) } } winit-0.29.15/src/platform_impl/macos/event_loop.rs000064400000000000000000000467031046102023000204240ustar 00000000000000use std::{ any::Any, cell::{Cell, RefCell}, collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe}, ptr, rc::{Rc, Weak}, sync::mpsc, time::{Duration, Instant}, }; use core_foundation::base::{CFIndex, CFRelease}; use core_foundation::runloop::{ kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, }; use icrate::Foundation::MainThreadMarker; use objc2::rc::{autoreleasepool, Id}; use objc2::runtime::NSObjectProtocol; use objc2::{msg_send_id, ClassType}; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent, NSWindow}; use crate::{ error::EventLoopError, event::Event, event_loop::{ ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget, }, platform::{macos::ActivationPolicy, pump_events::PumpStatus}, platform_impl::platform::{ app::WinitApplication, app_delegate::ApplicationDelegate, app_state::{AppState, Callback}, monitor::{self, MonitorHandle}, observer::setup_control_flow_observers, }, }; #[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(Debug)] pub struct EventLoopWindowTarget { mtm: MainThreadMarker, p: PhantomData, } impl EventLoopWindowTarget { #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } #[inline] pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(monitor) } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[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(), )) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { AppState::set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { AppState::control_flow() } pub(crate) fn exit(&self) { AppState::exit() } pub(crate) fn clear_exit(&self) { AppState::clear_exit() } pub(crate) fn exiting(&self) -> bool { AppState::exiting() } } impl EventLoopWindowTarget { pub(crate) fn hide_application(&self) { NSApplication::shared(self.mtm).hide(None) } pub(crate) fn hide_other_applications(&self) { NSApplication::shared(self.mtm).hideOtherApplications(None) } pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) { NSWindow::setAllowsAutomaticWindowTabbing(enabled) } pub(crate) fn allows_automatic_window_tabbing(&self) -> bool { NSWindow::allowsAutomaticWindowTabbing() } } pub struct EventLoop { /// Store a reference to the application for convenience. /// /// We intentially don't store `WinitApplication` since we want to have /// the possiblity of swapping that out at some point. app: Id, /// The delegate is only weakly referenced by NSApplication, so we keep /// it around here as well. _delegate: Id, // Event sender and receiver, used for EventLoopProxy. sender: mpsc::Sender, receiver: Rc>, 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>>, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { pub(crate) activation_policy: ActivationPolicy, pub(crate) default_menu: bool, pub(crate) activate_ignoring_other_apps: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { activation_policy: Default::default(), // Regular default_menu: true, activate_ignoring_other_apps: true, } } } impl EventLoop { pub(crate) fn new( attributes: &PlatformSpecificEventLoopAttributes, ) -> Result { let mtm = MainThreadMarker::new() .expect("on macOS, `EventLoop` must be created on the main thread!"); let app: Id = unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; if !app.is_kind_of::() { panic!("`winit` requires control over the principal class. You must create the event loop before other parts of your application initialize NSApplication"); } use NSApplicationActivationPolicy::*; let activation_policy = match attributes.activation_policy { ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, }; let delegate = ApplicationDelegate::new( activation_policy, attributes.default_menu, attributes.activate_ignoring_other_apps, ); autoreleasepool(|_| { app.setDelegate(&delegate); }); let panic_info: Rc = Default::default(); setup_control_flow_observers(Rc::downgrade(&panic_info)); let (sender, receiver) = mpsc::channel(); Ok(EventLoop { app, _delegate: delegate, sender, receiver: Rc::new(receiver), window_target: Rc::new(RootWindowTarget { p: EventLoopWindowTarget { mtm, p: PhantomData, }, _marker: PhantomData, }), panic_info, _callback: None, }) } pub fn window_target(&self) -> &RootWindowTarget { &self.window_target } pub fn run(mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootWindowTarget), { self.run_on_demand(callback) } // NB: we don't base this on `pump_events` because for `MacOs` we can't support // `pump_events` elegantly (we just ask to run the loop for a "short" amount of // time and so a layered implementation would end up using a lot of CPU due to // redundant wake ups. pub fn run_on_demand(&mut self, callback: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootWindowTarget), { if AppState::is_running() { return Err(EventLoopError::AlreadyRunning); } // # Safety // We are erasing the lifetime of the application callback here so that we // can (temporarily) store it within 'static global `AppState` that's // accessible to objc delegate callbacks. // // The safety of this depends on on making sure to also clear the callback // from the global `AppState` before we return from here, ensuring that // we don't retain a reference beyond the real lifetime of the callback. let callback = unsafe { mem::transmute::< Rc, &RootWindowTarget)>>, Rc, &RootWindowTarget)>>, >(Rc::new(RefCell::new(callback))) }; self._callback = Some(Rc::clone(&callback)); autoreleasepool(|_| { // 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); drop(callback); // # Safety // We make sure to call `AppState::clear_callback` before returning unsafe { AppState::set_callback( weak_cb, Rc::clone(&self.window_target), Rc::clone(&self.receiver), ); } // catch panics to make sure we can't unwind without clearing the set callback // (which would leave the global `AppState` in an undefined, unsafe state) let catch_result = catch_unwind(AssertUnwindSafe(|| { // clear / normalize pump_events state AppState::set_wait_timeout(None); AppState::set_stop_app_before_wait(false); AppState::set_stop_app_after_wait(false); AppState::set_stop_app_on_redraw_requested(false); if AppState::is_launched() { debug_assert!(!AppState::is_running()); AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` } unsafe { self.app.run() }; // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the `NSApp` and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } AppState::internal_exit() })); // # Safety // This pairs up with the `unsafe` call to `set_callback` above and ensures that // we always clear the application callback from the global `AppState` before // returning drop(self._callback.take()); AppState::clear_callback(); if let Err(payload) = catch_result { resume_unwind(payload) } }); Ok(()) } pub fn pump_events(&mut self, timeout: Option, callback: F) -> PumpStatus where F: FnMut(Event, &RootWindowTarget), { // # Safety // We are erasing the lifetime of the application callback here so that we // can (temporarily) store it within 'static global `AppState` that's // accessible to objc delegate callbacks. // // The safety of this depends on on making sure to also clear the callback // from the global `AppState` before we return from here, ensuring that // we don't retain a reference beyond the real lifetime of the callback. let callback = unsafe { mem::transmute::< Rc, &RootWindowTarget)>>, Rc, &RootWindowTarget)>>, >(Rc::new(RefCell::new(callback))) }; self._callback = Some(Rc::clone(&callback)); autoreleasepool(|_| { let app = NSApp(); // 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); drop(callback); // # Safety // We will make sure to call `AppState::clear_callback` before returning // to ensure that we don't hold on to the callback beyond its (erased) // lifetime unsafe { AppState::set_callback( weak_cb, Rc::clone(&self.window_target), Rc::clone(&self.receiver), ); } // catch panics to make sure we can't unwind without clearing the set callback // (which would leave the global `AppState` in an undefined, unsafe state) let catch_result = catch_unwind(AssertUnwindSafe(|| { // As a special case, if the `NSApp` hasn't been launched yet then we at least run // the loop until it has fully launched. if !AppState::is_launched() { debug_assert!(!AppState::is_running()); AppState::request_stop_on_launch(); unsafe { app.run(); } // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched } else if !AppState::is_running() { // Even though the NSApp may have been launched, it's possible we aren't running // if the `EventLoop` was run before and has since exited. This indicates that // we just starting to re-run the same `EventLoop` again. AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` } else { // Only run the NSApp for as long as the given `Duration` allows so we // don't block the external loop. match timeout { Some(Duration::ZERO) => { AppState::set_wait_timeout(None); AppState::set_stop_app_before_wait(true); } Some(duration) => { AppState::set_stop_app_before_wait(false); let timeout = Instant::now() + duration; AppState::set_wait_timeout(Some(timeout)); AppState::set_stop_app_after_wait(true); } None => { AppState::set_wait_timeout(None); AppState::set_stop_app_before_wait(false); AppState::set_stop_app_after_wait(true); } } AppState::set_stop_app_on_redraw_requested(true); unsafe { app.run(); } } // While the app is running it's possible that we catch a panic // to avoid unwinding across an objective-c ffi boundary, which // will lead to us stopping the `NSApp` and saving the // `PanicInfo` so that we can resume the unwind at a controlled, // safe point in time. if let Some(panic) = self.panic_info.take() { resume_unwind(panic); } if AppState::exiting() { AppState::internal_exit(); PumpStatus::Exit(0) } else { PumpStatus::Continue } })); // # Safety // This pairs up with the `unsafe` call to `set_callback` above and ensures that // we always clear the application callback from the global `AppState` before // returning AppState::clear_callback(); drop(self._callback.take()); match catch_result { Ok(pump_status) => pump_status, Err(payload) => resume_unwind(payload), } }) } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.sender.clone()) } } /// 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); } let app = NSApp(); app.stop(None); // 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 app.postEvent_atStart(&NSEvent::dummy(), true); None } } } pub struct EventLoopProxy { sender: mpsc::Sender, source: CFRunLoopSourceRef, } unsafe impl Send for EventLoopProxy {} impl Drop for EventLoopProxy { fn drop(&mut self) { unsafe { CFRelease(self.source as _); } } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy::new(self.sender.clone()) } } impl EventLoopProxy { fn new(sender: mpsc::Sender) -> Self { unsafe { // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *const 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 { version: 0, info: ptr::null_mut(), retain: None, release: None, copyDescription: None, equal: None, hash: None, schedule: None, cancel: None, perform: event_loop_proxy_handler, }; let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 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(|mpsc::SendError(x)| EventLoopClosed(x))?; unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); let rl = CFRunLoopGetMain(); CFRunLoopWakeUp(rl); } Ok(()) } } winit-0.29.15/src/platform_impl/macos/ffi.rs000064400000000000000000000157311046102023000170130ustar 00000000000000// TODO: Upstream these #![allow(dead_code, non_snake_case, non_upper_case_globals)] use std::ffi::c_void; use core_foundation::{ array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, }; use core_graphics::{ base::CGError, display::{CGDirectDisplayID, CGDisplayConfigRef}, }; use objc2::{ffi::NSInteger, runtime::AnyObject}; 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 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 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); // Wildly used private APIs; Apple uses them for their Terminal.app. pub fn CGSMainConnectionID() -> *mut AnyObject; pub fn CGSSetWindowBackgroundBlurRadius( connection_id: *mut AnyObject, window_id: NSInteger, radius: i64, ) -> i32; } 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::*; #[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 = std::os::raw::c_ushort; 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, ) -> *mut c_void; 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; } winit-0.29.15/src/platform_impl/macos/menu.rs000064400000000000000000000055051046102023000172110ustar 00000000000000use icrate::ns_string; use icrate::Foundation::{NSProcessInfo, NSString}; use objc2::rc::Id; use objc2::runtime::Sel; use objc2::sel; use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem}; struct KeyEquivalent<'a> { key: &'a NSString, masks: Option, } pub fn initialize() { let menubar = NSMenu::new(); let app_menu_item = NSMenuItem::new(); menubar.addItem(&app_menu_item); let app_menu = NSMenu::new(); let process_name = NSProcessInfo::processInfo().processName(); // About menu item let about_item_title = ns_string!("About ").stringByAppendingString(&process_name); let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None); // Seperator menu item let sep_first = NSMenuItem::separatorItem(); // Hide application menu item let hide_item_title = ns_string!("Hide ").stringByAppendingString(&process_name); let hide_item = menu_item( &hide_item_title, sel!(hide:), Some(KeyEquivalent { key: ns_string!("h"), masks: None, }), ); // Hide other applications menu item let hide_others_item_title = ns_string!("Hide Others"); let hide_others_item = menu_item( hide_others_item_title, sel!(hideOtherApplications:), Some(KeyEquivalent { key: ns_string!("h"), masks: Some( NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask, ), }), ); // Show applications menu item let show_all_item_title = ns_string!("Show All"); let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None); // Seperator menu item let sep = NSMenuItem::separatorItem(); // Quit application menu item let quit_item_title = ns_string!("Quit ").stringByAppendingString(&process_name); let quit_item = menu_item( &quit_item_title, sel!(terminate:), Some(KeyEquivalent { key: ns_string!("q"), masks: None, }), ); app_menu.addItem(&about_item); app_menu.addItem(&sep_first); app_menu.addItem(&hide_item); app_menu.addItem(&hide_others_item); app_menu.addItem(&show_all_item); app_menu.addItem(&sep); app_menu.addItem(&quit_item); app_menu_item.setSubmenu(&app_menu); let app = NSApp(); app.setMainMenu(&menubar); } fn menu_item( title: &NSString, selector: Sel, key_equivalent: Option>, ) -> Id { let (key, masks) = match key_equivalent { Some(ke) => (ke.key, ke.masks), None => (ns_string!(""), None), }; let item = NSMenuItem::newWithTitle(title, selector, key); if let Some(masks) = masks { item.setKeyEquivalentModifierMask(masks) } item } winit-0.29.15/src/platform_impl/macos/mod.rs000064400000000000000000000025341046102023000170230ustar 00000000000000#[macro_use] mod util; mod app; mod app_delegate; mod app_state; mod appkit; mod event; mod event_loop; mod ffi; mod menu; mod monitor; mod observer; mod view; mod window; mod window_delegate; use std::fmt; pub(crate) use self::{ event::KeyEventExtra, event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }, monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, WindowId}, }; use crate::event::DeviceId as RootDeviceId; pub(crate) use self::window::Window; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; impl DeviceId { pub const 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); #[derive(Debug)] pub enum OsError { CGError(core_graphics::base::CGError), CreationError(&'static str), } 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), } } } winit-0.29.15/src/platform_impl/macos/monitor.rs000064400000000000000000000233171046102023000177350ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::{collections::VecDeque, fmt}; use core_foundation::{ array::{CFArrayGetCount, CFArrayGetValueAtIndex}, base::{CFRelease, TCFType}, string::CFString, }; use core_graphics::display::{ CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, }; use objc2::rc::Id; use super::appkit::NSScreen; use super::ffi; use crate::dpi::{PhysicalPosition, PhysicalSize}; #[derive(Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, 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_millihertz == other.refresh_rate_millihertz && 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_millihertz.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_millihertz", &self.refresh_rate_millihertz) .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_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { 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) } 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)] 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 { match self.ns_screen() { Some(screen) => screen.backingScaleFactor() as f64, None => 1.0, // default to 1.0 when we can't find the screen } } pub fn refresh_rate_millihertz(&self) -> Option { unsafe { let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _); let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0); if refresh_rate > 0.0 { return Some((refresh_rate * 1000.0).round() as u32); } let mut display_link = std::ptr::null_mut(); if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link) != ffi::kCVReturnSuccess { return None; } let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link); ffi::CVDisplayLinkRelease(display_link); // This value is indefinite if an invalid display link was specified if time.flags & ffi::kCVTimeIsIndefinite != 0 { return None; } (time.time_scale as i64) .checked_div(time.time_value) .map(|v| (v * 1000) as u32) } } pub fn video_modes(&self) -> impl Iterator { let refresh_rate_millihertz = self.refresh_rate_millihertz().unwrap_or(0); 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_millihertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT let refresh_rate_millihertz = if cg_refresh_rate_millihertz > 0 { (cg_refresh_rate_millihertz * 1000) as u32 } else { refresh_rate_millihertz }; 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!() }; VideoMode { size: ( ffi::CGDisplayModeGetPixelWidth(mode) as u32, ffi::CGDisplayModeGetPixelHeight(mode) as u32, ), refresh_rate_millihertz, bit_depth, monitor: monitor.clone(), native_mode: NativeDisplayMode(mode), } }) } } pub(crate) fn ns_screen(&self) -> Option> { let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) }; NSScreen::screens().into_iter().find(|screen| { let other_native_id = screen.display_id(); let other_uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID) }; uuid == other_uuid }) } } winit-0.29.15/src/platform_impl/macos/observer.rs000064400000000000000000000167751046102023000201070ustar 00000000000000use std::{ ffi::c_void, panic::{AssertUnwindSafe, UnwindSafe}, ptr, rc::Weak, time::Instant, }; use crate::platform_impl::platform::{ app_state::AppState, event_loop::{stop_app_on_panic, PanicInfo}, ffi, }; use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease}; use core_foundation::date::CFAbsoluteTimeGetCurrent; use core_foundation::runloop::{ kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, }; unsafe fn control_flow_handler(panic_info: *mut c_void, f: F) where F: FnOnce(Weak) + UnwindSafe, { let info_from_raw = unsafe { 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`"); } _ => unreachable!(), } }); } } // end is queued with the lowest priority to ensure it is processed after other observers // without that, LoopExiting would get sent after AboutToWait 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(unsafe { CFRunLoopGetMain() }) } unsafe fn add_observer( &self, flags: CFOptionFlags, priority: CFIndex, handler: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, ) { let observer = unsafe { 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, ) }; unsafe { 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( kCFRunLoopAfterWaiting, CFIndex::min_value(), control_flow_begin_handler, &mut context as *mut _, ); run_loop.add_observer( kCFRunLoopExit | kCFRunLoopBeforeWaiting, CFIndex::max_value(), control_flow_end_handler, &mut context as *mut _, ); } } pub struct EventLoopWaker { timer: CFRunLoopTimerRef, /// An arbitrary instant in the past, that will trigger an immediate wake /// We save this as the `next_fire_date` for consistency so we can /// easily check if the next_fire_date needs updating. start_instant: Instant, /// This is what the `NextFireDate` has been set to. /// `None` corresponds to `waker.stop()` and `start_instant` is used /// for `waker.start()` next_fire_date: Option, } 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(), std::f64::MAX, 0.000_000_1, 0, 0, wakeup_main_loop, ptr::null_mut(), ); CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None, } } } } impl EventLoopWaker { pub fn stop(&mut self) { if self.next_fire_date.is_some() { self.next_fire_date = None; unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } } } pub fn start(&mut self) { if self.next_fire_date != Some(self.start_instant) { self.next_fire_date = Some(self.start_instant); unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } } } pub fn start_at(&mut self, instant: Option) { let now = Instant::now(); match instant { Some(instant) if now >= instant => { self.start(); } Some(instant) => { if self.next_fire_date != Some(instant) { self.next_fire_date = Some(instant); 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) } } } None => { self.stop(); } } } } winit-0.29.15/src/platform_impl/macos/util.rs000064400000000000000000000034231046102023000172170ustar 00000000000000use core_graphics::display::CGDisplay; use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; use crate::dpi::LogicalPosition; // Replace with `!` once stable #[derive(Debug)] pub enum Never {} pub const EMPTY_RANGE: NSRange = NSRange { location: NSNotFound as NSUInteger, length: 0, }; macro_rules! trace_scope { ($s:literal) => { let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s); }; } pub(crate) struct TraceGuard { module_path: &'static str, called_from_fn: &'static str, } impl TraceGuard { #[inline] pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self { trace!(target: module_path, "Triggered `{}`", called_from_fn); Self { module_path, called_from_fn, } } } impl Drop for TraceGuard { #[inline] fn drop(&mut self) { trace!(target: self.module_path, "Completed `{}`", self.called_from_fn); } } // 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 #[allow(clippy::unnecessary_cast)] pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64 } /// Converts from winit screen-coordinates to macOS screen-coordinates. /// Winit: 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 as CGFloat, CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, ) } winit-0.29.15/src/platform_impl/macos/view.rs000064400000000000000000001233551046102023000172230ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::boxed::Box; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, VecDeque}; use std::ptr::NonNull; use icrate::Foundation::{ NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{Id, WeakId}; use objc2::runtime::{AnyObject, Sel}; use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; use super::{ appkit::{ NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag, NSView, }, event::{code_to_key, code_to_location}, }; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{ DeviceEvent, ElementState, Event, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, }, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey}, platform::macos::{OptionAsAlt, WindowExtMacOS}, platform::scancode::PhysicalKeyExtScancode, platform_impl::platform::{ app_state::AppState, event::{create_key_event, event_mods}, util, window::WinitWindow, DEVICE_ID, }, window::WindowId, }; #[derive(Debug)] struct CursorState { visible: bool, cursor: Id, } impl Default for CursorState { fn default() -> Self { Self { visible: true, cursor: Default::default(), } } } #[derive(Debug, Eq, PartialEq, Clone, Copy, Default)] enum ImeState { #[default] /// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user. Disabled, /// The ground state of enabled IME input. It means that both Preedit and regular keyboard /// input could be start from it. Ground, /// The IME is in preedit. Preedit, /// The text was just commited, so the next input from the keyboard must be ignored. Commited, } bitflags! { #[derive(Debug, Clone, Copy, PartialEq)] struct ModLocationMask: u8 { const LEFT = 0b0001; const RIGHT = 0b0010; } } impl ModLocationMask { fn from_location(loc: KeyLocation) -> ModLocationMask { match loc { KeyLocation::Left => ModLocationMask::LEFT, KeyLocation::Right => ModLocationMask::RIGHT, _ => unreachable!(), } } } fn key_to_modifier(key: &Key) -> Option { match key { Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT), Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL), Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER), Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT), _ => None, } } fn get_right_modifier_code(key: &Key) -> KeyCode { match key { Key::Named(NamedKey::Alt) => KeyCode::AltRight, Key::Named(NamedKey::Control) => KeyCode::ControlRight, Key::Named(NamedKey::Shift) => KeyCode::ShiftRight, Key::Named(NamedKey::Super) => KeyCode::SuperRight, _ => unreachable!(), } } fn get_left_modifier_code(key: &Key) -> KeyCode { match key { Key::Named(NamedKey::Alt) => KeyCode::AltLeft, Key::Named(NamedKey::Control) => KeyCode::ControlLeft, Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft, Key::Named(NamedKey::Super) => KeyCode::SuperLeft, _ => unreachable!(), } } #[derive(Debug, Default)] pub struct ViewState { cursor_state: RefCell, ime_position: Cell>, ime_size: Cell>, modifiers: Cell, phys_modifiers: RefCell>, tracking_rect: Cell>, ime_state: Cell, input_source: RefCell, /// True iff the application wants IME events. /// /// Can be set using `set_ime_allowed` ime_allowed: Cell, /// True if the current key event should be forwarded /// to the application, even during IME forward_key_to_app: Cell, marked_text: RefCell>, accepts_first_mouse: bool, } declare_class!( #[derive(Debug)] #[allow(non_snake_case)] pub(super) struct WinitView { // Weak reference because the window keeps a strong reference to the view _ns_window: IvarDrop>, "__ns_window">, state: IvarDrop, "_state">, } mod ivars; unsafe impl ClassType for WinitView { #[inherits(NSResponder, NSObject)] type Super = NSView; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitView"; } unsafe impl WinitView { #[method(initWithId:acceptsFirstMouse:)] unsafe fn init_with_id( this: *mut Self, window: &WinitWindow, accepts_first_mouse: bool, ) -> Option> { let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; this.map(|this| { let state = ViewState { accepts_first_mouse, ..Default::default() }; Ivar::write( &mut this._ns_window, Box::new(WeakId::new(&window.retain())), ); Ivar::write(&mut this.state, Box::new(state)); this.setPostsFrameChangedNotifications(true); let notification_center: &AnyObject = unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] }; // About frame change let frame_did_change_notification_name = NSString::from_str("NSViewFrameDidChangeNotification"); #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![ notification_center, addObserver: &*this, selector: sel!(frameDidChange:), name: &*frame_did_change_notification_name, object: &*this, ]; } *this.state.input_source.borrow_mut() = this.current_input_source(); NonNull::from(this) }) } } unsafe impl WinitView { #[method(viewDidMoveToWindow)] fn view_did_move_to_window(&self) { trace_scope!("viewDidMoveToWindow"); if let Some(tracking_rect) = self.state.tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = self.add_tracking_rect(rect, false); self.state.tracking_rect.set(Some(tracking_rect)); } #[method(frameDidChange:)] fn frame_did_change(&self, _event: &NSEvent) { trace_scope!("frameDidChange:"); if let Some(tracking_rect) = self.state.tracking_rect.take() { self.removeTrackingRect(tracking_rect); } let rect = self.frame(); let tracking_rect = self.add_tracking_rect(rect, false); self.state.tracking_rect.set(Some(tracking_rect)); // Emit resize event here rather than from windowDidResize because: // 1. When a new window is created as a tab, the frame size may change without a window resize occurring. // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height). let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); let size = logical_size.to_physical::(self.scale_factor()); self.queue_event(WindowEvent::Resized(size)); } #[method(drawRect:)] fn draw_rect(&self, rect: NSRect) { trace_scope!("drawRect:"); // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. if let Some(window) = self._ns_window.load() { AppState::handle_redraw(WindowId(window.id())); } #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![super(self), drawRect: rect]; } } #[method(acceptsFirstResponder)] fn accepts_first_responder(&self) -> bool { trace_scope!("acceptsFirstResponder"); true } // 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` #[method_id(touchBar)] fn touch_bar(&self) -> Option> { trace_scope!("touchBar"); None } #[method(resetCursorRects)] fn reset_cursor_rects(&self) { trace_scope!("resetCursorRects"); let bounds = self.bounds(); let cursor_state = self.state.cursor_state.borrow(); // We correctly invoke `addCursorRect` only from inside `resetCursorRects` if cursor_state.visible { self.addCursorRect(bounds, &cursor_state.cursor); } else { self.addCursorRect(bounds, &NSCursor::invisible()); } } } unsafe impl NSTextInputClient for WinitView { #[method(hasMarkedText)] fn has_marked_text(&self) -> bool { trace_scope!("hasMarkedText"); self.state.marked_text.borrow().length() > 0 } #[method(markedRange)] fn marked_range(&self) -> NSRange { trace_scope!("markedRange"); let length = self.state.marked_text.borrow().length(); if length > 0 { NSRange::new(0, length) } else { util::EMPTY_RANGE } } #[method(selectedRange)] fn selected_range(&self) -> NSRange { trace_scope!("selectedRange"); util::EMPTY_RANGE } #[method(setMarkedText:selectedRange:replacementRange:)] fn set_marked_text( &self, string: &NSObject, _selected_range: NSRange, _replacement_range: NSRange, ) { trace_scope!("setMarkedText:selectedRange:replacementRange:"); // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let (marked_text, preedit_string) = if string.is_kind_of::() { let string: *const NSObject = string; let string: *const NSAttributedString = string.cast(); let string = unsafe { &*string }; ( NSMutableAttributedString::from_attributed_nsstring(string), string.string().to_string(), ) } else { let string: *const NSObject = string; let string: *const NSString = string.cast(); let string = unsafe { &*string }; ( NSMutableAttributedString::from_nsstring(string), string.to_string(), ) }; // Update marked text. *self.state.marked_text.borrow_mut() = marked_text; // Notify IME is active if application still doesn't know it. if self.state.ime_state.get() == ImeState::Disabled { *self.state.input_source.borrow_mut() = self.current_input_source(); self.queue_event(WindowEvent::Ime(Ime::Enabled)); } if self.hasMarkedText() { self.state.ime_state.set(ImeState::Preedit); } else { // In case the preedit was cleared, set IME into the Ground state. self.state.ime_state.set(ImeState::Ground); } // Empty string basically means that there's no preedit, so indicate that by sending // `None` cursor range. let cursor_range = if preedit_string.is_empty() { None } else { Some((preedit_string.len(), preedit_string.len())) }; // Send WindowEvent for updating marked text self.queue_event(WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range))); } #[method(unmarkText)] fn unmark_text(&self) { trace_scope!("unmarkText"); *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); let input_context = self.inputContext().expect("input context"); input_context.discardMarkedText(); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); if self.is_ime_enabled() { // Leave the Preedit self.state self.state.ime_state.set(ImeState::Ground); } else { warn!("Expected to have IME enabled when receiving unmarkText"); } } #[method_id(validAttributesForMarkedText)] fn valid_attributes_for_marked_text(&self) -> Id> { trace_scope!("validAttributesForMarkedText"); NSArray::new() } #[method_id(attributedSubstringForProposedRange:actualRange:)] fn attributed_substring_for_proposed_range( &self, _range: NSRange, _actual_range: *mut NSRange, ) -> Option> { trace_scope!("attributedSubstringForProposedRange:actualRange:"); None } #[method(characterIndexForPoint:)] fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { trace_scope!("characterIndexForPoint:"); 0 } #[method(firstRectForCharacterRange:actualRange:)] fn first_rect_for_character_range( &self, _range: NSRange, _actual_range: *mut NSRange, ) -> NSRect { trace_scope!("firstRectForCharacterRange:actualRange:"); let window = self.window(); let content_rect = window.contentRectForFrameRect(window.frame()); let base_x = content_rect.origin.x as f64; let base_y = (content_rect.origin.y + content_rect.size.height) as f64; let LogicalSize { width, height } = self.state.ime_size.get(); let x = base_x + self.state.ime_position.get().x; let y = base_y - self.state.ime_position.get().y - height; NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) } #[method(insertText:replacementRange:)] fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { trace_scope!("insertText:replacementRange:"); // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. let string = if string.is_kind_of::() { let string: *const NSObject = string; let string: *const NSAttributedString = string.cast(); unsafe { &*string }.string().to_string() } else { let string: *const NSObject = string; let string: *const NSString = string.cast(); unsafe { &*string }.to_string() }; let is_control = string.chars().next().map_or(false, |c| c.is_control()); // Commit only if we have marked text. if self.hasMarkedText() && self.is_ime_enabled() && !is_control { self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Commit(string))); self.state.ime_state.set(ImeState::Commited); } } // 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. #[method(doCommandBySelector:)] fn do_command_by_selector(&self, _command: Sel) { trace_scope!("doCommandBySelector:"); // We shouldn't forward any character from just commited text, since we'll end up sending // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, // which is not desired given it was used to confirm IME input. if self.state.ime_state.get() == ImeState::Commited { return; } self.state.forward_key_to_app.set(true); if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit { // Leave preedit so that we also report the key-up for this key. self.state.ime_state.set(ImeState::Ground); } } } unsafe impl WinitView { #[method(keyDown:)] fn key_down(&self, event: &NSEvent) { trace_scope!("keyDown:"); { let mut prev_input_source = self.state.input_source.borrow_mut(); let current_input_source = self.current_input_source(); if *prev_input_source != current_input_source && self.is_ime_enabled() { *prev_input_source = current_input_source; drop(prev_input_source); self.state.ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } // Get the characters from the event. let old_ime_state = self.state.ime_state.get(); self.state.forward_key_to_app.set(false); let event = replace_event(event, self.window().option_as_alt()); // The `interpretKeyEvents` function might call // `setMarkedText`, `insertText`, and `doCommandBySelector`. // It's important that we call this before queuing the KeyboardInput, because // we must send the `KeyboardInput` event during IME if it triggered // `doCommandBySelector`. (doCommandBySelector means that the keyboard input // is not handled by IME and should be handled by the application) if self.state.ime_allowed.get() { let events_for_nsview = NSArray::from_slice(&[&*event]); unsafe { self.interpretKeyEvents(&events_for_nsview) }; // If the text was commited we must treat the next keyboard event as IME related. if self.state.ime_state.get() == ImeState::Commited { // Remove any marked text, so normal input can continue. *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); } } self.update_modifiers(&event, false); let had_ime_input = match self.state.ime_state.get() { ImeState::Commited => { // Allow normal input after the commit. self.state.ime_state.set(ImeState::Ground); true } ImeState::Preedit => true, // `key_down` could result in preedit clear, so compare old and current state. _ => old_ime_state != self.state.ime_state.get(), }; if !had_ime_input || self.state.forward_key_to_app.get() { let key_event = create_key_event(&event, true, event.is_a_repeat(), None); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: key_event, is_synthetic: false, }); } } #[method(keyUp:)] fn key_up(&self, event: &NSEvent) { trace_scope!("keyUp:"); let event = replace_event(event, self.window().option_as_alt()); self.update_modifiers(&event, false); // We want to send keyboard input when we are currently in the ground state. if matches!( self.state.ime_state.get(), ImeState::Ground | ImeState::Disabled ) { self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: create_key_event(&event, false, false, None), is_synthetic: false, }); } } #[method(flagsChanged:)] fn flags_changed(&self, event: &NSEvent) { trace_scope!("flagsChanged:"); self.update_modifiers(event, true); } #[method(insertTab:)] fn insert_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertTab:"); let window = self.window(); if let Some(first_responder) = window.firstResponder() { if *first_responder == ***self { window.selectNextKeyView(Some(self)) } } } #[method(insertBackTab:)] fn insert_back_tab(&self, _sender: Option<&AnyObject>) { trace_scope!("insertBackTab:"); let window = self.window(); if let Some(first_responder) = window.firstResponder() { if *first_responder == ***self { window.selectPreviousKeyView(Some(self)) } } } // Allows us to receive Cmd-. (the shortcut for closing a dialog) // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 #[method(cancelOperation:)] fn cancel_operation(&self, _sender: Option<&AnyObject>) { trace_scope!("cancelOperation:"); let event = NSApp() .currentEvent() .expect("could not find current event"); self.update_modifiers(&event, false); let event = create_key_event(&event, true, event.is_a_repeat(), None); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } #[method(mouseDown:)] fn mouse_down(&self, event: &NSEvent) { trace_scope!("mouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(mouseUp:)] fn mouse_up(&self, event: &NSEvent) { trace_scope!("mouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } #[method(rightMouseDown:)] fn right_mouse_down(&self, event: &NSEvent) { trace_scope!("rightMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(rightMouseUp:)] fn right_mouse_up(&self, event: &NSEvent) { trace_scope!("rightMouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } #[method(otherMouseDown:)] fn other_mouse_down(&self, event: &NSEvent) { trace_scope!("otherMouseDown:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Pressed); } #[method(otherMouseUp:)] fn other_mouse_up(&self, event: &NSEvent) { trace_scope!("otherMouseUp:"); self.mouse_motion(event); self.mouse_click(event, ElementState::Released); } // No tracing on these because that would be overly verbose #[method(mouseMoved:)] fn mouse_moved(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(mouseDragged:)] fn mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(rightMouseDragged:)] fn right_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(otherMouseDragged:)] fn other_mouse_dragged(&self, event: &NSEvent) { self.mouse_motion(event); } #[method(mouseEntered:)] fn mouse_entered(&self, _event: &NSEvent) { trace_scope!("mouseEntered:"); self.queue_event(WindowEvent::CursorEntered { device_id: DEVICE_ID, }); } #[method(mouseExited:)] fn mouse_exited(&self, _event: &NSEvent) { trace_scope!("mouseExited:"); self.queue_event(WindowEvent::CursorLeft { device_id: DEVICE_ID, }); } #[method(scrollWheel:)] fn scroll_wheel(&self, event: &NSEvent) { trace_scope!("scrollWheel:"); self.mouse_motion(event); let delta = { let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); if event.hasPreciseScrollingDeltas() { let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor()); MouseScrollDelta::PixelDelta(delta) } else { MouseScrollDelta::LineDelta(x as f32, y as f32) } }; // The "momentum phase," if any, has higher priority than touch phase (the two should // be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum // phase is recorded (or rather, the started/ended cases of the momentum phase) then we // report the touch phase. let phase = match event.momentumPhase() { NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { TouchPhase::Started } NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { TouchPhase::Ended } _ => match event.phase() { NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => { TouchPhase::Started } NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => { TouchPhase::Ended } _ => TouchPhase::Moved, }, }; self.update_modifiers(event, false); self.queue_device_event(DeviceEvent::MouseWheel { delta }); self.queue_event(WindowEvent::MouseWheel { device_id: DEVICE_ID, delta, phase, }); } #[method(magnifyWithEvent:)] fn magnify_with_event(&self, event: &NSEvent) { trace_scope!("magnifyWithEvent:"); let phase = match event.phase() { NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, _ => return, }; self.queue_event(WindowEvent::TouchpadMagnify { device_id: DEVICE_ID, delta: event.magnification(), phase, }); } #[method(smartMagnifyWithEvent:)] fn smart_magnify_with_event(&self, _event: &NSEvent) { trace_scope!("smartMagnifyWithEvent:"); self.queue_event(WindowEvent::SmartMagnify { device_id: DEVICE_ID, }); } #[method(rotateWithEvent:)] fn rotate_with_event(&self, event: &NSEvent) { trace_scope!("rotateWithEvent:"); let phase = match event.phase() { NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved, NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled, NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, _ => return, }; self.queue_event(WindowEvent::TouchpadRotate { device_id: DEVICE_ID, delta: event.rotation(), phase, }); } #[method(pressureChangeWithEvent:)] fn pressure_change_with_event(&self, event: &NSEvent) { trace_scope!("pressureChangeWithEvent:"); self.mouse_motion(event); self.queue_event(WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: event.pressure(), stage: event.stage() as i64, }); } // 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 #[method(_wantsKeyDownForEvent:)] fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { trace_scope!("_wantsKeyDownForEvent:"); true } #[method(acceptsFirstMouse:)] fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { trace_scope!("acceptsFirstMouse:"); self.state.accepts_first_mouse } } ); impl WinitView { pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id { unsafe { msg_send_id![ Self::alloc(), initWithId: window, acceptsFirstMouse: accepts_first_mouse, ] } } fn window(&self) -> Id { // TODO: Simply use `window` property on `NSView`. // That only returns a window _after_ the view has been attached though! // (which is incompatible with `frameDidChange:`) // // unsafe { msg_send_id![self, window] } self._ns_window.load().expect("view to have a window") } fn window_id(&self) -> WindowId { WindowId(self.window().id()) } fn queue_event(&self, event: WindowEvent) { let event = Event::WindowEvent { window_id: self.window_id(), event, }; AppState::queue_event(event); } fn queue_device_event(&self, event: DeviceEvent) { let event = Event::DeviceEvent { device_id: DEVICE_ID, event, }; AppState::queue_event(event); } fn scale_factor(&self) -> f64 { self.window().backingScaleFactor() as f64 } fn is_ime_enabled(&self) -> bool { !matches!(self.state.ime_state.get(), ImeState::Disabled) } fn current_input_source(&self) -> String { self.inputContext() .expect("input context") .selectedKeyboardInputSource() .map(|input_source| input_source.to_string()) .unwrap_or_default() } pub(super) fn set_cursor_icon(&self, icon: Id) { let mut cursor_state = self.state.cursor_state.borrow_mut(); cursor_state.cursor = icon; } /// Set whether the cursor should be visible or not. /// /// Returns whether the state changed. pub(super) fn set_cursor_visible(&self, visible: bool) -> bool { let mut cursor_state = self.state.cursor_state.borrow_mut(); if visible != cursor_state.visible { cursor_state.visible = visible; true } else { false } } pub(super) fn set_ime_allowed(&self, ime_allowed: bool) { if self.state.ime_allowed.get() == ime_allowed { return; } self.state.ime_allowed.set(ime_allowed); if self.state.ime_allowed.get() { return; } // Clear markedText *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new(); if self.state.ime_state.get() != ImeState::Disabled { self.state.ime_state.set(ImeState::Disabled); self.queue_event(WindowEvent::Ime(Ime::Disabled)); } } pub(super) fn set_ime_cursor_area( &self, position: LogicalPosition, size: LogicalSize, ) { self.state.ime_position.set(position); self.state.ime_size.set(size); let input_context = self.inputContext().expect("input context"); input_context.invalidateCharacterCoordinates(); } /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary. pub(super) fn reset_modifiers(&self) { if !self.state.modifiers.get().state().is_empty() { self.state.modifiers.set(Modifiers::default()); self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get())); } } /// Update modifiers if `event` has something different fn update_modifiers(&self, ns_event: &NSEvent, is_flags_changed_event: bool) { use ElementState::{Pressed, Released}; let current_modifiers = event_mods(ns_event); let prev_modifiers = self.state.modifiers.get(); self.state.modifiers.set(current_modifiers); // This function was called form the flagsChanged event, which is triggered // when the user presses/releases a modifier even if the same kind of modifier // has already been pressed. // // When flags changed event has key code of zero it means that event doesn't carry any key // event, thus we can't generate regular presses based on that. The `ModifiersChanged` // later will work though, since the flags are attached to the event and contain valid // information. 'send_event: { if is_flags_changed_event && ns_event.key_code() != 0 { let scancode = ns_event.key_code(); let physical_key = PhysicalKey::from_scancode(scancode as u32); // We'll correct the `is_press` later. let mut event = create_key_event(ns_event, false, false, Some(physical_key)); let key = code_to_key(physical_key, scancode); // Ignore processing of unkown modifiers because we can't determine whether // it was pressed or release reliably. let Some(event_modifier) = key_to_modifier(&key) else { break 'send_event; }; event.physical_key = physical_key; event.logical_key = key.clone(); event.location = code_to_location(physical_key); let location_mask = ModLocationMask::from_location(event.location); let mut phys_mod_state = self.state.phys_modifiers.borrow_mut(); let phys_mod = phys_mod_state .entry(key) .or_insert(ModLocationMask::empty()); let is_active = current_modifiers.state().contains(event_modifier); let mut events = VecDeque::with_capacity(2); // There is no API for getting whether the button was pressed or released // during this event. For this reason we have to do a bit of magic below // to come up with a good guess whether this key was pressed or released. // (This is not trivial because there are multiple buttons that may affect // the same modifier) if !is_active { event.state = Released; if phys_mod.contains(ModLocationMask::LEFT) { let mut event = event.clone(); event.location = KeyLocation::Left; event.physical_key = get_left_modifier_code(&event.logical_key).into(); events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } if phys_mod.contains(ModLocationMask::RIGHT) { event.location = KeyLocation::Right; event.physical_key = get_right_modifier_code(&event.logical_key).into(); events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } *phys_mod = ModLocationMask::empty(); } else { if *phys_mod == location_mask { // Here we hit a contradiction: // The modifier state was "changed" to active, // yet the only pressed modifier key was the one that we // just got a change event for. // This seemingly means that the only pressed modifier is now released, // but at the same time the modifier became active. // // But this scenario is possible if we released modifiers // while the application was not in focus. (Because we don't // get informed of modifier key events while the application // is not focused) // In this case we prioritize the information // about the current modifier state which means // that the button was pressed. event.state = Pressed; } else { phys_mod.toggle(location_mask); let is_pressed = phys_mod.contains(location_mask); event.state = if is_pressed { Pressed } else { Released }; } events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } drop(phys_mod_state); for event in events { self.queue_event(event); } } } if prev_modifiers == current_modifiers { return; } self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get())); } fn mouse_click(&self, event: &NSEvent, button_state: ElementState) { let button = mouse_button(event); self.update_modifiers(event, false); self.queue_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: button_state, button, }); } fn mouse_motion(&self, event: &NSEvent) { let window_point = event.locationInWindow(); let view_point = self.convertPoint_fromView(window_point, None); let view_rect = self.frame(); 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 = 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); self.update_modifiers(event, false); self.queue_event(WindowEvent::CursorMoved { device_id: DEVICE_ID, position: logical_position.to_physical(self.scale_factor()), }); } } /// Get the mouse button from the NSEvent. fn mouse_button(event: &NSEvent) -> MouseButton { // The buttonNumber property only makes sense for the mouse events: // NSLeftMouse.../NSRightMouse.../NSOtherMouse... // For the other events, it's always set to 0. // MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons, // but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications. match event.buttonNumber() { 0 => MouseButton::Left, 1 => MouseButton::Right, 2 => MouseButton::Middle, 3 => MouseButton::Back, 4 => MouseButton::Forward, n => MouseButton::Other(n as u16), } } // NOTE: to get option as alt working we need to rewrite events // we're getting from the operating system, which makes it // impossible to provide such events as extra in `KeyEvent`. fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id { let ev_mods = event_mods(event).state; let ignore_alt_characters = match option_as_alt { OptionAsAlt::OnlyLeft if event.lalt_pressed() => true, OptionAsAlt::OnlyRight if event.ralt_pressed() => true, OptionAsAlt::Both if ev_mods.alt_key() => true, _ => false, } && !ev_mods.control_key() && !ev_mods.super_key(); if ignore_alt_characters { let ns_chars = event .charactersIgnoringModifiers() .expect("expected characters to be non-null"); NSEvent::keyEventWithType( event.type_(), event.locationInWindow(), event.modifierFlags(), event.timestamp(), event.window_number(), None, &ns_chars, &ns_chars, event.is_a_repeat(), event.key_code(), ) } else { event.copy() } } winit-0.29.15/src/platform_impl/macos/window.rs000064400000000000000000001647151046102023000175650ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::collections::VecDeque; use std::f64; use std::ops; use std::os::raw::c_void; use std::ptr::NonNull; use std::sync::{Mutex, MutexGuard}; use crate::{ dpi::{ LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, }, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::WindowEvent, icon::Icon, platform::macos::{OptionAsAlt, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, appkit::NSWindowOrderingMode, event_loop::EventLoopWindowTarget, ffi, monitor::{self, MonitorHandle, VideoMode}, util, view::WinitView, window_delegate::WinitWindowDelegate, Fullscreen, OsError, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; use core_graphics::display::{CGDisplay, CGPoint}; use icrate::Foundation::{ CGFloat, MainThreadBound, MainThreadMarker, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, }; use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; use super::appkit::{ NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen, NSView, NSWindow, NSWindowButton, NSWindowLevel, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, }; use super::ffi::CGSMainConnectionID; use super::ffi::CGSSetWindowBackgroundBlurRadius; pub(crate) struct Window { window: MainThreadBound>, // We keep this around so that it doesn't get dropped until the window does. _delegate: MainThreadBound>, } impl Drop for Window { fn drop(&mut self) { self.window .get_on_main(|window, _| autoreleasepool(|_| window.close())) } } impl Window { pub(crate) fn new( _window_target: &EventLoopWindowTarget, attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let mtm = MainThreadMarker::new() .expect("windows can only be created on the main thread on macOS"); let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?; Ok(Window { window: MainThreadBound::new(window, mtm), _delegate: MainThreadBound::new(_delegate, mtm), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WinitWindow) + Send + 'static) { // For now, don't actually do queuing, since it may be less predictable self.maybe_wait_on_main(f) } pub(crate) fn maybe_wait_on_main( &self, f: impl FnOnce(&WinitWindow) -> R + Send, ) -> R { self.window.get_on_main(|window, _mtm| f(window)) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub usize); impl WindowId { pub const unsafe fn dummy() -> Self { Self(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as usize) } } #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { 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 disallow_hidpi: bool, pub has_shadow: bool, pub accepts_first_mouse: bool, pub tabbing_identifier: Option, pub option_as_alt: OptionAsAlt, } impl Default for PlatformSpecificWindowBuilderAttributes { #[inline] fn default() -> Self { Self { movable_by_window_background: false, titlebar_transparent: false, title_hidden: false, titlebar_hidden: false, titlebar_buttons_hidden: false, fullsize_content_view: false, disallow_hidpi: false, has_shadow: true, accepts_first_mouse: true, tabbing_identifier: None, option_as_alt: Default::default(), } } } declare_class!( #[derive(Debug)] pub struct WinitWindow { // TODO: Fix unnecessary boxing here shared_state: IvarDrop>, "_shared_state">, } mod ivars; unsafe impl ClassType for WinitWindow { #[inherits(NSResponder, NSObject)] type Super = NSWindow; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitWindow"; } unsafe impl WinitWindow { #[method(initWithContentRect:styleMask:state:)] unsafe fn init( this: *mut Self, frame: NSRect, mask: NSWindowStyleMask, state: *mut c_void, ) -> Option> { let this: Option<&mut Self> = unsafe { msg_send![ super(this), initWithContentRect: frame, styleMask: mask, backing: NSBackingStoreType::NSBackingStoreBuffered, defer: false, ] }; this.map(|this| { // SAFETY: The pointer originally came from `Box::into_raw`. Ivar::write(&mut this.shared_state, unsafe { Box::from_raw(state as *mut Mutex) }); // It is imperative to correct memory management that we // disable the extra release that would otherwise happen when // calling `clone` on the window. this.setReleasedWhenClosed(false); NonNull::from(this) }) } } unsafe impl WinitWindow { #[method(canBecomeMainWindow)] fn can_become_main_window(&self) -> bool { trace_scope!("canBecomeMainWindow"); true } #[method(canBecomeKeyWindow)] fn can_become_key_window(&self) -> bool { trace_scope!("canBecomeKeyWindow"); true } } ); #[derive(Debug, Default)] pub struct SharedState { pub resizable: bool, /// This field tracks the current fullscreen state of the window /// (as seen by `WindowDelegate`). pub(crate) 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(crate) target_fullscreen: Option>, pub maximized: bool, pub standard_frame: Option, pub(crate) 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 current_theme: Option, /// The current resize incerments for the window content. pub(crate) resize_increments: NSSize, /// The state of the `Option` as `Alt`. pub(crate) option_as_alt: OptionAsAlt, decorations: bool, } 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))) } } pub(crate) struct SharedStateMutexGuard<'a> { guard: MutexGuard<'a, SharedState>, called_from_fn: &'static str, } impl<'a> SharedStateMutexGuard<'a> { #[inline] fn new(guard: MutexGuard<'a, SharedState>, called_from_fn: &'static str) -> Self { trace!("Locked shared state in `{}`", called_from_fn); Self { guard, called_from_fn, } } } impl ops::Deref for SharedStateMutexGuard<'_> { type Target = SharedState; #[inline] fn deref(&self) -> &Self::Target { self.guard.deref() } } impl ops::DerefMut for SharedStateMutexGuard<'_> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.guard.deref_mut() } } impl Drop for SharedStateMutexGuard<'_> { #[inline] fn drop(&mut self) { trace!("Unlocked shared state in `{}`", self.called_from_fn); } } impl WinitWindow { #[allow(clippy::type_complexity)] fn new( attrs: WindowAttributes, pl_attrs: PlatformSpecificWindowBuilderAttributes, ) -> Result<(Id, Id), RootOsError> { trace_scope!("WinitWindow::new"); let this = autoreleasepool(|_| { let screen = match attrs.fullscreen.0.clone().map(Into::into) { Some(Fullscreen::Borderless(Some(monitor))) | Some(Fullscreen::Exclusive(VideoMode { monitor, .. })) => { monitor.ns_screen().or_else(NSScreen::main) } Some(Fullscreen::Borderless(None)) => NSScreen::main(), None => None, }; let frame = match &screen { Some(screen) => screen.frame(), None => { let scale_factor = NSScreen::main() .map(|screen| screen.backingScaleFactor() as f64) .unwrap_or(1.0); let (width, height) = match attrs.inner_size { Some(size) => { let logical = size.to_logical(scale_factor); (logical.width, logical.height) } None => (800.0, 600.0), }; 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 without a titlebar or borders // if decorations is set to false, ignore pl_attrs // // if the titlebar is hidden, ignore other pl_attrs NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask } else { // default case, resizable window with titlebar and titlebar buttons NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask | NSWindowStyleMask::NSResizableWindowMask | NSWindowStyleMask::NSTitledWindowMask }; if !attrs.resizable { masks &= !NSWindowStyleMask::NSResizableWindowMask; } if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) { masks &= !NSWindowStyleMask::NSMiniaturizableWindowMask; } if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) { masks &= !NSWindowStyleMask::NSClosableWindowMask; } if pl_attrs.fullsize_content_view { masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; } let state = SharedState { resizable: attrs.resizable, maximized: attrs.maximized, decorations: attrs.decorations, ..Default::default() }; // Pass the state through FFI to the method declared on the class let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast(); let this: Option> = unsafe { msg_send_id![ WinitWindow::alloc(), initWithContentRect: frame, styleMask: masks, state: state_ptr, ] }; let this = this?; let resize_increments = match attrs .resize_increments .map(|i| i.to_logical::(this.scale_factor())) { Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => { NSSize::new(width, height) } _ => NSSize::new(1., 1.), }; this.lock_shared_state("init").resize_increments = resize_increments; this.setTitle(&NSString::from_str(&attrs.title)); this.setAcceptsMouseMovedEvents(true); if let Some(identifier) = pl_attrs.tabbing_identifier { this.setTabbingIdentifier(&NSString::from_str(&identifier)); this.setTabbingMode(NSWindowTabbingMode::NSWindowTabbingModePreferred); } if attrs.content_protected { this.setSharingType(NSWindowSharingType::NSWindowSharingNone); } if pl_attrs.titlebar_transparent { this.setTitlebarAppearsTransparent(true); } if pl_attrs.title_hidden { this.setTitleVisibility(NSWindowTitleVisibility::Hidden); } if pl_attrs.titlebar_buttons_hidden { for titlebar_button in &[ #[allow(deprecated)] NSWindowButton::FullScreen, NSWindowButton::Miniaturize, NSWindowButton::Close, NSWindowButton::Zoom, ] { if let Some(button) = this.standardWindowButton(*titlebar_button) { button.setHidden(true); } } } if pl_attrs.movable_by_window_background { this.setMovableByWindowBackground(true); } if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) { button.setEnabled(false); } } if !pl_attrs.has_shadow { this.setHasShadow(false); } if attrs.position.is_none() { this.center(); } this.set_option_as_alt(pl_attrs.option_as_alt); Some(this) }) .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; #[cfg(feature = "rwh_06")] match attrs.parent_window.0 { Some(rwh_06::RawWindowHandle::AppKit(handle)) => { // SAFETY: Caller ensures the pointer is valid or NULL // Unwrap is fine, since the pointer comes from `NonNull`. let parent_view: Id = unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap(); let parent = parent_view.window().ok_or_else(|| { os_error!(OsError::CreationError( "parent view should be installed in a window" )) })?; // SAFETY: We know that there are no parent -> child -> parent cycles since the only place in `winit` // where we allow making a window a child window is right here, just after it's been created. unsafe { parent.addChildWindow(&this, NSWindowOrderingMode::NSWindowAbove) }; } Some(raw) => panic!("Invalid raw window handle {raw:?} on macOS"), None => (), } let view = WinitView::new(&this, pl_attrs.accepts_first_mouse); // The default value of `setWantsBestResolutionOpenGLSurface:` was `false` until // macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid // always the default system value in favour of the user's code view.setWantsBestResolutionOpenGLSurface(!pl_attrs.disallow_hidpi); // 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 NSAppKitVersion::current().floor() > NSAppKitVersion::NSAppKitVersionNumber10_12 { view.setWantsLayer(true); } // Configure the new view as the "key view" for the window this.setContentView(&view); this.setInitialFirstResponder(&view); if attrs.transparent { this.setOpaque(false); this.setBackgroundColor(&NSColor::clear()); } if attrs.blur { this.set_blur(attrs.blur); } if let Some(dim) = attrs.min_inner_size { this.set_min_inner_size(Some(dim)); } if let Some(dim) = attrs.max_inner_size { this.set_max_inner_size(Some(dim)); } this.set_window_level(attrs.window_level); // register for drag and drop operations. this.registerForDraggedTypes(&NSArray::from_id_slice(&[ unsafe { NSFilenamesPboardType }.copy() ])); match attrs.preferred_theme { Some(theme) => { set_ns_theme(Some(theme)); let mut state = this.lock_shared_state("WinitWindow::new"); state.current_theme = Some(theme); } None => { let mut state = this.lock_shared_state("WinitWindow::new"); state.current_theme = Some(get_ns_theme()); } } let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.0.is_some()); // XXX Send `Focused(false)` right after creating the window delegate, so we won't // obscure the real focused events on the startup. delegate.queue_event(WindowEvent::Focused(false)); // Set fullscreen mode after we setup everything this.set_fullscreen(attrs.fullscreen.0.map(Into::into)); // 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 attrs.visible { if attrs.active { // Tightly linked with `app_state::window_activation_hack` this.makeKeyAndOrderFront(None); } else { this.orderFront(None); } } if attrs.maximized { this.set_maximized(attrs.maximized); } Ok((this, delegate)) } pub(super) fn view(&self) -> Id { // SAFETY: The view inside WinitWindow is always `WinitView` unsafe { Id::cast(self.contentView()) } } #[track_caller] pub(crate) fn lock_shared_state( &self, called_from_fn: &'static str, ) -> SharedStateMutexGuard<'_> { SharedStateMutexGuard::new(self.shared_state.lock().unwrap(), called_from_fn) } fn set_style_mask(&self, mask: NSWindowStyleMask) { self.setStyleMask(mask); // If we don't do this, key handling will break // (at least until the window is clicked again/etc.) let _ = self.makeFirstResponder(Some(&self.contentView())); } } impl WinitWindow { pub fn id(&self) -> WindowId { WindowId(self as *const Self as usize) } pub fn set_title(&self, title: &str) { self.setTitle(&NSString::from_str(title)) } pub fn set_transparent(&self, transparent: bool) { self.setOpaque(!transparent) } pub fn set_blur(&self, blur: bool) { // NOTE: in general we want to specify the blur radius, but the choice of 80 // should be a reasonable default. let radius = if blur { 80 } else { 0 }; let window_number = self.windowNumber(); unsafe { CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, radius); } } pub fn set_visible(&self, visible: bool) { match visible { true => self.makeKeyAndOrderFront(None), false => self.orderOut(None), } } #[inline] pub fn is_visible(&self) -> Option { Some(self.isVisible()) } pub fn request_redraw(&self) { AppState::queue_redraw(RootWindowId(self.id())); } #[inline] pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { let frame_rect = self.frame(); 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 = self.contentRectForFrameRect(self.frame()); 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); self.setFrameTopLeftPoint(util::window_position(position)); } #[inline] pub fn inner_size(&self) -> PhysicalSize { let frame = self.contentView().frame(); let logical: LogicalSize = (frame.size.width as f64, 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 frame = self.frame(); let logical: LogicalSize = (frame.size.width as f64, frame.size.height as f64).into(); let scale_factor = self.scale_factor(); logical.to_physical(scale_factor) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let size: LogicalSize = size.to_logical(scale_factor); self.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); None } pub fn set_min_inner_size(&self, dimensions: Option) { let dimensions = dimensions.unwrap_or(Logical(LogicalSize { width: 0.0, height: 0.0, })); let min_size = dimensions.to_logical::(self.scale_factor()); let mut current_rect = self.frame(); let content_rect = self.contentRectForFrameRect(current_rect); // Convert from client area size to window size let min_size = NSSize::new( min_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 min_size.height + (current_rect.size.height - content_rect.size.height), ); self.setMinSize(min_size); // If necessary, resize the window to match constraint if current_rect.size.width < min_size.width { current_rect.size.width = min_size.width; self.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; self.setFrame_display(current_rect, false) } } pub fn set_max_inner_size(&self, dimensions: Option) { let dimensions = dimensions.unwrap_or(Logical(LogicalSize { width: std::f32::MAX as f64, height: std::f32::MAX as f64, })); let scale_factor = self.scale_factor(); let max_size = dimensions.to_logical::(scale_factor); let mut current_rect = self.frame(); let content_rect = self.contentRectForFrameRect(current_rect); // Convert from client area size to window size let max_size = NSSize::new( max_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 max_size.height + (current_rect.size.height - content_rect.size.height), ); self.setMaxSize(max_size); // If necessary, resize the window to match constraint if current_rect.size.width > max_size.width { current_rect.size.width = max_size.width; self.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; self.setFrame_display(current_rect, false) } } pub fn resize_increments(&self) -> Option> { let increments = self .lock_shared_state("set_resize_increments") .resize_increments; let (w, h) = (increments.width, increments.height); if w > 1.0 || h > 1.0 { Some(LogicalSize::new(w, h).to_physical(self.scale_factor())) } else { None } } pub fn set_resize_increments(&self, increments: Option) { // XXX the resize increments are only used during live resizes. let mut shared_state_lock = self.lock_shared_state("set_resize_increments"); shared_state_lock.resize_increments = increments .map(|increments| { let logical = increments.to_logical::(self.scale_factor()); NSSize::new(logical.width.max(1.0), logical.height.max(1.0)) }) .unwrap_or_else(|| NSSize::new(1.0, 1.0)); } pub(crate) fn set_resize_increments_inner(&self, size: NSSize) { // It was concluded (#2411) that there is never a use-case for // "outer" resize increments, hence we set "inner" ones here. // ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements) // This is consistent with X11 size hints behavior self.setContentResizeIncrements(size); } #[inline] pub fn set_resizable(&self, resizable: bool) { let fullscreen = { let mut shared_state_lock = self.lock_shared_state("set_resizable"); shared_state_lock.resizable = resizable; shared_state_lock.fullscreen.is_some() }; if !fullscreen { let mut mask = self.styleMask(); if resizable { mask |= NSWindowStyleMask::NSResizableWindowMask; } else { mask &= !NSWindowStyleMask::NSResizableWindowMask; } self.set_style_mask(mask); } // Otherwise, we don't change the mask until we exit fullscreen. } #[inline] pub fn is_resizable(&self) -> bool { self.isResizable() } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { let mut mask = self.styleMask(); if buttons.contains(WindowButtons::CLOSE) { mask |= NSWindowStyleMask::NSClosableWindowMask; } else { mask &= !NSWindowStyleMask::NSClosableWindowMask; } if buttons.contains(WindowButtons::MINIMIZE) { mask |= NSWindowStyleMask::NSMiniaturizableWindowMask; } else { mask &= !NSWindowStyleMask::NSMiniaturizableWindowMask; } // This must happen before the button's "enabled" status has been set, // hence we do it synchronously. self.set_style_mask(mask); // We edit the button directly instead of using `NSResizableWindowMask`, // since that mask also affect the resizability of the window (which is // controllable by other means in `winit`). if let Some(button) = self.standardWindowButton(NSWindowButton::Zoom) { button.setEnabled(buttons.contains(WindowButtons::MAXIMIZE)); } } #[inline] pub fn enabled_buttons(&self) -> WindowButtons { let mut buttons = WindowButtons::empty(); if self.isMiniaturizable() { buttons |= WindowButtons::MINIMIZE; } if self .standardWindowButton(NSWindowButton::Zoom) .map(|b| b.isEnabled()) .unwrap_or(true) { buttons |= WindowButtons::MAXIMIZE; } if self.hasCloseBox() { buttons |= WindowButtons::CLOSE; } buttons } pub fn set_cursor_icon(&self, icon: CursorIcon) { let view = self.view(); view.set_cursor_icon(NSCursor::from_icon(icon)); self.invalidateCursorRectsForView(&view); } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let associate_mouse_cursor = match mode { CursorGrabMode::Locked => false, CursorGrabMode::None => true, CursorGrabMode::Confined => { return Err(ExternalError::NotSupported(NotSupportedError::new())) } }; // TODO: Do this for real https://stackoverflow.com/a/40922095/5435443 CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor) .map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status)))) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { let view = self.view(); let state_changed = view.set_cursor_visible(visible); if state_changed { self.invalidateCursorRectsForView(&view); } } #[inline] pub fn scale_factor(&self) -> f64 { self.backingScaleFactor() as f64 } #[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 drag_window(&self) -> Result<(), ExternalError> { let event = NSApp().currentEvent(); self.performWindowDragWithEvent(event.as_deref()); Ok(()) } #[inline] pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.setIgnoresMouseEvents(!hittest); Ok(()) } pub(crate) fn is_zoomed(&self) -> bool { // because `isZoomed` doesn't work if the window's borderless, // we make it resizable temporalily. let curr_mask = self.styleMask(); let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; let needs_temp_mask = !curr_mask.contains(required); if needs_temp_mask { self.set_style_mask(required); } let is_zoomed = self.isZoomed(); // Roll back temp styles if needs_temp_mask { self.set_style_mask(curr_mask); } is_zoomed } fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { let base_mask = shared_state .saved_style .take() .unwrap_or_else(|| self.styleMask()); if shared_state.resizable { base_mask | NSWindowStyleMask::NSResizableWindowMask } else { base_mask & !NSWindowStyleMask::NSResizableWindowMask } } /// 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) { let mut shared_state_lock = self.lock_shared_state("restore_state_from_fullscreen"); shared_state_lock.fullscreen = None; let maximized = shared_state_lock.maximized; let mask = self.saved_style(&mut shared_state_lock); drop(shared_state_lock); self.set_style_mask(mask); self.set_maximized(maximized); } #[inline] pub fn set_minimized(&self, minimized: bool) { let is_minimized = self.isMiniaturized(); if is_minimized == minimized { return; } if minimized { self.miniaturize(Some(self)); } else { self.deminiaturize(Some(self)); } } #[inline] pub fn is_minimized(&self) -> Option { Some(self.isMiniaturized()) } #[inline] pub fn set_maximized(&self, maximized: bool) { let is_zoomed = self.is_zoomed(); if is_zoomed == maximized { return; }; let mut shared_state = self.lock_shared_state("set_maximized"); // Save the standard frame sized if it is not zoomed if !is_zoomed { shared_state.standard_frame = Some(self.frame()); } shared_state.maximized = maximized; if shared_state.fullscreen.is_some() { // Handle it in window_did_exit_fullscreen return; } if self .styleMask() .contains(NSWindowStyleMask::NSResizableWindowMask) { drop(shared_state); // Just use the native zoom if resizable self.zoom(None); } else { // if it's not resizable, we set the frame directly let new_rect = if maximized { let screen = NSScreen::main().expect("no screen found"); screen.visibleFrame() } else { shared_state.saved_standard_frame() }; drop(shared_state); self.setFrame_display(new_rect, false); } } #[inline] pub(crate) fn fullscreen(&self) -> Option { let shared_state_lock = self.lock_shared_state("fullscreen"); shared_state_lock.fullscreen.clone() } #[inline] pub fn is_maximized(&self) -> bool { self.is_zoomed() } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { let mut shared_state_lock = self.lock_shared_state("set_fullscreen"); if shared_state_lock.is_simple_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); return; } let old_fullscreen = shared_state_lock.fullscreen.clone(); if fullscreen == old_fullscreen { return; } 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(monitor)) => monitor.clone(), Fullscreen::Borderless(None) => { if let Some(monitor) = self.current_monitor_inner() { monitor } else { return; } } Fullscreen::Exclusive(video_mode) => video_mode.monitor(), } .ns_screen() .unwrap(); let old_screen = self.screen().unwrap(); if old_screen != new_screen { let mut screen_frame = 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; self.setFrameTopLeftPoint(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().native_identifier(); let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; if matches!(old_fullscreen, Some(Fullscreen::Borderless(_))) { let app = NSApp(); let mut shared_state_lock = self.lock_shared_state("set_fullscreen"); 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.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); } } } self.lock_shared_state("set_fullscreen").fullscreen = fullscreen.clone(); fn toggle_fullscreen(window: &WinitWindow) { // Window level must be restored from `CGShieldingWindowLevel() // + 1` back to normal in order for `toggleFullScreen` to do // anything window.setLevel(NSWindowLevel::Normal); window.toggleFullScreen(None); } match (old_fullscreen, fullscreen) { (None, Some(_)) => { // `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`. let curr_mask = self.styleMask(); let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; if !curr_mask.contains(required) { self.set_style_mask(required); self.lock_shared_state("set_fullscreen").saved_style = Some(curr_mask); } toggle_fullscreen(self); } (Some(Fullscreen::Borderless(_)), None) => { // State is restored by `window_did_exit_fullscreen` toggle_fullscreen(self); } (Some(Fullscreen::Exclusive(ref video_mode)), None) => { unsafe { ffi::CGRestorePermanentDisplayConfiguration(); assert_eq!( ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), ffi::kCGErrorSuccess ); }; toggle_fullscreen(self); } (Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => { // 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(); self.lock_shared_state("set_fullscreen") .save_presentation_opts = Some(app.presentationOptions()); let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; app.setPresentationOptions(presentation_options); let window_level = NSWindowLevel(unsafe { ffi::CGShieldingWindowLevel() } as NSInteger + 1); self.setLevel(window_level); } (Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => { let presentation_options = self .lock_shared_state("set_fullscreen") .save_presentation_opts .unwrap_or_else(|| { NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar }); NSApp().setPresentationOptions(presentation_options); unsafe { ffi::CGRestorePermanentDisplayConfiguration(); assert_eq!( ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), ffi::kCGErrorSuccess ); }; // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. self.setLevel(NSWindowLevel::Normal); } _ => {} }; } #[inline] pub fn set_decorations(&self, decorations: bool) { let mut shared_state_lock = self.lock_shared_state("set_decorations"); if decorations == shared_state_lock.decorations { return; } shared_state_lock.decorations = decorations; let fullscreen = shared_state_lock.fullscreen.is_some(); let resizable = shared_state_lock.resizable; drop(shared_state_lock); // 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::NSClosableWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask | NSWindowStyleMask::NSResizableWindowMask | NSWindowStyleMask::NSTitledWindowMask } else { NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask }; if !resizable { new_mask &= !NSWindowStyleMask::NSResizableWindowMask; } new_mask }; self.set_style_mask(new_mask); } #[inline] pub fn is_decorated(&self) -> bool { self.lock_shared_state("is_decorated").decorations } #[inline] pub fn set_window_level(&self, level: WindowLevel) { let level = match level { WindowLevel::AlwaysOnTop => NSWindowLevel::Floating, WindowLevel::AlwaysOnBottom => NSWindowLevel::BELOW_NORMAL, WindowLevel::Normal => NSWindowLevel::Normal, }; self.setLevel(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_cursor_area(&self, spot: Position, size: Size) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); let size = size.to_logical(scale_factor); self.view().set_ime_cursor_area(logical_spot, size); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { self.view().set_ime_allowed(allowed); } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) { let is_minimized = self.isMiniaturized(); let is_visible = self.isVisible(); if !is_minimized && is_visible { NSApp().activateIgnoringOtherApps(true); self.makeKeyAndOrderFront(None); } } #[inline] pub fn request_user_attention(&self, request_type: Option) { let ns_request_type = request_type.map(|ty| match ty { UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, }); if let Some(ty) = ns_request_type { NSApp().requestUserAttention(ty); } } #[inline] // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> Option { let display_id = self.screen()?.display_id(); Some(MonitorHandle::new(display_id)) } #[inline] pub fn current_monitor(&self) -> Option { self.current_monitor_inner() } #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() } #[inline] pub fn primary_monitor(&self) -> Option { let monitor = monitor::primary_monitor(); Some(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 as *const Self as *mut _; window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _; 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 as *const Self as *mut _; window_handle.ns_view = Id::as_ptr(&self.contentView()) as *mut _; 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 = Id::as_ptr(&self.contentView()) as *mut _; 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(), )) } fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) { let current_style_mask = self.styleMask(); if on { self.set_style_mask(current_style_mask | mask); } else { self.set_style_mask(current_style_mask & (!mask)); } } #[inline] pub fn theme(&self) -> Option { self.lock_shared_state("theme").current_theme } #[inline] pub fn has_focus(&self) -> bool { self.isKeyWindow() } pub fn set_theme(&self, theme: Option) { set_ns_theme(theme); self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme())); } #[inline] pub fn set_content_protected(&self, protected: bool) { self.setSharingType(if protected { NSWindowSharingType::NSWindowSharingNone } else { NSWindowSharingType::NSWindowSharingReadOnly }) } pub fn title(&self) -> String { self.title_().to_string() } pub fn reset_dead_keys(&self) { // (Artur) I couldn't find a way to implement this. } } impl WindowExtMacOS for WinitWindow { #[inline] fn simple_fullscreen(&self) -> bool { self.lock_shared_state("simple_fullscreen") .is_simple_fullscreen } #[inline] fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { let mut shared_state_lock = self.lock_shared_state("set_simple_fullscreen"); let app = NSApp(); 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(self.contentRectForFrameRect(self.frame())); shared_state_lock.saved_style = Some(self.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; // Drop shared state lock before calling app.setPresentationOptions, because // it will call our windowDidChangeScreen listener which reacquires the lock drop(shared_state_lock); // Simulate pre-Lion fullscreen by hiding the dock and menu bar let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; app.setPresentationOptions(presentation_options); // Hide the titlebar self.toggle_style_mask(NSWindowStyleMask::NSTitledWindowMask, false); // Set the window frame to the screen frame size let screen = self.screen().expect("expected screen to be available"); self.setFrame_display(screen.frame(), true); // Fullscreen windows can't be resized, minimized, or moved self.toggle_style_mask(NSWindowStyleMask::NSMiniaturizableWindowMask, false); self.toggle_style_mask(NSWindowStyleMask::NSResizableWindowMask, false); self.setMovable(false); true } else { let new_mask = self.saved_style(&mut shared_state_lock); self.set_style_mask(new_mask); shared_state_lock.is_simple_fullscreen = false; let save_presentation_opts = shared_state_lock.save_presentation_opts; let frame = shared_state_lock.saved_standard_frame(); // Drop shared state lock before calling app.setPresentationOptions, because // it will call our windowDidChangeScreen listener which reacquires the lock drop(shared_state_lock); if let Some(presentation_opts) = save_presentation_opts { app.setPresentationOptions(presentation_opts); } self.setFrame_display(frame, true); self.setMovable(true); true } } #[inline] fn has_shadow(&self) -> bool { self.hasShadow() } #[inline] fn set_has_shadow(&self, has_shadow: bool) { self.setHasShadow(has_shadow) } #[inline] fn set_tabbing_identifier(&self, identifier: &str) { self.setTabbingIdentifier(&NSString::from_str(identifier)) } #[inline] fn tabbing_identifier(&self) -> String { self.tabbingIdentifier().to_string() } #[inline] fn select_next_tab(&self) { if let Some(group) = self.tabGroup() { group.selectNextTab(); } } #[inline] fn select_previous_tab(&self) { if let Some(group) = self.tabGroup() { group.selectPreviousTab() } } #[inline] fn select_tab_at_index(&self, index: usize) { if let Some(group) = self.tabGroup() { if let Some(windows) = group.tabbedWindows() { if index < windows.len() { group.setSelectedWindow(&windows[index]); } } } } #[inline] fn num_tabs(&self) -> usize { self.tabGroup() .and_then(|group| group.tabbedWindows()) .map(|windows| windows.len()) .unwrap_or(1) } fn is_document_edited(&self) -> bool { self.isDocumentEdited() } fn set_document_edited(&self, edited: bool) { self.setDocumentEdited(edited) } fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { let mut shared_state_lock = self.lock_shared_state("set_option_as_alt"); shared_state_lock.option_as_alt = option_as_alt; } fn option_as_alt(&self) -> OptionAsAlt { let shared_state_lock = self.lock_shared_state("option_as_alt"); shared_state_lock.option_as_alt } } pub(super) fn get_ns_theme() -> Theme { let app = NSApp(); let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] }; if !has_theme { return Theme::Light; } let appearance = app.effectiveAppearance(); let name = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ NSString::from_str("NSAppearanceNameAqua"), NSString::from_str("NSAppearanceNameDarkAqua"), ])); match &*name.to_string() { "NSAppearanceNameDarkAqua" => Theme::Dark, _ => Theme::Light, } } fn set_ns_theme(theme: Option) { let app = NSApp(); let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] }; if has_theme { let appearance = theme.map(|t| { let name = match t { Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"), Theme::Light => NSString::from_str("NSAppearanceNameAqua"), }; NSAppearance::appearanceNamed(&name) }); app.setAppearance(appearance.as_ref().map(|a| a.as_ref())); } } winit-0.29.15/src/platform_impl/macos/window_delegate.rs000064400000000000000000000471511046102023000214110ustar 00000000000000#![allow(clippy::unnecessary_cast)] use std::cell::Cell; use std::ptr::{self, NonNull}; use icrate::Foundation::{NSArray, NSObject, NSSize, NSString}; use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; use objc2::runtime::AnyObject; use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; use super::appkit::{ NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, }; use super::{ app_state::AppState, util, window::{get_ns_theme, WinitWindow}, Fullscreen, }; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{Event, WindowEvent}, window::WindowId, }; #[derive(Debug)] pub struct State { // This is set when WindowBuilder::with_fullscreen was set, // see comments of `window_did_fail_to_enter_fullscreen` initial_fullscreen: Cell, // During `windowDidResize`, we use this to only send Moved if the position changed. previous_position: Cell>, // Used to prevent redundant events. previous_scale_factor: Cell, } declare_class!( #[derive(Debug)] pub(crate) struct WinitWindowDelegate { window: IvarDrop, "_window">, // TODO: It may be possible for delegate methods to be called // asynchronously, causing data races panics? // TODO: Remove unnecessary boxing here state: IvarDrop, "_state">, } mod ivars; unsafe impl ClassType for WinitWindowDelegate { type Super = NSObject; type Mutability = mutability::InteriorMutable; const NAME: &'static str = "WinitWindowDelegate"; } unsafe impl WinitWindowDelegate { #[method(initWithWindow:initialFullscreen:)] unsafe fn init_with_winit( this: *mut Self, window: &WinitWindow, initial_fullscreen: bool, ) -> Option> { let this: Option<&mut Self> = unsafe { msg_send![super(this), init] }; this.map(|this| { let scale_factor = window.scale_factor(); Ivar::write(&mut this.window, window.retain()); Ivar::write( &mut this.state, Box::new(State { initial_fullscreen: Cell::new(initial_fullscreen), previous_position: Cell::new(None), previous_scale_factor: Cell::new(scale_factor), }), ); if scale_factor != 1.0 { this.queue_static_scale_factor_changed_event(); } this.window.setDelegate(Some(this)); // Enable theme change event let notification_center: Id = unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] }; let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification"); let _: () = unsafe { msg_send![ ¬ification_center, addObserver: &*this selector: sel!(effectiveAppearanceDidChange:) name: &*notification_name object: ptr::null::() ] }; NonNull::from(this) }) } } // NSWindowDelegate + NSDraggingDestination protocols unsafe impl WinitWindowDelegate { #[method(windowShouldClose:)] fn window_should_close(&self, _: Option<&AnyObject>) -> bool { trace_scope!("windowShouldClose:"); self.queue_event(WindowEvent::CloseRequested); false } #[method(windowWillClose:)] fn window_will_close(&self, _: Option<&AnyObject>) { trace_scope!("windowWillClose:"); // `setDelegate:` retains the previous value and then autoreleases it autoreleasepool(|_| { // Since El Capitan, we need to be careful that delegate methods can't // be called after the window closes. self.window.setDelegate(None); }); self.queue_event(WindowEvent::Destroyed); } #[method(windowDidResize:)] fn window_did_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResize:"); // NOTE: WindowEvent::Resized is reported in frameDidChange. self.emit_move_event(); } #[method(windowWillStartLiveResize:)] fn window_will_start_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowWillStartLiveResize:"); let increments = self .window .lock_shared_state("window_will_enter_fullscreen") .resize_increments; self.window.set_resize_increments_inner(increments); } #[method(windowDidEndLiveResize:)] fn window_did_end_live_resize(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEndLiveResize:"); self.window.set_resize_increments_inner(NSSize::new(1., 1.)); } // This won't be triggered if the move was part of a resize. #[method(windowDidMove:)] fn window_did_move(&self, _: Option<&AnyObject>) { trace_scope!("windowDidMove:"); self.emit_move_event(); } #[method(windowDidChangeBackingProperties:)] fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeBackingProperties:"); self.queue_static_scale_factor_changed_event(); } #[method(windowDidBecomeKey:)] fn window_did_become_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidBecomeKey:"); // TODO: center the cursor if the window had mouse grab when it // lost focus self.queue_event(WindowEvent::Focused(true)); } #[method(windowDidResignKey:)] fn window_did_resign_key(&self, _: Option<&AnyObject>) { trace_scope!("windowDidResignKey:"); // 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. self.window.view().reset_modifiers(); self.queue_event(WindowEvent::Focused(false)); } /// Invoked when the dragged image enters destination bounds or frame #[method(draggingEntered:)] fn dragging_entered(&self, sender: &NSObject) -> bool { trace_scope!("draggingEntered:"); use std::path::PathBuf; let pb: Id = unsafe { msg_send_id![sender, draggingPasteboard] }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }); let filenames: Id> = unsafe { Id::cast(filenames) }; filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); self.queue_event(WindowEvent::HoveredFile(path)); }); true } /// Invoked when the image is released #[method(prepareForDragOperation:)] fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool { trace_scope!("prepareForDragOperation:"); true } /// Invoked after the released image has been removed from the screen #[method(performDragOperation:)] fn perform_drag_operation(&self, sender: &NSObject) -> bool { trace_scope!("performDragOperation:"); use std::path::PathBuf; let pb: Id = unsafe { msg_send_id![sender, draggingPasteboard] }; let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }); let filenames: Id> = unsafe { Id::cast(filenames) }; filenames.into_iter().for_each(|file| { let path = PathBuf::from(file.to_string()); self.queue_event(WindowEvent::DroppedFile(path)); }); true } /// Invoked when the dragging operation is complete #[method(concludeDragOperation:)] fn conclude_drag_operation(&self, _sender: Option<&NSObject>) { trace_scope!("concludeDragOperation:"); } /// Invoked when the dragging operation is cancelled #[method(draggingExited:)] fn dragging_exited(&self, _sender: Option<&NSObject>) { trace_scope!("draggingExited:"); self.queue_event(WindowEvent::HoveredFileCancelled); } /// Invoked when before enter fullscreen #[method(windowWillEnterFullScreen:)] fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillEnterFullScreen:"); let mut shared_state = self .window .lock_shared_state("window_will_enter_fullscreen"); shared_state.maximized = self.window.is_zoomed(); let fullscreen = shared_state.fullscreen.as_ref(); match 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 = self.window.current_monitor_inner(); shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) } } shared_state.in_fullscreen_transition = true; } /// Invoked when before exit fullscreen #[method(windowWillExitFullScreen:)] fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowWillExitFullScreen:"); let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen"); shared_state.in_fullscreen_transition = true; } #[method(window:willUseFullScreenPresentationOptions:)] fn window_will_use_fullscreen_presentation_options( &self, _: Option<&AnyObject>, proposed_options: NSApplicationPresentationOptions, ) -> NSApplicationPresentationOptions { trace_scope!("window:willUseFullScreenPresentationOptions:"); // 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 = proposed_options; let shared_state = self .window .lock_shared_state("window_will_use_fullscreen_presentation_options"); if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen { options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen | NSApplicationPresentationOptions::NSApplicationPresentationHideDock | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; } options } /// Invoked when entered fullscreen #[method(windowDidEnterFullScreen:)] fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidEnterFullScreen:"); self.state.initial_fullscreen.set(false); let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen"); shared_state.in_fullscreen_transition = false; let target_fullscreen = shared_state.target_fullscreen.take(); drop(shared_state); if let Some(target_fullscreen) = target_fullscreen { self.window.set_fullscreen(target_fullscreen); } } /// Invoked when exited fullscreen #[method(windowDidExitFullScreen:)] fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidExitFullScreen:"); self.window.restore_state_from_fullscreen(); let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen"); shared_state.in_fullscreen_transition = false; let target_fullscreen = shared_state.target_fullscreen.take(); drop(shared_state); if let Some(target_fullscreen) = target_fullscreen { self.window.set_fullscreen(target_fullscreen); } } /// 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. #[method(windowDidFailToEnterFullScreen:)] fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidFailToEnterFullScreen:"); let mut shared_state = self .window .lock_shared_state("window_did_fail_to_enter_fullscreen"); shared_state.in_fullscreen_transition = false; shared_state.target_fullscreen = None; if self.state.initial_fullscreen.get() { #[allow(clippy::let_unit_value)] unsafe { let _: () = msg_send![ &*self.window, performSelector: sel!(toggleFullScreen:), withObject: ptr::null::(), afterDelay: 0.5, ]; }; } else { self.window.restore_state_from_fullscreen(); } } // Invoked when the occlusion state of the window changes #[method(windowDidChangeOcclusionState:)] fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeOcclusionState:"); self.queue_event(WindowEvent::Occluded( !self .window .occlusionState() .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), )) } // Observe theme change #[method(effectiveAppearanceDidChange:)] fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) { trace_scope!("Triggered `effectiveAppearanceDidChange:`"); unsafe { msg_send![ self, performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:), withObject: sender, waitUntilDone: false, ] } } #[method(effectiveAppearanceDidChangedOnMainThread:)] fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) { let theme = get_ns_theme(); let mut shared_state = self .window .lock_shared_state("effective_appearance_did_change"); let current_theme = shared_state.current_theme; shared_state.current_theme = Some(theme); drop(shared_state); if current_theme != Some(theme) { self.queue_event(WindowEvent::ThemeChanged(theme)); } } #[method(windowDidChangeScreen:)] fn window_did_change_screen(&self, _: Option<&AnyObject>) { trace_scope!("windowDidChangeScreen:"); let is_simple_fullscreen = self .window .lock_shared_state("window_did_change_screen") .is_simple_fullscreen; if is_simple_fullscreen { if let Some(screen) = self.window.screen() { self.window.setFrame_display(screen.frame(), true); } } } } ); impl WinitWindowDelegate { pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id { unsafe { msg_send_id![ Self::alloc(), initWithWindow: window, initialFullscreen: initial_fullscreen, ] } } pub(crate) fn queue_event(&self, event: WindowEvent) { let event = Event::WindowEvent { window_id: WindowId(self.window.id()), event, }; AppState::queue_event(event); } fn queue_static_scale_factor_changed_event(&self) { let scale_factor = self.window.scale_factor(); if scale_factor == self.state.previous_scale_factor.get() { return; }; self.state.previous_scale_factor.set(scale_factor); let suggested_size = self.view_size(); AppState::queue_static_scale_factor_changed_event( self.window.clone(), suggested_size.to_physical(scale_factor), scale_factor, ); } fn emit_move_event(&self) { let rect = self.window.frame(); let x = rect.origin.x as f64; let y = util::bottom_left_to_top_left(rect); if self.state.previous_position.get() != Some((x, y)) { self.state.previous_position.set(Some((x, y))); let scale_factor = self.window.scale_factor(); let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); self.queue_event(WindowEvent::Moved(physical_pos)); } } fn view_size(&self) -> LogicalSize { let size = self.window.contentView().frame().size; LogicalSize::new(size.width as f64, size.height as f64) } } winit-0.29.15/src/platform_impl/mod.rs000064400000000000000000000036121046102023000157170ustar 00000000000000use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}; use crate::window::Fullscreen as RootFullscreen; #[cfg(windows_platform)] #[path = "windows/mod.rs"] mod platform; #[cfg(any(x11_platform, wayland_platform))] #[path = "linux/mod.rs"] mod platform; #[cfg(macos_platform)] #[path = "macos/mod.rs"] mod platform; #[cfg(android_platform)] #[path = "android/mod.rs"] mod platform; #[cfg(ios_platform)] #[path = "ios/mod.rs"] mod platform; #[cfg(wasm_platform)] #[path = "web/mod.rs"] mod platform; #[cfg(orbital_platform)] #[path = "orbital/mod.rs"] mod platform; pub use self::platform::*; /// Helper for converting between platform-specific and generic VideoMode/MonitorHandle #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Fullscreen { Exclusive(VideoMode), Borderless(Option), } impl From for Fullscreen { fn from(f: RootFullscreen) -> Self { match f { RootFullscreen::Exclusive(mode) => Self::Exclusive(mode.video_mode), RootFullscreen::Borderless(Some(handle)) => Self::Borderless(Some(handle.inner)), RootFullscreen::Borderless(None) => Self::Borderless(None), } } } impl From for RootFullscreen { fn from(f: Fullscreen) -> Self { match f { Fullscreen::Exclusive(video_mode) => Self::Exclusive(RootVideoMode { video_mode }), Fullscreen::Borderless(Some(inner)) => { Self::Borderless(Some(RootMonitorHandle { inner })) } Fullscreen::Borderless(None) => Self::Borderless(None), } } } #[cfg(all( not(ios_platform), not(windows_platform), not(macos_platform), not(android_platform), not(x11_platform), not(wayland_platform), not(wasm_platform), not(orbital_platform), ))] compile_error!("The platform you're compiling for is not supported by winit"); winit-0.29.15/src/platform_impl/orbital/event_loop.rs000064400000000000000000001010101046102023000207350ustar 00000000000000use std::{ cell::Cell, collections::VecDeque, marker::PhantomData, mem, slice, sync::{mpsc, Arc, Mutex}, time::Instant, }; use orbclient::{ ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent, MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent, }; use smol_str::SmolStr; use crate::{ error::EventLoopError, event::{self, Ime, Modifiers, StartCause}, event_loop::{self, ControlFlow, DeviceEvents}, keyboard::{ Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode, PhysicalKey, }, window::WindowId as RootWindowId, }; use super::{ DeviceId, KeyEventExtra, MonitorHandle, OsError, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties, }; fn convert_scancode(scancode: u8) -> (PhysicalKey, Option) { // Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html let (key_code, named_key_opt) = match scancode { orbclient::K_A => (KeyCode::KeyA, None), orbclient::K_B => (KeyCode::KeyB, None), orbclient::K_C => (KeyCode::KeyC, None), orbclient::K_D => (KeyCode::KeyD, None), orbclient::K_E => (KeyCode::KeyE, None), orbclient::K_F => (KeyCode::KeyF, None), orbclient::K_G => (KeyCode::KeyG, None), orbclient::K_H => (KeyCode::KeyH, None), orbclient::K_I => (KeyCode::KeyI, None), orbclient::K_J => (KeyCode::KeyJ, None), orbclient::K_K => (KeyCode::KeyK, None), orbclient::K_L => (KeyCode::KeyL, None), orbclient::K_M => (KeyCode::KeyM, None), orbclient::K_N => (KeyCode::KeyN, None), orbclient::K_O => (KeyCode::KeyO, None), orbclient::K_P => (KeyCode::KeyP, None), orbclient::K_Q => (KeyCode::KeyQ, None), orbclient::K_R => (KeyCode::KeyR, None), orbclient::K_S => (KeyCode::KeyS, None), orbclient::K_T => (KeyCode::KeyT, None), orbclient::K_U => (KeyCode::KeyU, None), orbclient::K_V => (KeyCode::KeyV, None), orbclient::K_W => (KeyCode::KeyW, None), orbclient::K_X => (KeyCode::KeyX, None), orbclient::K_Y => (KeyCode::KeyY, None), orbclient::K_Z => (KeyCode::KeyZ, None), orbclient::K_0 => (KeyCode::Digit0, None), orbclient::K_1 => (KeyCode::Digit1, None), orbclient::K_2 => (KeyCode::Digit2, None), orbclient::K_3 => (KeyCode::Digit3, None), orbclient::K_4 => (KeyCode::Digit4, None), orbclient::K_5 => (KeyCode::Digit5, None), orbclient::K_6 => (KeyCode::Digit6, None), orbclient::K_7 => (KeyCode::Digit7, None), orbclient::K_8 => (KeyCode::Digit8, None), orbclient::K_9 => (KeyCode::Digit9, None), orbclient::K_ALT => (KeyCode::AltLeft, Some(NamedKey::Alt)), orbclient::K_ALT_GR => (KeyCode::AltRight, Some(NamedKey::AltGraph)), orbclient::K_BACKSLASH => (KeyCode::Backslash, None), orbclient::K_BKSP => (KeyCode::Backspace, Some(NamedKey::Backspace)), orbclient::K_BRACE_CLOSE => (KeyCode::BracketRight, None), orbclient::K_BRACE_OPEN => (KeyCode::BracketLeft, None), orbclient::K_CAPS => (KeyCode::CapsLock, Some(NamedKey::CapsLock)), orbclient::K_COMMA => (KeyCode::Comma, None), orbclient::K_CTRL => (KeyCode::ControlLeft, Some(NamedKey::Control)), orbclient::K_DEL => (KeyCode::Delete, Some(NamedKey::Delete)), orbclient::K_DOWN => (KeyCode::ArrowDown, Some(NamedKey::ArrowDown)), orbclient::K_END => (KeyCode::End, Some(NamedKey::End)), orbclient::K_ENTER => (KeyCode::Enter, Some(NamedKey::Enter)), orbclient::K_EQUALS => (KeyCode::Equal, None), orbclient::K_ESC => (KeyCode::Escape, Some(NamedKey::Escape)), orbclient::K_F1 => (KeyCode::F1, Some(NamedKey::F1)), orbclient::K_F2 => (KeyCode::F2, Some(NamedKey::F2)), orbclient::K_F3 => (KeyCode::F3, Some(NamedKey::F3)), orbclient::K_F4 => (KeyCode::F4, Some(NamedKey::F4)), orbclient::K_F5 => (KeyCode::F5, Some(NamedKey::F5)), orbclient::K_F6 => (KeyCode::F6, Some(NamedKey::F6)), orbclient::K_F7 => (KeyCode::F7, Some(NamedKey::F7)), orbclient::K_F8 => (KeyCode::F8, Some(NamedKey::F8)), orbclient::K_F9 => (KeyCode::F9, Some(NamedKey::F9)), orbclient::K_F10 => (KeyCode::F10, Some(NamedKey::F10)), orbclient::K_F11 => (KeyCode::F11, Some(NamedKey::F11)), orbclient::K_F12 => (KeyCode::F12, Some(NamedKey::F12)), orbclient::K_HOME => (KeyCode::Home, Some(NamedKey::Home)), orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)), orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)), orbclient::K_MINUS => (KeyCode::Minus, None), orbclient::K_NUM_0 => (KeyCode::Numpad0, None), orbclient::K_NUM_1 => (KeyCode::Numpad1, None), orbclient::K_NUM_2 => (KeyCode::Numpad2, None), orbclient::K_NUM_3 => (KeyCode::Numpad3, None), orbclient::K_NUM_4 => (KeyCode::Numpad4, None), orbclient::K_NUM_5 => (KeyCode::Numpad5, None), orbclient::K_NUM_6 => (KeyCode::Numpad6, None), orbclient::K_NUM_7 => (KeyCode::Numpad7, None), orbclient::K_NUM_8 => (KeyCode::Numpad8, None), orbclient::K_NUM_9 => (KeyCode::Numpad9, None), orbclient::K_PERIOD => (KeyCode::Period, None), orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)), orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)), orbclient::K_QUOTE => (KeyCode::Quote, None), orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)), orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)), orbclient::K_SEMICOLON => (KeyCode::Semicolon, None), orbclient::K_SLASH => (KeyCode::Slash, None), orbclient::K_SPACE => (KeyCode::Space, Some(NamedKey::Space)), orbclient::K_SUPER => (KeyCode::SuperLeft, Some(NamedKey::Super)), orbclient::K_TAB => (KeyCode::Tab, Some(NamedKey::Tab)), orbclient::K_TICK => (KeyCode::Backquote, None), orbclient::K_UP => (KeyCode::ArrowUp, Some(NamedKey::ArrowUp)), orbclient::K_VOLUME_DOWN => (KeyCode::AudioVolumeDown, Some(NamedKey::AudioVolumeDown)), orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)), orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)), _ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None), }; (PhysicalKey::Code(key_code), named_key_opt) } fn element_state(pressed: bool) -> event::ElementState { if pressed { event::ElementState::Pressed } else { event::ElementState::Released } } bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] struct KeyboardModifierState: u8 { const LSHIFT = 1 << 0; const RSHIFT = 1 << 1; const LCTRL = 1 << 2; const RCTRL = 1 << 3; const LALT = 1 << 4; const RALT = 1 << 5; const LSUPER = 1 << 6; const RSUPER = 1 << 7; } } bitflags! { #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] struct MouseButtonState: u8 { const LEFT = 1 << 0; const MIDDLE = 1 << 1; const RIGHT = 1 << 2; } } #[derive(Default)] struct EventState { keyboard: KeyboardModifierState, mouse: MouseButtonState, resize_opt: Option<(u32, u32)>, } impl EventState { fn character_all_modifiers(&self, character: char) -> char { // Modify character if Ctrl is pressed #[allow(clippy::collapsible_if)] if self.keyboard.contains(KeyboardModifierState::LCTRL) || self.keyboard.contains(KeyboardModifierState::RCTRL) { if character.is_ascii_lowercase() { return ((character as u8 - b'a') + 1) as char; } //TODO: more control key variants? } // Return character as-is if no special handling required character } fn key(&mut self, key: PhysicalKey, pressed: bool) { let code = match key { PhysicalKey::Code(code) => code, _ => return, }; match code { KeyCode::ShiftLeft => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed), KeyCode::ShiftRight => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed), KeyCode::ControlLeft => self.keyboard.set(KeyboardModifierState::LCTRL, pressed), KeyCode::ControlRight => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), KeyCode::AltLeft => self.keyboard.set(KeyboardModifierState::LALT, pressed), KeyCode::AltRight => self.keyboard.set(KeyboardModifierState::RALT, pressed), KeyCode::SuperLeft => self.keyboard.set(KeyboardModifierState::LSUPER, pressed), KeyCode::SuperRight => self.keyboard.set(KeyboardModifierState::RSUPER, pressed), _ => (), } } fn mouse( &mut self, left: bool, middle: bool, right: bool, ) -> Option<(event::MouseButton, event::ElementState)> { if self.mouse.contains(MouseButtonState::LEFT) != left { self.mouse.set(MouseButtonState::LEFT, left); return Some((event::MouseButton::Left, element_state(left))); } if self.mouse.contains(MouseButtonState::MIDDLE) != middle { self.mouse.set(MouseButtonState::MIDDLE, middle); return Some((event::MouseButton::Middle, element_state(middle))); } if self.mouse.contains(MouseButtonState::RIGHT) != right { self.mouse.set(MouseButtonState::RIGHT, right); return Some((event::MouseButton::Right, element_state(right))); } None } fn modifiers(&self) -> Modifiers { let mut state = ModifiersState::empty(); let mut pressed_mods = ModifiersKeys::empty(); if self .keyboard .intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) { state |= ModifiersState::SHIFT; } pressed_mods.set( ModifiersKeys::LSHIFT, self.keyboard.contains(KeyboardModifierState::LSHIFT), ); pressed_mods.set( ModifiersKeys::RSHIFT, self.keyboard.contains(KeyboardModifierState::RSHIFT), ); if self .keyboard .intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) { state |= ModifiersState::CONTROL; } pressed_mods.set( ModifiersKeys::LCONTROL, self.keyboard.contains(KeyboardModifierState::LCTRL), ); pressed_mods.set( ModifiersKeys::RCONTROL, self.keyboard.contains(KeyboardModifierState::RCTRL), ); if self .keyboard .intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) { state |= ModifiersState::ALT; } pressed_mods.set( ModifiersKeys::LALT, self.keyboard.contains(KeyboardModifierState::LALT), ); pressed_mods.set( ModifiersKeys::RALT, self.keyboard.contains(KeyboardModifierState::RALT), ); if self .keyboard .intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) { state |= ModifiersState::SUPER } pressed_mods.set( ModifiersKeys::LSUPER, self.keyboard.contains(KeyboardModifierState::LSUPER), ); pressed_mods.set( ModifiersKeys::RSUPER, self.keyboard.contains(KeyboardModifierState::RSUPER), ); Modifiers { state, pressed_mods, } } } pub struct EventLoop { windows: Vec<(Arc, EventState)>, window_target: event_loop::EventLoopWindowTarget, user_events_sender: mpsc::Sender, user_events_receiver: mpsc::Receiver, } impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result { let (user_events_sender, user_events_receiver) = mpsc::channel(); let event_socket = Arc::new( RedoxSocket::event() .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?, ); let wake_socket = Arc::new( TimeSocket::open() .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?, ); event_socket .write(&syscall::Event { id: wake_socket.0.fd, flags: syscall::EventFlags::EVENT_READ, data: wake_socket.0.fd, }) .map_err(OsError::new) .map_err(|error| EventLoopError::Os(os_error!(error)))?; Ok(Self { windows: Vec::new(), window_target: event_loop::EventLoopWindowTarget { p: EventLoopWindowTarget { control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(false), creates: Mutex::new(VecDeque::new()), redraws: Arc::new(Mutex::new(VecDeque::new())), destroys: Arc::new(Mutex::new(VecDeque::new())), event_socket, wake_socket, p: PhantomData, }, _marker: PhantomData, }, user_events_sender, user_events_receiver, }) } fn process_event( window_id: WindowId, event_option: EventOption, event_state: &mut EventState, mut event_handler: F, ) where F: FnMut(event::Event), { match event_option { EventOption::Key(KeyEvent { character, scancode, pressed, }) => { // Convert scancode let (physical_key, named_key_opt) = convert_scancode(scancode); // Get previous modifiers and update modifiers based on physical key let modifiers_before = event_state.keyboard; event_state.key(physical_key, pressed); // Default to unidentified key with no text let mut logical_key = Key::Unidentified(NativeKey::Unidentified); let mut key_without_modifiers = logical_key.clone(); let mut text = None; let mut text_with_all_modifiers = None; // Set key and text based on character if character != '\0' { let mut tmp = [0u8; 4]; let character_str = character.encode_utf8(&mut tmp); // The key with Shift and Caps Lock applied (but not Ctrl) logical_key = Key::Character(character_str.into()); // The key without Shift or Caps Lock applied key_without_modifiers = Key::Character(SmolStr::from_iter(character.to_lowercase())); if pressed { // The key with Shift and Caps Lock applied (but not Ctrl) text = Some(character_str.into()); // The key with Shift, Caps Lock, and Ctrl applied let character_all_modifiers = event_state.character_all_modifiers(character); text_with_all_modifiers = Some(character_all_modifiers.encode_utf8(&mut tmp).into()) } }; // Override key if a named key was found (this is to allow Enter to replace '\n') if let Some(named_key) = named_key_opt { logical_key = Key::Named(named_key); key_without_modifiers = logical_key.clone(); } event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::KeyboardInput { device_id: event::DeviceId(DeviceId), event: event::KeyEvent { logical_key, physical_key, location: KeyLocation::Standard, state: element_state(pressed), repeat: false, text, platform_specific: KeyEventExtra { key_without_modifiers, text_with_all_modifiers, }, }, is_synthetic: false, }, }); // If the state of the modifiers has changed, send the event. if modifiers_before != event_state.keyboard { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::ModifiersChanged(event_state.modifiers()), }) } } EventOption::TextInput(TextInputEvent { character }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)), }); event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Ime(Ime::Commit(character.into())), }); } EventOption::Mouse(MouseEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorMoved { device_id: event::DeviceId(DeviceId), position: (x, y).into(), }, }); } EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => { event_handler(event::Event::DeviceEvent { device_id: event::DeviceId(DeviceId), event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64), }, }); } EventOption::Button(ButtonEvent { left, middle, right, }) => { while let Some((button, state)) = event_state.mouse(left, middle, right) { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::MouseInput { device_id: event::DeviceId(DeviceId), state, button, }, }); } } EventOption::Scroll(ScrollEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::MouseWheel { device_id: event::DeviceId(DeviceId), delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32), phase: event::TouchPhase::Moved, }, }); } EventOption::Quit(QuitEvent {}) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CloseRequested, }); } EventOption::Focus(FocusEvent { focused }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Focused(focused), }); } EventOption::Move(MoveEvent { x, y }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Moved((x, y).into()), }); } EventOption::Resize(ResizeEvent { width, height }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Resized((width, height).into()), }); // Acknowledge resize after event loop. event_state.resize_opt = Some((width, height)); } //TODO: Screen, Clipboard, Drop EventOption::Hover(HoverEvent { entered }) => { if entered { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId), }, }); } else { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId), }, }); } } other => { warn!("unhandled event: {:?}", other); } } } pub fn run(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError> where F: FnMut(event::Event, &event_loop::EventLoopWindowTarget), { let mut event_handler = move |event: event::Event, window_target: &event_loop::EventLoopWindowTarget| { event_handler_inner(event, window_target); }; let mut start_cause = StartCause::Init; loop { event_handler(event::Event::NewEvents(start_cause), &self.window_target); if start_cause == StartCause::Init { event_handler(event::Event::Resumed, &self.window_target); } // Handle window creates. while let Some(window) = { let mut creates = self.window_target.p.creates.lock().unwrap(); creates.pop_front() } { let window_id = WindowId { fd: window.fd as u64, }; let mut buf: [u8; 4096] = [0; 4096]; let path = window.fpath(&mut buf).expect("failed to read properties"); let properties = WindowProperties::new(path); self.windows.push((window, EventState::default())); // Send resize event on create to indicate first size. event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Resized((properties.w, properties.h).into()), }, &self.window_target, ); // Send resize event on create to indicate first position. event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::Moved((properties.x, properties.y).into()), }, &self.window_target, ); } // Handle window destroys. while let Some(destroy_id) = { let mut destroys = self.window_target.p.destroys.lock().unwrap(); destroys.pop_front() } { event_handler( event::Event::WindowEvent { window_id: RootWindowId(destroy_id), event: event::WindowEvent::Destroyed, }, &self.window_target, ); self.windows .retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd); } // Handle window events. let mut i = 0; // While loop is used here because the same window may be processed more than once. while let Some((window, event_state)) = self.windows.get_mut(i) { let window_id = WindowId { fd: window.fd as u64, }; let mut event_buf = [0u8; 16 * mem::size_of::()]; let count = syscall::read(window.fd, &mut event_buf).expect("failed to read window events"); // Safety: orbclient::Event is a packed struct designed to be transferred over a socket. let events = unsafe { slice::from_raw_parts( event_buf.as_ptr() as *const orbclient::Event, count / mem::size_of::(), ) }; for orbital_event in events { Self::process_event( window_id, orbital_event.to_option(), event_state, |event| event_handler(event, &self.window_target), ); } if count == event_buf.len() { // If event buf was full, process same window again to ensure all events are drained. continue; } // Acknowledge the latest resize event. if let Some((w, h)) = event_state.resize_opt.take() { window .write(format!("S,{w},{h}").as_bytes()) .expect("failed to acknowledge resize"); // Require redraw after resize. let mut redraws = self.window_target.p.redraws.lock().unwrap(); if !redraws.contains(&window_id) { redraws.push_back(window_id); } } // Move to next window. i += 1; } while let Ok(event) = self.user_events_receiver.try_recv() { event_handler(event::Event::UserEvent(event), &self.window_target); } // To avoid deadlocks the redraws lock is not held during event processing. while let Some(window_id) = { let mut redraws = self.window_target.p.redraws.lock().unwrap(); redraws.pop_front() } { event_handler( event::Event::WindowEvent { window_id: RootWindowId(window_id), event: event::WindowEvent::RedrawRequested, }, &self.window_target, ); } event_handler(event::Event::AboutToWait, &self.window_target); if self.window_target.p.exiting() { break; } let requested_resume = match self.window_target.p.control_flow() { ControlFlow::Poll => { start_cause = StartCause::Poll; continue; } ControlFlow::Wait => None, ControlFlow::WaitUntil(instant) => Some(instant), }; // Re-using wake socket caused extra wake events before because there were leftover // timeouts, and then new timeouts were added each time a spurious timeout expired. let timeout_socket = TimeSocket::open().unwrap(); self.window_target .p .event_socket .write(&syscall::Event { id: timeout_socket.0.fd, flags: syscall::EventFlags::EVENT_READ, data: 0, }) .unwrap(); let start = Instant::now(); if let Some(instant) = requested_resume { let mut time = timeout_socket.current_time().unwrap(); if let Some(duration) = instant.checked_duration_since(start) { time.tv_sec += duration.as_secs() as i64; time.tv_nsec += duration.subsec_nanos() as i32; // Normalize timespec so tv_nsec is not greater than one second. while time.tv_nsec >= 1_000_000_000 { time.tv_sec += 1; time.tv_nsec -= 1_000_000_000; } } timeout_socket.timeout(&time).unwrap(); } // Wait for event if needed. let mut event = syscall::Event::default(); self.window_target.p.event_socket.read(&mut event).unwrap(); // TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) match requested_resume { Some(requested_resume) if event.id == timeout_socket.0.fd => { // If the event is from the special timeout socket, report that resume // time was reached. start_cause = StartCause::ResumeTimeReached { start, requested_resume, }; } _ => { // Normal window event or spurious timeout. start_cause = StartCause::WaitCancelled { start, requested_resume, }; } } } event_handler(event::Event::LoopExiting, &self.window_target); Ok(()) } pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { &self.window_target } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_events_sender: self.user_events_sender.clone(), wake_socket: self.window_target.p.wake_socket.clone(), } } } pub struct EventLoopProxy { user_events_sender: mpsc::Sender, wake_socket: Arc, } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { self.user_events_sender .send(event) .map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?; self.wake_socket.wake().unwrap(); Ok(()) } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { user_events_sender: self.user_events_sender.clone(), wake_socket: self.wake_socket.clone(), } } } impl Unpin for EventLoopProxy {} pub struct EventLoopWindowTarget { control_flow: Cell, exit: Cell, pub(super) creates: Mutex>>, pub(super) redraws: Arc>>, pub(super) destroys: Arc>>, pub(super) event_socket: Arc, pub(super) wake_socket: Arc, p: PhantomData, } impl EventLoopWindowTarget { pub fn primary_monitor(&self) -> Option { Some(MonitorHandle) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle); v } #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Orbital( rwh_06::OrbitalDisplayHandle::new(), )) } pub fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub(crate) fn exit(&self) { self.exit.set(true); } pub(crate) fn exiting(&self) -> bool { self.exit.get() } } winit-0.29.15/src/platform_impl/orbital/mod.rs000064400000000000000000000147771046102023000173710ustar 00000000000000#![cfg(target_os = "redox")] use std::fmt::{self, Display, Formatter}; use std::str; use std::sync::Arc; use smol_str::SmolStr; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, keyboard::Key, }; pub use self::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; mod event_loop; pub use self::window::Window; mod window; struct RedoxSocket { fd: usize, } impl RedoxSocket { fn event() -> syscall::Result { Self::open_raw("event:") } fn orbital(properties: &WindowProperties<'_>) -> syscall::Result { Self::open_raw(&format!("{properties}")) } // Paths should be checked to ensure they are actually sockets and not normal files. If a // non-socket path is used, it could cause read and write to not function as expected. For // example, the seek would change in a potentially unpredictable way if either read or write // were called at the same time by multiple threads. fn open_raw(path: &str) -> syscall::Result { let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?; Ok(Self { fd }) } fn read(&self, buf: &mut [u8]) -> syscall::Result<()> { let count = syscall::read(self.fd, buf)?; if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) } } fn write(&self, buf: &[u8]) -> syscall::Result<()> { let count = syscall::write(self.fd, buf)?; if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) } } fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> { let count = syscall::fpath(self.fd, buf)?; str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL)) } } impl Drop for RedoxSocket { fn drop(&mut self) { let _ = syscall::close(self.fd); } } pub struct TimeSocket(RedoxSocket); impl TimeSocket { fn open() -> syscall::Result { RedoxSocket::open_raw("time:4").map(Self) } // Read current time. fn current_time(&self) -> syscall::Result { let mut timespec = syscall::TimeSpec::default(); self.0.read(&mut timespec)?; Ok(timespec) } // Write a timeout. fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> { self.0.write(timespec) } // Wake immediately. fn wake(&self) -> syscall::Result<()> { // Writing a default TimeSpec will always trigger a time event. self.timeout(&syscall::TimeSpec::default()) } } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId { fd: u64, } impl WindowId { pub const fn dummy() -> Self { WindowId { fd: u64::max_value(), } } } impl From for u64 { fn from(id: WindowId) -> Self { id.fd } } impl From for WindowId { fn from(fd: u64) -> Self { Self { fd } } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; impl DeviceId { pub const fn dummy() -> Self { DeviceId } } #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowBuilderAttributes; struct WindowProperties<'a> { flags: &'a str, x: i32, y: i32, w: u32, h: u32, title: &'a str, } impl<'a> WindowProperties<'a> { fn new(path: &'a str) -> Self { // orbital:flags/x/y/w/h/t let mut parts = path.splitn(6, '/'); let flags = parts.next().unwrap_or(""); let x = parts .next() .map_or(0, |part| part.parse::().unwrap_or(0)); let y = parts .next() .map_or(0, |part| part.parse::().unwrap_or(0)); let w = parts .next() .map_or(0, |part| part.parse::().unwrap_or(0)); let h = parts .next() .map_or(0, |part| part.parse::().unwrap_or(0)); let title = parts.next().unwrap_or(""); Self { flags, x, y, w, h, title, } } } impl<'a> fmt::Display for WindowProperties<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "orbital:{}/{}/{}/{}/{}/{}", self.flags, self.x, self.y, self.w, self.h, self.title ) } } #[derive(Clone, Debug)] pub struct OsError(Arc); impl OsError { fn new(error: syscall::Error) -> Self { Self(Arc::new(error)) } } impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { self.0.fmt(fmt) } } pub(crate) use crate::icon::NoIcon as PlatformIcon; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct MonitorHandle; impl MonitorHandle { pub fn name(&self) -> Option { Some("Redox Device".to_owned()) } pub fn size(&self) -> PhysicalSize { PhysicalSize::new(0, 0) // TODO } pub fn position(&self) -> PhysicalPosition { (0, 0).into() } pub fn scale_factor(&self) -> f64 { 1.0 // TODO } pub fn refresh_rate_millihertz(&self) -> Option { // FIXME no way to get real refresh rate for now. None } pub fn video_modes(&self) -> impl Iterator { let size = self.size().into(); // FIXME this is not the real refresh rate // (it is guaranteed to support 32 bit color though) std::iter::once(VideoMode { size, bit_depth: 32, refresh_rate_millihertz: 60000, monitor: self.clone(), }) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct VideoMode { size: (u32, u32), bit_depth: u16, refresh_rate_millihertz: u32, 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_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub key_without_modifiers: Key, pub text_with_all_modifiers: Option, } winit-0.29.15/src/platform_impl/orbital/window.rs000064400000000000000000000365731046102023000201170ustar 00000000000000use std::{ collections::VecDeque, sync::{Arc, Mutex}, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, platform_impl::Fullscreen, window, window::ImePurpose, }; use super::{ EventLoopWindowTarget, MonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, RedoxSocket, TimeSocket, WindowId, WindowProperties, }; // These values match the values uses in the `window_new` function in orbital: // https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs const ORBITAL_FLAG_ASYNC: char = 'a'; const ORBITAL_FLAG_BACK: char = 'b'; const ORBITAL_FLAG_FRONT: char = 'f'; const ORBITAL_FLAG_HIDDEN: char = 'h'; const ORBITAL_FLAG_BORDERLESS: char = 'l'; const ORBITAL_FLAG_MAXIMIZED: char = 'm'; const ORBITAL_FLAG_RESIZABLE: char = 'r'; const ORBITAL_FLAG_TRANSPARENT: char = 't'; pub struct Window { window_socket: Arc, redraws: Arc>>, destroys: Arc>>, wake_socket: Arc, } impl Window { pub(crate) fn new( el: &EventLoopWindowTarget, attrs: window::WindowAttributes, _: PlatformSpecificWindowBuilderAttributes, ) -> Result { let scale = MonitorHandle.scale_factor(); let (x, y) = if let Some(pos) = attrs.position { pos.to_physical::(scale).into() } else { // These coordinates are a special value to center the window. (-1, -1) }; let (w, h): (u32, u32) = if let Some(size) = attrs.inner_size { size.to_physical::(scale).into() } else { (1024, 768) }; //TODO: min/max inner_size // Async by default. let mut flag_str = ORBITAL_FLAG_ASYNC.to_string(); if attrs.maximized { flag_str.push(ORBITAL_FLAG_MAXIMIZED); } if attrs.resizable { flag_str.push(ORBITAL_FLAG_RESIZABLE); } //TODO: fullscreen if attrs.transparent { flag_str.push(ORBITAL_FLAG_TRANSPARENT); } if !attrs.decorations { flag_str.push(ORBITAL_FLAG_BORDERLESS); } if !attrs.visible { flag_str.push(ORBITAL_FLAG_HIDDEN); } match attrs.window_level { window::WindowLevel::AlwaysOnBottom => { flag_str.push(ORBITAL_FLAG_BACK); } window::WindowLevel::Normal => {} window::WindowLevel::AlwaysOnTop => { flag_str.push(ORBITAL_FLAG_FRONT); } } //TODO: window_icon // Open window. let window = RedoxSocket::orbital(&WindowProperties { flags: &flag_str, x, y, w, h, title: &attrs.title, }) .expect("failed to open window"); // Add to event socket. el.event_socket .write(&syscall::Event { id: window.fd, flags: syscall::EventFlags::EVENT_READ, data: window.fd, }) .unwrap(); let window_socket = Arc::new(window); // Notify event thread that this window was created, it will send some default events. { let mut creates = el.creates.lock().unwrap(); creates.push_back(window_socket.clone()); } el.wake_socket.wake().unwrap(); Ok(Self { window_socket, redraws: el.redraws.clone(), destroys: el.destroys.clone(), wake_socket: el.wake_socket.clone(), }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { f(self) } fn get_flag(&self, flag: char) -> Result { let mut buf: [u8; 4096] = [0; 4096]; let path = self .window_socket .fpath(&mut buf) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; let properties = WindowProperties::new(path); Ok(properties.flags.contains(flag)) } fn set_flag(&self, flag: char, value: bool) -> Result<(), error::ExternalError> { self.window_socket .write(format!("F,{flag},{}", if value { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn id(&self) -> WindowId { WindowId { fd: self.window_socket.fd as u64, } } #[inline] pub fn primary_monitor(&self) -> Option { Some(MonitorHandle) } #[inline] pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); v.push_back(MonitorHandle); v } #[inline] pub fn current_monitor(&self) -> Option { Some(MonitorHandle) } #[inline] pub fn scale_factor(&self) -> f64 { MonitorHandle.scale_factor() } #[inline] pub fn request_redraw(&self) { let window_id = self.id(); let mut redraws = self.redraws.lock().unwrap(); if !redraws.contains(&window_id) { redraws.push_back(window_id); self.wake_socket.wake().unwrap(); } } #[inline] pub fn pre_present_notify(&self) {} #[inline] pub fn reset_dead_keys(&self) { // TODO? } #[inline] pub fn inner_position(&self) -> Result, error::NotSupportedError> { let mut buf: [u8; 4096] = [0; 4096]; let path = self .window_socket .fpath(&mut buf) .expect("failed to read properties"); let properties = WindowProperties::new(path); Ok((properties.x, properties.y).into()) } #[inline] pub fn outer_position(&self) -> Result, error::NotSupportedError> { //TODO: adjust for window decorations self.inner_position() } #[inline] pub fn set_outer_position(&self, position: Position) { //TODO: adjust for window decorations let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); self.window_socket .write(format!("P,{x},{y}").as_bytes()) .expect("failed to set position"); } #[inline] pub fn inner_size(&self) -> PhysicalSize { let mut buf: [u8; 4096] = [0; 4096]; let path = self .window_socket .fpath(&mut buf) .expect("failed to read properties"); let properties = WindowProperties::new(path); (properties.w, properties.h).into() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let (w, h): (u32, u32) = size.to_physical::(self.scale_factor()).into(); self.window_socket .write(format!("S,{w},{h}").as_bytes()) .expect("failed to set size"); None } #[inline] pub fn outer_size(&self) -> PhysicalSize { //TODO: adjust for window decorations self.inner_size() } #[inline] pub fn set_min_inner_size(&self, _: Option) {} #[inline] pub fn set_max_inner_size(&self, _: Option) {} #[inline] pub fn title(&self) -> String { let mut buf: [u8; 4096] = [0; 4096]; let path = self .window_socket .fpath(&mut buf) .expect("failed to read properties"); let properties = WindowProperties::new(path); properties.title.to_string() } #[inline] pub fn set_title(&self, title: &str) { self.window_socket .write(format!("T,{title}").as_bytes()) .expect("failed to set title"); } #[inline] pub fn set_transparent(&self, transparent: bool) { let _ = self.set_flag(ORBITAL_FLAG_TRANSPARENT, transparent); } #[inline] pub fn set_blur(&self, _blur: bool) {} #[inline] pub fn set_visible(&self, visible: bool) { let _ = self.set_flag(ORBITAL_FLAG_HIDDEN, !visible); } #[inline] pub fn is_visible(&self) -> Option { Some(!self.get_flag(ORBITAL_FLAG_HIDDEN).unwrap_or(false)) } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) {} #[inline] pub fn set_resizable(&self, resizeable: bool) { let _ = self.set_flag(ORBITAL_FLAG_RESIZABLE, resizeable); } #[inline] pub fn is_resizable(&self) -> bool { self.get_flag(ORBITAL_FLAG_RESIZABLE).unwrap_or(false) } #[inline] pub fn set_minimized(&self, _minimized: bool) {} #[inline] pub fn is_minimized(&self) -> Option { None } #[inline] pub fn set_maximized(&self, maximized: bool) { let _ = self.set_flag(ORBITAL_FLAG_MAXIMIZED, maximized); } #[inline] pub fn is_maximized(&self) -> bool { self.get_flag(ORBITAL_FLAG_MAXIMIZED).unwrap_or(false) } #[inline] pub(crate) fn set_fullscreen(&self, _monitor: Option) {} #[inline] pub(crate) fn fullscreen(&self) -> Option { None } #[inline] pub fn set_decorations(&self, decorations: bool) { let _ = self.set_flag(ORBITAL_FLAG_BORDERLESS, !decorations); } #[inline] pub fn is_decorated(&self) -> bool { !self.get_flag(ORBITAL_FLAG_BORDERLESS).unwrap_or(false) } #[inline] pub fn set_window_level(&self, level: window::WindowLevel) { match level { window::WindowLevel::AlwaysOnBottom => { let _ = self.set_flag(ORBITAL_FLAG_BACK, true); } window::WindowLevel::Normal => { let _ = self.set_flag(ORBITAL_FLAG_BACK, false); let _ = self.set_flag(ORBITAL_FLAG_FRONT, false); } window::WindowLevel::AlwaysOnTop => { let _ = self.set_flag(ORBITAL_FLAG_FRONT, true); } } } #[inline] pub fn set_window_icon(&self, _window_icon: Option) {} #[inline] pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} #[inline] pub fn set_ime_allowed(&self, _allowed: bool) {} #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn focus_window(&self) {} #[inline] pub fn request_user_attention(&self, _request_type: Option) {} #[inline] pub fn set_cursor_icon(&self, _: window::CursorIcon) {} #[inline] pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), )) } #[inline] pub fn set_cursor_grab( &self, mode: window::CursorGrabMode, ) -> Result<(), error::ExternalError> { let (grab, relative) = match mode { window::CursorGrabMode::None => (false, false), window::CursorGrabMode::Confined => (true, false), window::CursorGrabMode::Locked => (true, true), }; self.window_socket .write(format!("M,G,{}", if grab { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; self.window_socket .write(format!("M,R,{}", if relative { 1 } else { 0 }).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { let _ = self .window_socket .write(format!("M,C,{}", if visible { 1 } else { 0 }).as_bytes()); } #[inline] pub fn drag_window(&self) -> Result<(), error::ExternalError> { self.window_socket .write(b"D") .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn drag_resize_window( &self, direction: window::ResizeDirection, ) -> Result<(), error::ExternalError> { let arg = match direction { window::ResizeDirection::East => "R", window::ResizeDirection::North => "T", window::ResizeDirection::NorthEast => "T,R", window::ResizeDirection::NorthWest => "T,L", window::ResizeDirection::South => "B", window::ResizeDirection::SouthEast => "B,R", window::ResizeDirection::SouthWest => "B,L", window::ResizeDirection::West => "L", }; self.window_socket .write(format!("D,{}", arg).as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), )) } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut handle = rwh_04::OrbitalHandle::empty(); handle.window = self.window_socket.fd as *mut _; rwh_04::RawWindowHandle::Orbital(handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut handle = rwh_05::OrbitalWindowHandle::empty(); handle.window = self.window_socket.fd as *mut _; rwh_05::RawWindowHandle::Orbital(handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { let handle = rwh_06::OrbitalWindowHandle::new({ let window = self.window_socket.fd as *mut _; std::ptr::NonNull::new(window).expect("orbital fd shoul never be null") }); Ok(rwh_06::RawWindowHandle::Orbital(handle)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Orbital( rwh_06::OrbitalDisplayHandle::new(), )) } #[inline] pub fn set_enabled_buttons(&self, _buttons: window::WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> window::WindowButtons { window::WindowButtons::all() } #[inline] pub fn theme(&self) -> Option { None } #[inline] pub fn has_focus(&self) -> bool { false } #[inline] pub fn set_theme(&self, _theme: Option) {} pub fn set_content_protected(&self, _protected: bool) {} } impl Drop for Window { fn drop(&mut self) { { let mut destroys = self.destroys.lock().unwrap(); destroys.push_back(self.id()); } self.wake_socket.wake().unwrap(); } } winit-0.29.15/src/platform_impl/web/async/channel.rs000064400000000000000000000063611046102023000204460ustar 00000000000000use atomic_waker::AtomicWaker; use std::future; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError}; use std::sync::{Arc, Mutex}; use std::task::Poll; // NOTE: This channel doesn't wake up when all senders or receivers are // dropped. This is acceptable as long as it's only used in `Dispatcher`, which // has it's own `Drop` behavior. pub fn channel() -> (AsyncSender, AsyncReceiver) { let (sender, receiver) = mpsc::channel(); let sender = Arc::new(Mutex::new(sender)); let inner = Arc::new(Inner { closed: AtomicBool::new(false), waker: AtomicWaker::new(), }); let sender = AsyncSender { sender, inner: Arc::clone(&inner), }; let receiver = AsyncReceiver { receiver: Rc::new(receiver), inner, }; (sender, receiver) } pub struct AsyncSender { // We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't // be accessed on the main thread, as it could block. Additionally we need // to wrap it in an `Arc` to make it clonable on the main thread without // having to block. sender: Arc>>, inner: Arc, } impl AsyncSender { pub fn send(&self, event: T) -> Result<(), SendError> { self.sender.lock().unwrap().send(event)?; self.inner.waker.wake(); Ok(()) } pub fn close(&self) { self.inner.closed.store(true, Ordering::Relaxed); self.inner.waker.wake() } } impl Clone for AsyncSender { fn clone(&self) -> Self { Self { sender: Arc::clone(&self.sender), inner: Arc::clone(&self.inner), } } } pub struct AsyncReceiver { receiver: Rc>, inner: Arc, } impl AsyncReceiver { pub async fn next(&self) -> Result { future::poll_fn(|cx| match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { self.inner.waker.register(cx.waker()); match self.receiver.try_recv() { Ok(event) => Poll::Ready(Ok(event)), Err(TryRecvError::Empty) => { if self.inner.closed.load(Ordering::Relaxed) { Poll::Ready(Err(RecvError)) } else { Poll::Pending } } Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)), } } Err(TryRecvError::Disconnected) => Poll::Ready(Err(RecvError)), }) .await } pub fn try_recv(&self) -> Result, RecvError> { match self.receiver.try_recv() { Ok(value) => Ok(Some(value)), Err(TryRecvError::Empty) => Ok(None), Err(TryRecvError::Disconnected) => Err(RecvError), } } } impl Clone for AsyncReceiver { fn clone(&self) -> Self { Self { receiver: Rc::clone(&self.receiver), inner: Arc::clone(&self.inner), } } } struct Inner { closed: AtomicBool, waker: AtomicWaker, } winit-0.29.15/src/platform_impl/web/async/dispatcher.rs000064400000000000000000000073631046102023000211670ustar 00000000000000use super::{channel, AsyncReceiver, AsyncSender, Wrapper}; use std::{ cell::Ref, sync::{Arc, Condvar, Mutex}, }; pub struct Dispatcher(Wrapper>, Closure>); struct Closure(Box); impl Dispatcher { #[track_caller] pub fn new(value: T) -> Option<(Self, DispatchRunner)> { let (sender, receiver) = channel::>(); Wrapper::new( value, |value, Closure(closure)| { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. closure(value.borrow().as_ref().unwrap()) }, { let receiver = receiver.clone(); move |value| async move { while let Ok(Closure(closure)) = receiver.next().await { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. closure(value.borrow().as_ref().unwrap()) } } }, sender, |sender, closure| { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. sender.send(closure).unwrap() }, ) .map(|wrapper| (Self(wrapper.clone()), DispatchRunner { wrapper, receiver })) } pub fn value(&self) -> Option> { self.0.value() } pub fn dispatch(&self, f: impl 'static + FnOnce(&T) + Send) { if let Some(value) = self.0.value() { f(&value) } else { self.0.send(Closure(Box::new(f))) } } pub fn queue(&self, f: impl FnOnce(&T) -> R + Send) -> R { if let Some(value) = self.0.value() { f(&value) } else { let pair = Arc::new((Mutex::new(None), Condvar::new())); let closure = Box::new({ let pair = pair.clone(); move |value: &T| { *pair.0.lock().unwrap() = Some(f(value)); pair.1.notify_one(); } }) as Box; // SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is // safe because this function won't return until `f` has finished executing. See // `Self::new()`. let closure = Closure(unsafe { std::mem::transmute(closure) }); self.0.send(closure); let mut started = pair.0.lock().unwrap(); while started.is_none() { started = pair.1.wait(started).unwrap(); } started.take().unwrap() } } } impl Drop for Dispatcher { fn drop(&mut self) { self.0.with_sender_data(|sender| sender.close()) } } pub struct DispatchRunner { wrapper: Wrapper>, Closure>, receiver: AsyncReceiver>, } impl DispatchRunner { pub fn run(&self) { while let Some(Closure(closure)) = self .receiver .try_recv() .expect("should only be closed when `Dispatcher` is dropped") { // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything // funny with it here. See `Self::queue()`. closure( &self .wrapper .value() .expect("don't call this outside the main thread"), ) } } } winit-0.29.15/src/platform_impl/web/async/mod.rs000064400000000000000000000003601046102023000176060ustar 00000000000000mod channel; mod dispatcher; mod waker; mod wrapper; use self::channel::{channel, AsyncReceiver, AsyncSender}; pub use self::dispatcher::{DispatchRunner, Dispatcher}; pub use self::waker::{Waker, WakerSpawner}; use self::wrapper::Wrapper; winit-0.29.15/src/platform_impl/web/async/waker.rs000064400000000000000000000064631046102023000201520ustar 00000000000000use super::Wrapper; use atomic_waker::AtomicWaker; use std::future; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::task::Poll; pub struct WakerSpawner(Wrapper, Sender, usize>); pub struct Waker(Wrapper, Sender, usize>); struct Handler { value: T, handler: fn(&T, usize), } #[derive(Clone)] struct Sender(Arc); impl WakerSpawner { #[track_caller] pub fn new(value: T, handler: fn(&T, usize)) -> Option { let inner = Arc::new(Inner { counter: AtomicUsize::new(0), waker: AtomicWaker::new(), closed: AtomicBool::new(false), }); let handler = Handler { value, handler }; let sender = Sender(Arc::clone(&inner)); let wrapper = Wrapper::new( handler, |handler, count| { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); (handler.handler)(&handler.value, count); }, { let inner = Arc::clone(&inner); move |handler| async move { while let Some(count) = future::poll_fn(|cx| { let count = inner.counter.swap(0, Ordering::Relaxed); if count > 0 { Poll::Ready(Some(count)) } else { inner.waker.register(cx.waker()); let count = inner.counter.swap(0, Ordering::Relaxed); if count > 0 { Poll::Ready(Some(count)) } else { if inner.closed.load(Ordering::Relaxed) { return Poll::Ready(None); } Poll::Pending } } }) .await { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); (handler.handler)(&handler.value, count); } } }, sender, |inner, _| { inner.0.counter.fetch_add(1, Ordering::Relaxed); inner.0.waker.wake(); }, )?; Some(Self(wrapper)) } pub fn waker(&self) -> Waker { Waker(self.0.clone()) } pub fn fetch(&self) -> usize { debug_assert!( self.0.is_main_thread(), "this should only be called from the main thread" ); self.0 .with_sender_data(|inner| inner.0.counter.swap(0, Ordering::Relaxed)) } } impl Drop for WakerSpawner { fn drop(&mut self) { self.0.with_sender_data(|inner| { inner.0.closed.store(true, Ordering::Relaxed); inner.0.waker.wake(); }); } } impl Waker { pub fn wake(&self) { self.0.send(1) } } impl Clone for Waker { fn clone(&self) -> Self { Self(self.0.clone()) } } struct Inner { counter: AtomicUsize, waker: AtomicWaker, closed: AtomicBool, } winit-0.29.15/src/platform_impl/web/async/wrapper.rs000064400000000000000000000076251046102023000205220ustar 00000000000000use std::cell::{Ref, RefCell}; use std::future::Future; use std::marker::PhantomData; use std::sync::Arc; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; // Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads. // `value` **must** only be accessed on the main thread. pub struct Wrapper { value: Value, handler: fn(&RefCell>, E), sender_data: S, sender_handler: fn(&S, E), } struct Value { // SAFETY: // This value must not be accessed if not on the main thread. // // - We wrap this in an `Arc` to allow it to be safely cloned without // accessing the value. // - The `RefCell` lets us mutably access in the main thread but is safe to // drop in any thread because it has no `Drop` behavior. // - The `Option` lets us safely drop `T` only in the main thread. value: Arc>>, // Prevent's `Send` or `Sync` to be automatically implemented. local: PhantomData<*const ()>, } // SAFETY: See `Self::value`. unsafe impl Send for Value {} // SAFETY: See `Self::value`. unsafe impl Sync for Value {} impl Wrapper { thread_local! { static MAIN_THREAD: bool = { #[wasm_bindgen] extern "C" { #[derive(Clone)] type Global; #[wasm_bindgen(method, getter, js_name = Window)] fn window(this: &Global) -> JsValue; } let global: Global = js_sys::global().unchecked_into(); !global.window().is_undefined() }; } #[track_caller] pub fn new>( value: V, handler: fn(&RefCell>, E), receiver: impl 'static + FnOnce(Arc>>) -> R, sender_data: S, sender_handler: fn(&S, E), ) -> Option { Self::MAIN_THREAD.with(|safe| { if !safe { panic!("only callable from inside the `Window`") } }); let value = Arc::new(RefCell::new(Some(value))); wasm_bindgen_futures::spawn_local({ let value = Arc::clone(&value); async move { receiver(Arc::clone(&value)).await; drop(value.borrow_mut().take().unwrap()); } }); Some(Self { value: Value { value, local: PhantomData, }, handler, sender_data, sender_handler, }) } pub fn send(&self, event: E) { Self::MAIN_THREAD.with(|is_main_thread| { if *is_main_thread { (self.handler)(&self.value.value, event) } else { (self.sender_handler)(&self.sender_data, event) } }) } pub fn is_main_thread(&self) -> bool { Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread) } pub fn value(&self) -> Option> { Self::MAIN_THREAD.with(|is_main_thread| { if *is_main_thread { Some(Ref::map(self.value.value.borrow(), |value| { value.as_ref().unwrap() })) } else { None } }) } pub fn with_sender_data(&self, f: impl FnOnce(&S) -> T) -> T { f(&self.sender_data) } } impl Clone for Wrapper { fn clone(&self) -> Self { Self { value: Value { value: self.value.value.clone(), local: PhantomData, }, handler: self.handler, sender_data: self.sender_data.clone(), sender_handler: self.sender_handler, } } } winit-0.29.15/src/platform_impl/web/device.rs000064400000000000000000000002651046102023000171550ustar 00000000000000#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(pub i32); impl DeviceId { pub const unsafe fn dummy() -> Self { Self(0) } } winit-0.29.15/src/platform_impl/web/error.rs000064400000000000000000000003101046102023000170360ustar 00000000000000use std::fmt; #[derive(Debug)] pub struct OsError(pub String); impl fmt::Display for OsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } winit-0.29.15/src/platform_impl/web/event_loop/mod.rs000064400000000000000000000072351046102023000206530ustar 00000000000000use std::marker::PhantomData; use std::sync::mpsc::{self, Receiver, Sender}; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::EventLoopWindowTarget as RootEventLoopWindowTarget; use super::{backend, device, window}; mod proxy; pub(crate) mod runner; mod state; mod window_target; pub use proxy::EventLoopProxy; pub use window_target::EventLoopWindowTarget; pub struct EventLoop { elw: RootEventLoopWindowTarget, user_event_sender: Sender, user_event_receiver: Receiver, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result { let (user_event_sender, user_event_receiver) = mpsc::channel(); let elw = RootEventLoopWindowTarget { p: EventLoopWindowTarget::new(), _marker: PhantomData, }; Ok(EventLoop { elw, user_event_sender, user_event_receiver, }) } pub fn run(self, mut event_handler: F) -> ! where F: FnMut(Event, &RootEventLoopWindowTarget), { let target = RootEventLoopWindowTarget { p: self.elw.p.clone(), _marker: PhantomData, }; // SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`. let handler: Box)> = Box::new(|event| { let event = match event.map_nonuser_event() { Ok(event) => event, Err(Event::UserEvent(())) => Event::UserEvent( self.user_event_receiver .try_recv() .expect("handler woken up without user event"), ), Err(_) => unreachable!(), }; event_handler(event, &target) }); // SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe // because this function will never return and all resources not cleaned up by the point we // `throw` will leak, making this actually `'static`. let handler = unsafe { std::mem::transmute(handler) }; self.elw.p.run(handler, false); // Throw an exception to break out of Rust execution and use unreachable to tell the // compiler this function won't return, giving it a return type of '!' backend::throw( "Using exceptions for control flow, don't mind me. This isn't actually an error!", ); unreachable!(); } pub fn spawn(self, mut event_handler: F) where F: 'static + FnMut(Event, &RootEventLoopWindowTarget), { let target = RootEventLoopWindowTarget { p: self.elw.p.clone(), _marker: PhantomData, }; self.elw.p.run( Box::new(move |event| { let event = match event.map_nonuser_event() { Ok(event) => event, Err(Event::UserEvent(())) => Event::UserEvent( self.user_event_receiver .try_recv() .expect("handler woken up without user event"), ), Err(_) => unreachable!(), }; event_handler(event, &target) }), true, ); } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy::new(self.elw.p.waker(), self.user_event_sender.clone()) } pub fn window_target(&self) -> &RootEventLoopWindowTarget { &self.elw } } winit-0.29.15/src/platform_impl/web/event_loop/proxy.rs000064400000000000000000000015451046102023000212530ustar 00000000000000use std::rc::Weak; use std::sync::mpsc::{SendError, Sender}; use super::runner::Execution; use crate::event_loop::EventLoopClosed; use crate::platform_impl::platform::r#async::Waker; pub struct EventLoopProxy { runner: Waker>, sender: Sender, } impl EventLoopProxy { pub fn new(runner: Waker>, sender: Sender) -> Self { Self { runner, sender } } pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.sender .send(event) .map_err(|SendError(event)| EventLoopClosed(event))?; self.runner.wake(); Ok(()) } } impl Clone for EventLoopProxy { fn clone(&self) -> Self { Self { runner: self.runner.clone(), sender: self.sender.clone(), } } } winit-0.29.15/src/platform_impl/web/event_loop/runner.rs000064400000000000000000000757331046102023000214150ustar 00000000000000use super::super::DeviceId; use super::{backend, state::State}; use crate::dpi::PhysicalSize; use crate::event::{ DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, RawKeyEvent, StartCause, WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::platform::web::PollStrategy; use crate::platform_impl::platform::backend::EventListenerHandle; use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; use std::{ cell::{Cell, RefCell}, collections::{HashSet, VecDeque}, iter, ops::Deref, rc::{Rc, Weak}, }; use wasm_bindgen::prelude::Closure; use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; use web_time::{Duration, Instant}; pub struct Shared(Rc); pub(super) type EventHandler = dyn FnMut(Event<()>); impl Clone for Shared { fn clone(&self) -> Self { Shared(self.0.clone()) } } type OnEventHandle = RefCell>>; pub struct Execution { proxy_spawner: WakerSpawner>, control_flow: Cell, poll_strategy: Cell, exit: Cell, runner: RefCell, suspended: Cell, event_loop_recreation: Cell, events: RefCell>, id: RefCell, window: web_sys::Window, document: Document, #[allow(clippy::type_complexity)] all_canvases: RefCell< Vec<( WindowId, Weak>, DispatchRunner, )>, >, redraw_pending: RefCell>, destroy_pending: RefCell>, page_transition_event_handle: RefCell>, device_events: Cell, on_mouse_move: OnEventHandle, on_wheel: OnEventHandle, on_mouse_press: OnEventHandle, on_mouse_release: OnEventHandle, on_key_press: OnEventHandle, on_key_release: OnEventHandle, on_visibility_change: OnEventHandle, on_touch_end: OnEventHandle, } enum RunnerEnum { /// The `EventLoop` is created but not being run. Pending, /// The `EventLoop` is being run. Running(Runner), /// The `EventLoop` is exited after being started with `EventLoop::run`. Since /// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain /// that this event loop will never be run again. Destroyed, } impl RunnerEnum { fn maybe_runner(&self) -> Option<&Runner> { match self { RunnerEnum::Running(runner) => Some(runner), _ => None, } } } struct Runner { state: State, event_handler: Box, } impl Runner { pub fn new(event_handler: Box) -> Self { Runner { state: State::Init, event_handler, } } /// Returns the corresponding `StartCause` for the current `state`, or `None` /// when in `Exit` state. fn maybe_start_cause(&self) -> Option { Some(match self.state { State::Init => StartCause::Init, State::Poll { .. } => StartCause::Poll, State::Wait { start } => StartCause::WaitCancelled { start, requested_resume: None, }, State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { start, requested_resume: Some(end), }, State::Exit => return None, }) } fn handle_single_event(&mut self, runner: &Shared, event: impl Into) { match event.into() { EventWrapper::Event(event) => (self.event_handler)(event), EventWrapper::ScaleChange { canvas, size, scale, } => { if let Some(canvas) = canvas.upgrade() { canvas.borrow().handle_scale_change( runner, |event| (self.event_handler)(event), size, scale, ) } } } } } impl Shared { pub fn new() -> Self { #[allow(clippy::disallowed_methods)] let window = web_sys::window().expect("only callable from inside the `Window`"); #[allow(clippy::disallowed_methods)] let document = window.document().expect("Failed to obtain document"); Shared(Rc::::new_cyclic(|weak| { let proxy_spawner = WakerSpawner::new(weak.clone(), |runner, count| { if let Some(runner) = runner.upgrade() { Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count)) } }) .expect("`EventLoop` has to be created in the main thread"); Execution { proxy_spawner, control_flow: Cell::new(ControlFlow::default()), poll_strategy: Cell::new(PollStrategy::default()), exit: Cell::new(false), runner: RefCell::new(RunnerEnum::Pending), suspended: Cell::new(false), event_loop_recreation: Cell::new(false), events: RefCell::new(VecDeque::new()), window, document, id: RefCell::new(0), all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), destroy_pending: RefCell::new(VecDeque::new()), page_transition_event_handle: RefCell::new(None), device_events: Cell::default(), on_mouse_move: RefCell::new(None), on_wheel: RefCell::new(None), on_mouse_press: RefCell::new(None), on_mouse_release: RefCell::new(None), on_key_press: RefCell::new(None), on_key_release: RefCell::new(None), on_visibility_change: RefCell::new(None), on_touch_end: RefCell::new(None), } })) } pub fn window(&self) -> &web_sys::Window { &self.0.window } pub fn document(&self) -> &Document { &self.0.document } pub fn add_canvas( &self, id: WindowId, canvas: Weak>, runner: DispatchRunner, ) { self.0.all_canvases.borrow_mut().push((id, canvas, runner)); } pub fn notify_destroy_window(&self, id: WindowId) { self.0.destroy_pending.borrow_mut().push_back(id); } // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootEventLoopWindowTarget reference pub fn set_listener(&self, event_handler: Box) { { let mut runner = self.0.runner.borrow_mut(); assert!(matches!(*runner, RunnerEnum::Pending)); *runner = RunnerEnum::Running(Runner::new(event_handler)); } self.init(); *self.0.page_transition_event_handle.borrow_mut() = Some(backend::on_page_transition( self.window().clone(), { let runner = self.clone(); move |event: PageTransitionEvent| { if event.persisted() { runner.0.suspended.set(false); runner.send_event(Event::Resumed); } } }, { let runner = self.clone(); move |event: PageTransitionEvent| { runner.0.suspended.set(true); if event.persisted() { runner.send_event(Event::Suspended); } else { runner.handle_unload(); } } }, )); let runner = self.clone(); let window = self.window().clone(); *self.0.on_mouse_move.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointermove", Closure::new(move |event: PointerEvent| { if !runner.device_events() { return; } let pointer_type = event.pointer_type(); if pointer_type != "mouse" { return; } // chorded button event let device_id = RootDeviceId(DeviceId(event.pointer_id())); if let Some(button) = backend::event::mouse_button(&event) { debug_assert_eq!( pointer_type, "mouse", "expect pointer type of a chorded button event to be a mouse" ); let state = if backend::event::mouse_buttons(&event).contains(button.into()) { ElementState::Pressed } else { ElementState::Released }; runner.send_event(Event::DeviceEvent { device_id, event: DeviceEvent::Button { button: button.to_id(), state, }, }); return; } // pointer move event let mut delta = backend::event::MouseDelta::init(&window, &event); runner.send_events(backend::event::pointer_move_event(event).flat_map(|event| { let delta = delta .delta(&event) .to_physical(backend::scale_factor(&window)); let x_motion = (delta.x != 0.0).then_some(Event::DeviceEvent { device_id, event: DeviceEvent::Motion { axis: 0, value: delta.x, }, }); let y_motion = (delta.y != 0.0).then_some(Event::DeviceEvent { device_id, event: DeviceEvent::Motion { axis: 1, value: delta.y, }, }); x_motion .into_iter() .chain(y_motion) .chain(iter::once(Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta: (delta.x, delta.y), }, })) })); }), )); let runner = self.clone(); let window = self.window().clone(); *self.0.on_wheel.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "wheel", Closure::new(move |event: WheelEvent| { if !runner.device_events() { return; } if let Some(delta) = backend::event::mouse_scroll_delta(&window, &event) { runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(0)), event: DeviceEvent::MouseWheel { delta }, }); } }), )); let runner = self.clone(); *self.0.on_mouse_press.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointerdown", Closure::new(move |event: PointerEvent| { runner.transient_activation(); if !runner.device_events() { return; } if event.pointer_type() != "mouse" { return; } let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { button: button.to_id(), state: ElementState::Pressed, }, }); }), )); let runner = self.clone(); *self.0.on_mouse_release.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "pointerup", Closure::new(move |event: PointerEvent| { runner.transient_activation(); if !runner.device_events() { return; } if event.pointer_type() != "mouse" { return; } let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), event: DeviceEvent::Button { button: button.to_id(), state: ElementState::Released, }, }); }), )); let runner = self.clone(); *self.0.on_key_press.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "keydown", Closure::new(move |event: KeyboardEvent| { runner.transient_activation(); if !runner.device_events() { return; } runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(unsafe { DeviceId::dummy() }), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Pressed, }), }); }), )); let runner = self.clone(); *self.0.on_key_release.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "keyup", Closure::new(move |event: KeyboardEvent| { if !runner.device_events() { return; } runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(unsafe { DeviceId::dummy() }), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Released, }), }); }), )); let runner = self.clone(); *self.0.on_visibility_change.borrow_mut() = Some(EventListenerHandle::new( // Safari <14 doesn't support the `visibilitychange` event on `Window`. self.document().clone(), "visibilitychange", Closure::new(move |_| { if !runner.0.suspended.get() { for (id, canvas, _) in &*runner.0.all_canvases.borrow() { if let Some(canvas) = canvas.upgrade() { let is_visible = backend::is_visible(runner.document()); // only fire if: // - not visible and intersects // - not visible and we don't know if it intersects yet // - visible and intersects if let (false, Some(true) | None) | (true, Some(true)) = (is_visible, canvas.borrow().is_intersecting) { runner.send_event(Event::WindowEvent { window_id: *id, event: WindowEvent::Occluded(!is_visible), }); } } } } }), )); let runner = self.clone(); *self.0.on_touch_end.borrow_mut() = Some(EventListenerHandle::new( self.window().clone(), "touchend", Closure::new(move |_| { runner.transient_activation(); }), )); } // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { let mut id = self.0.id.borrow_mut(); *id += 1; *id } pub fn request_redraw(&self, id: WindowId) { self.0.redraw_pending.borrow_mut().insert(id); self.send_events::(iter::empty()); } pub fn init(&self) { // NB: For consistency all platforms must emit a 'resumed' event even though web // applications don't themselves have a formal suspend/resume lifecycle. self.run_until_cleared([Event::NewEvents(StartCause::Init), Event::Resumed].into_iter()); } // Run the polling logic for the Poll ControlFlow, which involves clearing the queue pub fn poll(&self) { let start_cause = Event::NewEvents(StartCause::Poll); self.run_until_cleared(iter::once(start_cause)); } // Run the logic for waking from a WaitUntil, which involves clearing the queue // Generally there shouldn't be events built up when this is called pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) { let start_cause = Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume, }); self.run_until_cleared(iter::once(start_cause)); } // Add an event to the event loop runner, from the user or an event handler // // It will determine if the event should be immediately sent to the user or buffered for later pub(crate) fn send_event>(&self, event: E) { self.send_events(iter::once(event)); } // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later pub(crate) fn send_events>(&self, events: impl IntoIterator) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(ref runner)) => { // If we're currently polling, queue this and wait for the poll() method to be called if let State::Poll { .. } = runner.state { process_immediately = false; } } Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; } // Some other code is mutating the runner, which most likely means // the event loop is running and busy. So we queue this event for // it to be processed later. Err(_) => { process_immediately = false; } // This is unreachable since `self.is_closed() == true`. Ok(RunnerEnum::Destroyed) => unreachable!(), } if !process_immediately { // Queue these events to look at later self.0 .events .borrow_mut() .extend(events.into_iter().map(Into::into)); return; } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| { unreachable!("The runner cannot process events when it is not attached") }) .maybe_start_cause() { Some(c) => c, // If we're in the exit state, don't do event processing None => return, }; // Take the start event, then the events provided to this function, and run an iteration of // the event loop let start_event = Event::NewEvents(start_cause); let events = iter::once(EventWrapper::from(start_event)).chain(events.into_iter().map(Into::into)); self.run_until_cleared(events); } // Process the destroy-pending windows. This should only be called from // `run_until_cleared`, somewhere between emitting `NewEvents` and `AboutToWait`. fn process_destroy_pending_windows(&self) { while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() { self.0 .all_canvases .borrow_mut() .retain(|&(item_id, _, _)| item_id != id); self.handle_event(Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::Destroyed, }); self.0.redraw_pending.borrow_mut().remove(&id); } } // Given the set of new events, run the event loop until the main events and redraw events are // cleared // // This will also process any events that have been queued or that are queued during processing fn run_until_cleared>(&self, events: impl Iterator) { for event in events { self.handle_event(event.into()); } self.process_destroy_pending_windows(); // Collect all of the redraw events to avoid double-locking the RefCell let redraw_events: Vec = self.0.redraw_pending.borrow_mut().drain().collect(); for window_id in redraw_events { self.handle_event(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested, }); } self.handle_event(Event::AboutToWait); self.apply_control_flow(); // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { self.handle_loop_destroyed(); } } fn handle_unload(&self) { self.exit(); self.apply_control_flow(); // We don't call `handle_loop_destroyed` here because we don't need to // perform cleanup when the web browser is going to destroy the page. self.handle_event(Event::LoopExiting); } // handle_event takes in events and either queues them or applies a callback // // It should only ever be called from `run_until_cleared`. fn handle_event(&self, event: impl Into) { if self.is_closed() { self.exit(); } match *self.0.runner.borrow_mut() { RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(self, event); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event.into()), // If the Runner has been destroyed, there is nothing to do. RunnerEnum::Destroyed => return, } let is_closed = self.exiting(); // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().maybe_runner().is_some() { // Pre-fetch window commands to avoid having to wait until the next event loop cycle // and potentially block other threads in the meantime. for (_, window, runner) in self.0.all_canvases.borrow().iter() { if let Some(window) = window.upgrade() { runner.run(); drop(window) } } // Take an event out of the queue and handle it // Make sure not to let the borrow_mut live during the next handle_event let event = { let mut events = self.0.events.borrow_mut(); // Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle. events.extend( iter::repeat(Event::UserEvent(())) .take(self.0.proxy_spawner.fetch()) .map(EventWrapper::from), ); events.pop_front() }; if let Some(event) = event { self.handle_event(event); } } } // Apply the new ControlFlow that has been selected by the user // Start any necessary timeouts etc fn apply_control_flow(&self) { let new_state = if self.exiting() { State::Exit } else { match self.control_flow() { ControlFlow::Poll => { let cloned = self.clone(); State::Poll { request: backend::Schedule::new( self.poll_strategy(), self.window(), move || cloned.poll(), ), } } ControlFlow::Wait => State::Wait { start: Instant::now(), }, ControlFlow::WaitUntil(end) => { let start = Instant::now(); let delay = if end <= start { Duration::from_millis(0) } else { end - start }; let cloned = self.clone(); State::WaitUntil { start, end, timeout: backend::Schedule::new_with_duration( self.window(), move || cloned.resume_time_reached(start, end), delay, ), } } } }; if let RunnerEnum::Running(ref mut runner) = *self.0.runner.borrow_mut() { runner.state = new_state; } } fn handle_loop_destroyed(&self) { self.handle_event(Event::LoopExiting); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); *self.0.page_transition_event_handle.borrow_mut() = None; *self.0.on_mouse_move.borrow_mut() = None; *self.0.on_wheel.borrow_mut() = None; *self.0.on_mouse_press.borrow_mut() = None; *self.0.on_mouse_release.borrow_mut() = None; *self.0.on_key_press.borrow_mut() = None; *self.0.on_key_release.borrow_mut() = None; *self.0.on_visibility_change.borrow_mut() = None; // Dropping the `Runner` drops the event handler closure, which will in // turn drop all `Window`s moved into the closure. *self.0.runner.borrow_mut() = RunnerEnum::Destroyed; for (_, canvas, _) in all_canvases { // In case any remaining `Window`s are still not dropped, we will need // to explicitly remove the event handlers associated with their canvases. if let Some(canvas) = canvas.upgrade() { let mut canvas = canvas.borrow_mut(); canvas.remove_listeners(); } } // At this point, the `self.0` `Rc` should only be strongly referenced // by the following: // * `self`, i.e. the item which triggered this event loop wakeup, which // is usually a `wasm-bindgen` `Closure`, which will be dropped after // returning to the JS glue code. // * The `EventLoopWindowTarget` leaked inside `EventLoop::run` due to the // JS exception thrown at the end. // * For each undropped `Window`: // * The `register_redraw_request` closure. // * The `destroy_fn` closure. if self.0.event_loop_recreation.get() { crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation(); } } // Check if the event loop is currently closed fn is_closed(&self) -> bool { match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(runner)) => runner.state.exiting(), // The event loop is not closed since it is not initialized. Ok(RunnerEnum::Pending) => false, // The event loop is closed since it has been destroyed. Ok(RunnerEnum::Destroyed) => true, // Some other code is mutating the runner, which most likely means // the event loop is running and busy. Err(_) => false, } } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.0.device_events.set(allowed) } fn device_events(&self) -> bool { match self.0.device_events.get() { DeviceEvents::Always => true, DeviceEvents::WhenFocused => { self.0.all_canvases.borrow().iter().any(|(_, canvas, _)| { if let Some(canvas) = canvas.upgrade() { canvas.borrow().has_focus.get() } else { false } }) } DeviceEvents::Never => false, } } fn transient_activation(&self) { self.0 .all_canvases .borrow() .iter() .for_each(|(_, canvas, _)| { if let Some(canvas) = canvas.upgrade() { canvas.borrow().transient_activation(); } }); } pub fn event_loop_recreation(&self, allow: bool) { self.0.event_loop_recreation.set(allow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.0.control_flow.get() } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.0.control_flow.set(control_flow) } pub(crate) fn exit(&self) { self.0.exit.set(true) } pub(crate) fn exiting(&self) -> bool { self.0.exit.get() } pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) { self.0.poll_strategy.set(strategy) } pub(crate) fn poll_strategy(&self) -> PollStrategy { self.0.poll_strategy.get() } pub(crate) fn waker(&self) -> Waker> { self.0.proxy_spawner.waker() } } pub(crate) enum EventWrapper { Event(Event<()>), ScaleChange { canvas: Weak>, size: PhysicalSize, scale: f64, }, } impl From> for EventWrapper { fn from(value: Event<()>) -> Self { Self::Event(value) } } winit-0.29.15/src/platform_impl/web/event_loop/state.rs000064400000000000000000000006141046102023000212060ustar 00000000000000use super::backend; use web_time::Instant; #[derive(Debug)] pub enum State { Init, WaitUntil { timeout: backend::Schedule, start: Instant, end: Instant, }, Wait { start: Instant, }, Poll { request: backend::Schedule, }, Exit, } impl State { pub fn exiting(&self) -> bool { matches!(self, State::Exit) } } winit-0.29.15/src/platform_impl/web/event_loop/window_target.rs000064400000000000000000000663511046102023000227550ustar 00000000000000use std::cell::{Cell, RefCell}; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; use std::iter; use std::marker::PhantomData; use std::rc::{Rc, Weak}; use web_sys::Element; use super::runner::{EventWrapper, Execution}; use super::{ super::{monitor::MonitorHandle, KeyEventExtra}, backend, device::DeviceId, runner, window::WindowId, }; use crate::event::{ DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; use crate::platform::web::PollStrategy; use crate::platform_impl::platform::r#async::Waker; use crate::window::{Theme, WindowId as RootWindowId}; #[derive(Default)] struct ModifiersShared(Rc>); impl ModifiersShared { fn set(&self, new: ModifiersState) { self.0.set(new) } fn get(&self) -> ModifiersState { self.0.get() } } impl Clone for ModifiersShared { fn clone(&self) -> Self { Self(Rc::clone(&self.0)) } } pub struct EventLoopWindowTarget { pub(crate) runner: runner::Shared, modifiers: ModifiersShared, _marker: PhantomData, } impl Clone for EventLoopWindowTarget { fn clone(&self) -> Self { Self { runner: self.runner.clone(), modifiers: self.modifiers.clone(), _marker: PhantomData, } } } impl EventLoopWindowTarget { pub fn new() -> Self { Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default(), _marker: PhantomData, } } pub fn run(&self, event_handler: Box, event_loop_recreation: bool) { self.runner.event_loop_recreation(event_loop_recreation); self.runner.set_listener(event_handler); } pub fn generate_id(&self) -> WindowId { WindowId(self.runner.generate_id()) } pub fn register( &self, canvas: &Rc>, id: WindowId, prevent_default: bool, ) { let canvas_clone = canvas.clone(); let mut canvas = canvas.borrow_mut(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); canvas.on_touch_start(prevent_default); let runner = self.runner.clone(); let has_focus = canvas.has_focus.clone(); let modifiers = self.modifiers.clone(); canvas.on_blur(move || { has_focus.set(false); let clear_modifiers = (!modifiers.get().is_empty()).then(|| { modifiers.set(ModifiersState::empty()); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()), } }); runner.send_events( clear_modifiers .into_iter() .chain(iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(false), })), ); }); let runner = self.runner.clone(); let has_focus = canvas.has_focus.clone(); canvas.on_focus(move || { if !has_focus.replace(true) { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(true), }); } }); // It is possible that at this point the canvas has // been focused before the callback can be called. let focused = canvas .document() .active_element() .filter(|element| { let canvas: &Element = canvas.raw(); element == canvas }) .is_some(); if focused { canvas.has_focus.set(true); self.runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Focused(true), }) } let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_keyboard_press( move |physical_key, logical_key, text, location, repeat, active_modifiers| { let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(unsafe { DeviceId::dummy() }); runner.send_events( iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::KeyboardInput { device_id, event: KeyEvent { physical_key, logical_key, text, location, state: ElementState::Pressed, repeat, platform_specific: KeyEventExtra, }, is_synthetic: false, }, }) .chain(modifiers_changed), ); }, prevent_default, ); let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_keyboard_release( move |physical_key, logical_key, text, location, repeat, active_modifiers| { let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(unsafe { DeviceId::dummy() }); runner.send_events( iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::KeyboardInput { device_id, event: KeyEvent { physical_key, logical_key, text, location, state: ElementState::Released, repeat, platform_specific: KeyEventExtra, }, is_synthetic: false, }, }) .chain(modifiers_changed), ) }, prevent_default, ); let has_focus = canvas.has_focus.clone(); canvas.on_cursor_leave({ let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id| { let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let pointer = pointer_id.map(|pointer_id| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorLeft { device_id: RootDeviceId(DeviceId(pointer_id)), }, }); if focus.is_some() || pointer.is_some() { runner.send_events(focus.into_iter().chain(pointer)) } } }); canvas.on_cursor_enter({ let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id| { let focus = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let pointer = pointer_id.map(|pointer_id| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorEntered { device_id: RootDeviceId(DeviceId(pointer_id)), }, }); if focus.is_some() || pointer.is_some() { runner.send_events(focus.into_iter().chain(pointer)) } } }); canvas.on_cursor_move( { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers| { if has_focus.get() && modifiers.get() != active_modifiers { modifiers.set(active_modifiers); runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), }) } } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, events| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(events.flat_map(|position| { let device_id = RootDeviceId(DeviceId(pointer_id)); iter::once(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position, }, }) }))); } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, events| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(events.map( |(location, force)| Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Moved, force: Some(force), location, }), }, ))); } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position: crate::dpi::PhysicalPosition, buttons, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id = RootDeviceId(DeviceId(pointer_id)); let state = if buttons.contains(button.into()) { ElementState::Pressed } else { ElementState::Released }; // A chorded button event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position, }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state, button, }, }, ])); } }, prevent_default, ); canvas.on_mouse_press( { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers| { if modifiers.get() != active_modifiers { modifiers.set(active_modifiers); runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), }) } } }, { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position, button| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id: RootDeviceId = RootDeviceId(DeviceId(pointer_id)); // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position, }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state: ElementState::Pressed, button, }, }, ])); } }, { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, location, force| { let modifiers = (modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Started, force: Some(force), location, }), }, ))) } }, prevent_default, ); canvas.on_mouse_release( { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers| { if has_focus.get() && modifiers.get() != active_modifiers { modifiers.set(active_modifiers); runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), }); } } }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, pointer_id, position, button| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); let device_id: RootDeviceId = RootDeviceId(DeviceId(pointer_id)); // A mouse up event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. runner.send_events(modifiers.into_iter().chain([ Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id, position, }, }, Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id, state: ElementState::Released, button, }, }, ])); } }, { let runner_touch = self.runner.clone(); let has_focus = has_focus.clone(); let modifiers = self.modifiers.clone(); move |active_modifiers, device_id, location, force| { let modifiers = (has_focus.get() && modifiers.get() != active_modifiers).then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner_touch.send_events(modifiers.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Ended, force: Some(force), location, }), }, ))); } }, ); let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); canvas.on_mouse_wheel( move |pointer_id, delta, active_modifiers| { let modifiers_changed = (has_focus.get() && modifiers.get() != active_modifiers) .then(|| { modifiers.set(active_modifiers); Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ModifiersChanged(active_modifiers.into()), } }); runner.send_events(modifiers_changed.into_iter().chain(iter::once( Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseWheel { device_id: RootDeviceId(DeviceId(pointer_id)), delta, phase: TouchPhase::Moved, }, }, ))); }, prevent_default, ); let runner = self.runner.clone(); canvas.on_touch_cancel(move |device_id, location, force| { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Touch(Touch { id: device_id as u64, device_id: RootDeviceId(DeviceId(device_id)), phase: TouchPhase::Cancelled, force: Some(force), location, }), }); }); let runner = self.runner.clone(); canvas.on_dark_mode(move |is_dark_mode| { let theme = if is_dark_mode { Theme::Dark } else { Theme::Light }; runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::ThemeChanged(theme), }); }); canvas.on_resize_scale( { let runner = self.runner.clone(); let canvas = canvas_clone.clone(); move |size, scale| { runner.send_event(EventWrapper::ScaleChange { canvas: Rc::downgrade(&canvas), size, scale, }) } }, { let runner = self.runner.clone(); let canvas = canvas_clone.clone(); move |new_size| { let canvas = canvas.borrow(); canvas.set_current_size(new_size); if canvas.old_size() != new_size { canvas.set_old_size(new_size); runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Resized(new_size), }); runner.request_redraw(RootWindowId(id)); } } }, ); let runner = self.runner.clone(); canvas.on_intersection(move |is_intersecting| { // only fire if visible while skipping the first event if it's intersecting if backend::is_visible(runner.document()) && !(is_intersecting && canvas_clone.borrow().is_intersecting.is_none()) { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Occluded(!is_intersecting), }); } canvas_clone.borrow_mut().is_intersecting = Some(is_intersecting); }); let runner = self.runner.clone(); canvas.on_animation_frame(move || runner.request_redraw(RootWindowId(id))); canvas.on_touch_end(); canvas.on_context_menu(prevent_default); } pub fn available_monitors(&self) -> VecDequeIter { VecDeque::new().into_iter() } pub fn primary_monitor(&self) -> Option { None } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Web( rwh_06::WebDisplayHandle::new(), )) } pub fn listen_device_events(&self, allowed: DeviceEvents) { self.runner.listen_device_events(allowed) } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.runner.control_flow() } pub(crate) fn exit(&self) { self.runner.exit() } pub(crate) fn exiting(&self) -> bool { self.runner.exiting() } pub(crate) fn set_poll_strategy(&self, strategy: PollStrategy) { self.runner.set_poll_strategy(strategy) } pub(crate) fn poll_strategy(&self) -> PollStrategy { self.runner.poll_strategy() } pub(crate) fn waker(&self) -> Waker> { self.runner.waker() } } winit-0.29.15/src/platform_impl/web/keyboard.rs000064400000000000000000000567341046102023000175320ustar 00000000000000use smol_str::SmolStr; use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub(crate) struct KeyEventExtra; impl Key { pub(crate) fn from_key_attribute_value(kav: &str) -> Self { Key::Named(match kav { "Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))), "Dead" => return Key::Dead(None), "Alt" => NamedKey::Alt, "AltGraph" => NamedKey::AltGraph, "CapsLock" => NamedKey::CapsLock, "Control" => NamedKey::Control, "Fn" => NamedKey::Fn, "FnLock" => NamedKey::FnLock, "NumLock" => NamedKey::NumLock, "ScrollLock" => NamedKey::ScrollLock, "Shift" => NamedKey::Shift, "Symbol" => NamedKey::Symbol, "SymbolLock" => NamedKey::SymbolLock, "Hyper" => NamedKey::Hyper, "Meta" => NamedKey::Super, "Enter" => NamedKey::Enter, "Tab" => NamedKey::Tab, " " => NamedKey::Space, "ArrowDown" => NamedKey::ArrowDown, "ArrowLeft" => NamedKey::ArrowLeft, "ArrowRight" => NamedKey::ArrowRight, "ArrowUp" => NamedKey::ArrowUp, "End" => NamedKey::End, "Home" => NamedKey::Home, "PageDown" => NamedKey::PageDown, "PageUp" => NamedKey::PageUp, "Backspace" => NamedKey::Backspace, "Clear" => NamedKey::Clear, "Copy" => NamedKey::Copy, "CrSel" => NamedKey::CrSel, "Cut" => NamedKey::Cut, "Delete" => NamedKey::Delete, "EraseEof" => NamedKey::EraseEof, "ExSel" => NamedKey::ExSel, "Insert" => NamedKey::Insert, "Paste" => NamedKey::Paste, "Redo" => NamedKey::Redo, "Undo" => NamedKey::Undo, "Accept" => NamedKey::Accept, "Again" => NamedKey::Again, "Attn" => NamedKey::Attn, "Cancel" => NamedKey::Cancel, "ContextMenu" => NamedKey::ContextMenu, "Escape" => NamedKey::Escape, "Execute" => NamedKey::Execute, "Find" => NamedKey::Find, "Help" => NamedKey::Help, "Pause" => NamedKey::Pause, "Play" => NamedKey::Play, "Props" => NamedKey::Props, "Select" => NamedKey::Select, "ZoomIn" => NamedKey::ZoomIn, "ZoomOut" => NamedKey::ZoomOut, "BrightnessDown" => NamedKey::BrightnessDown, "BrightnessUp" => NamedKey::BrightnessUp, "Eject" => NamedKey::Eject, "LogOff" => NamedKey::LogOff, "Power" => NamedKey::Power, "PowerOff" => NamedKey::PowerOff, "PrintScreen" => NamedKey::PrintScreen, "Hibernate" => NamedKey::Hibernate, "Standby" => NamedKey::Standby, "WakeUp" => NamedKey::WakeUp, "AllCandidates" => NamedKey::AllCandidates, "Alphanumeric" => NamedKey::Alphanumeric, "CodeInput" => NamedKey::CodeInput, "Compose" => NamedKey::Compose, "Convert" => NamedKey::Convert, "FinalMode" => NamedKey::FinalMode, "GroupFirst" => NamedKey::GroupFirst, "GroupLast" => NamedKey::GroupLast, "GroupNext" => NamedKey::GroupNext, "GroupPrevious" => NamedKey::GroupPrevious, "ModeChange" => NamedKey::ModeChange, "NextCandidate" => NamedKey::NextCandidate, "NonConvert" => NamedKey::NonConvert, "PreviousCandidate" => NamedKey::PreviousCandidate, "Process" => NamedKey::Process, "SingleCandidate" => NamedKey::SingleCandidate, "HangulMode" => NamedKey::HangulMode, "HanjaMode" => NamedKey::HanjaMode, "JunjaMode" => NamedKey::JunjaMode, "Eisu" => NamedKey::Eisu, "Hankaku" => NamedKey::Hankaku, "Hiragana" => NamedKey::Hiragana, "HiraganaKatakana" => NamedKey::HiraganaKatakana, "KanaMode" => NamedKey::KanaMode, "KanjiMode" => NamedKey::KanjiMode, "Katakana" => NamedKey::Katakana, "Romaji" => NamedKey::Romaji, "Zenkaku" => NamedKey::Zenkaku, "ZenkakuHankaku" => NamedKey::ZenkakuHankaku, "Soft1" => NamedKey::Soft1, "Soft2" => NamedKey::Soft2, "Soft3" => NamedKey::Soft3, "Soft4" => NamedKey::Soft4, "ChannelDown" => NamedKey::ChannelDown, "ChannelUp" => NamedKey::ChannelUp, "Close" => NamedKey::Close, "MailForward" => NamedKey::MailForward, "MailReply" => NamedKey::MailReply, "MailSend" => NamedKey::MailSend, "MediaClose" => NamedKey::MediaClose, "MediaFastForward" => NamedKey::MediaFastForward, "MediaPause" => NamedKey::MediaPause, "MediaPlay" => NamedKey::MediaPlay, "MediaPlayPause" => NamedKey::MediaPlayPause, "MediaRecord" => NamedKey::MediaRecord, "MediaRewind" => NamedKey::MediaRewind, "MediaStop" => NamedKey::MediaStop, "MediaTrackNext" => NamedKey::MediaTrackNext, "MediaTrackPrevious" => NamedKey::MediaTrackPrevious, "New" => NamedKey::New, "Open" => NamedKey::Open, "Print" => NamedKey::Print, "Save" => NamedKey::Save, "SpellCheck" => NamedKey::SpellCheck, "Key11" => NamedKey::Key11, "Key12" => NamedKey::Key12, "AudioBalanceLeft" => NamedKey::AudioBalanceLeft, "AudioBalanceRight" => NamedKey::AudioBalanceRight, "AudioBassBoostDown" => NamedKey::AudioBassBoostDown, "AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle, "AudioBassBoostUp" => NamedKey::AudioBassBoostUp, "AudioFaderFront" => NamedKey::AudioFaderFront, "AudioFaderRear" => NamedKey::AudioFaderRear, "AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext, "AudioTrebleDown" => NamedKey::AudioTrebleDown, "AudioTrebleUp" => NamedKey::AudioTrebleUp, "AudioVolumeDown" => NamedKey::AudioVolumeDown, "AudioVolumeUp" => NamedKey::AudioVolumeUp, "AudioVolumeMute" => NamedKey::AudioVolumeMute, "MicrophoneToggle" => NamedKey::MicrophoneToggle, "MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown, "MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp, "MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute, "SpeechCorrectionList" => NamedKey::SpeechCorrectionList, "SpeechInputToggle" => NamedKey::SpeechInputToggle, "LaunchApplication1" => NamedKey::LaunchApplication1, "LaunchApplication2" => NamedKey::LaunchApplication2, "LaunchCalendar" => NamedKey::LaunchCalendar, "LaunchContacts" => NamedKey::LaunchContacts, "LaunchMail" => NamedKey::LaunchMail, "LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer, "LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer, "LaunchPhone" => NamedKey::LaunchPhone, "LaunchScreenSaver" => NamedKey::LaunchScreenSaver, "LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet, "LaunchWebBrowser" => NamedKey::LaunchWebBrowser, "LaunchWebCam" => NamedKey::LaunchWebCam, "LaunchWordProcessor" => NamedKey::LaunchWordProcessor, "BrowserBack" => NamedKey::BrowserBack, "BrowserFavorites" => NamedKey::BrowserFavorites, "BrowserForward" => NamedKey::BrowserForward, "BrowserHome" => NamedKey::BrowserHome, "BrowserRefresh" => NamedKey::BrowserRefresh, "BrowserSearch" => NamedKey::BrowserSearch, "BrowserStop" => NamedKey::BrowserStop, "AppSwitch" => NamedKey::AppSwitch, "Call" => NamedKey::Call, "Camera" => NamedKey::Camera, "CameraFocus" => NamedKey::CameraFocus, "EndCall" => NamedKey::EndCall, "GoBack" => NamedKey::GoBack, "GoHome" => NamedKey::GoHome, "HeadsetHook" => NamedKey::HeadsetHook, "LastNumberRedial" => NamedKey::LastNumberRedial, "Notification" => NamedKey::Notification, "MannerMode" => NamedKey::MannerMode, "VoiceDial" => NamedKey::VoiceDial, "TV" => NamedKey::TV, "TV3DMode" => NamedKey::TV3DMode, "TVAntennaCable" => NamedKey::TVAntennaCable, "TVAudioDescription" => NamedKey::TVAudioDescription, "TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown, "TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp, "TVContentsMenu" => NamedKey::TVContentsMenu, "TVDataService" => NamedKey::TVDataService, "TVInput" => NamedKey::TVInput, "TVInputComponent1" => NamedKey::TVInputComponent1, "TVInputComponent2" => NamedKey::TVInputComponent2, "TVInputComposite1" => NamedKey::TVInputComposite1, "TVInputComposite2" => NamedKey::TVInputComposite2, "TVInputHDMI1" => NamedKey::TVInputHDMI1, "TVInputHDMI2" => NamedKey::TVInputHDMI2, "TVInputHDMI3" => NamedKey::TVInputHDMI3, "TVInputHDMI4" => NamedKey::TVInputHDMI4, "TVInputVGA1" => NamedKey::TVInputVGA1, "TVMediaContext" => NamedKey::TVMediaContext, "TVNetwork" => NamedKey::TVNetwork, "TVNumberEntry" => NamedKey::TVNumberEntry, "TVPower" => NamedKey::TVPower, "TVRadioService" => NamedKey::TVRadioService, "TVSatellite" => NamedKey::TVSatellite, "TVSatelliteBS" => NamedKey::TVSatelliteBS, "TVSatelliteCS" => NamedKey::TVSatelliteCS, "TVSatelliteToggle" => NamedKey::TVSatelliteToggle, "TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog, "TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital, "TVTimer" => NamedKey::TVTimer, "AVRInput" => NamedKey::AVRInput, "AVRPower" => NamedKey::AVRPower, "ColorF0Red" => NamedKey::ColorF0Red, "ColorF1Green" => NamedKey::ColorF1Green, "ColorF2Yellow" => NamedKey::ColorF2Yellow, "ColorF3Blue" => NamedKey::ColorF3Blue, "ColorF4Grey" => NamedKey::ColorF4Grey, "ColorF5Brown" => NamedKey::ColorF5Brown, "ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle, "Dimmer" => NamedKey::Dimmer, "DisplaySwap" => NamedKey::DisplaySwap, "DVR" => NamedKey::DVR, "Exit" => NamedKey::Exit, "FavoriteClear0" => NamedKey::FavoriteClear0, "FavoriteClear1" => NamedKey::FavoriteClear1, "FavoriteClear2" => NamedKey::FavoriteClear2, "FavoriteClear3" => NamedKey::FavoriteClear3, "FavoriteRecall0" => NamedKey::FavoriteRecall0, "FavoriteRecall1" => NamedKey::FavoriteRecall1, "FavoriteRecall2" => NamedKey::FavoriteRecall2, "FavoriteRecall3" => NamedKey::FavoriteRecall3, "FavoriteStore0" => NamedKey::FavoriteStore0, "FavoriteStore1" => NamedKey::FavoriteStore1, "FavoriteStore2" => NamedKey::FavoriteStore2, "FavoriteStore3" => NamedKey::FavoriteStore3, "Guide" => NamedKey::Guide, "GuideNextDay" => NamedKey::GuideNextDay, "GuidePreviousDay" => NamedKey::GuidePreviousDay, "Info" => NamedKey::Info, "InstantReplay" => NamedKey::InstantReplay, "Link" => NamedKey::Link, "ListProgram" => NamedKey::ListProgram, "LiveContent" => NamedKey::LiveContent, "Lock" => NamedKey::Lock, "MediaApps" => NamedKey::MediaApps, "MediaAudioTrack" => NamedKey::MediaAudioTrack, "MediaLast" => NamedKey::MediaLast, "MediaSkipBackward" => NamedKey::MediaSkipBackward, "MediaSkipForward" => NamedKey::MediaSkipForward, "MediaStepBackward" => NamedKey::MediaStepBackward, "MediaStepForward" => NamedKey::MediaStepForward, "MediaTopMenu" => NamedKey::MediaTopMenu, "NavigateIn" => NamedKey::NavigateIn, "NavigateNext" => NamedKey::NavigateNext, "NavigateOut" => NamedKey::NavigateOut, "NavigatePrevious" => NamedKey::NavigatePrevious, "NextFavoriteChannel" => NamedKey::NextFavoriteChannel, "NextUserProfile" => NamedKey::NextUserProfile, "OnDemand" => NamedKey::OnDemand, "Pairing" => NamedKey::Pairing, "PinPDown" => NamedKey::PinPDown, "PinPMove" => NamedKey::PinPMove, "PinPToggle" => NamedKey::PinPToggle, "PinPUp" => NamedKey::PinPUp, "PlaySpeedDown" => NamedKey::PlaySpeedDown, "PlaySpeedReset" => NamedKey::PlaySpeedReset, "PlaySpeedUp" => NamedKey::PlaySpeedUp, "RandomToggle" => NamedKey::RandomToggle, "RcLowBattery" => NamedKey::RcLowBattery, "RecordSpeedNext" => NamedKey::RecordSpeedNext, "RfBypass" => NamedKey::RfBypass, "ScanChannelsToggle" => NamedKey::ScanChannelsToggle, "ScreenModeNext" => NamedKey::ScreenModeNext, "Settings" => NamedKey::Settings, "SplitScreenToggle" => NamedKey::SplitScreenToggle, "STBInput" => NamedKey::STBInput, "STBPower" => NamedKey::STBPower, "Subtitle" => NamedKey::Subtitle, "Teletext" => NamedKey::Teletext, "VideoModeNext" => NamedKey::VideoModeNext, "Wink" => NamedKey::Wink, "ZoomToggle" => NamedKey::ZoomToggle, "F1" => NamedKey::F1, "F2" => NamedKey::F2, "F3" => NamedKey::F3, "F4" => NamedKey::F4, "F5" => NamedKey::F5, "F6" => NamedKey::F6, "F7" => NamedKey::F7, "F8" => NamedKey::F8, "F9" => NamedKey::F9, "F10" => NamedKey::F10, "F11" => NamedKey::F11, "F12" => NamedKey::F12, "F13" => NamedKey::F13, "F14" => NamedKey::F14, "F15" => NamedKey::F15, "F16" => NamedKey::F16, "F17" => NamedKey::F17, "F18" => NamedKey::F18, "F19" => NamedKey::F19, "F20" => NamedKey::F20, "F21" => NamedKey::F21, "F22" => NamedKey::F22, "F23" => NamedKey::F23, "F24" => NamedKey::F24, "F25" => NamedKey::F25, "F26" => NamedKey::F26, "F27" => NamedKey::F27, "F28" => NamedKey::F28, "F29" => NamedKey::F29, "F30" => NamedKey::F30, "F31" => NamedKey::F31, "F32" => NamedKey::F32, "F33" => NamedKey::F33, "F34" => NamedKey::F34, "F35" => NamedKey::F35, string => return Key::Character(SmolStr::new(string)), }) } } impl PhysicalKey { pub fn from_key_code_attribute_value(kcav: &str) -> Self { PhysicalKey::Code(match kcav { "Backquote" => KeyCode::Backquote, "Backslash" => KeyCode::Backslash, "BracketLeft" => KeyCode::BracketLeft, "BracketRight" => KeyCode::BracketRight, "Comma" => KeyCode::Comma, "Digit0" => KeyCode::Digit0, "Digit1" => KeyCode::Digit1, "Digit2" => KeyCode::Digit2, "Digit3" => KeyCode::Digit3, "Digit4" => KeyCode::Digit4, "Digit5" => KeyCode::Digit5, "Digit6" => KeyCode::Digit6, "Digit7" => KeyCode::Digit7, "Digit8" => KeyCode::Digit8, "Digit9" => KeyCode::Digit9, "Equal" => KeyCode::Equal, "IntlBackslash" => KeyCode::IntlBackslash, "IntlRo" => KeyCode::IntlRo, "IntlYen" => KeyCode::IntlYen, "KeyA" => KeyCode::KeyA, "KeyB" => KeyCode::KeyB, "KeyC" => KeyCode::KeyC, "KeyD" => KeyCode::KeyD, "KeyE" => KeyCode::KeyE, "KeyF" => KeyCode::KeyF, "KeyG" => KeyCode::KeyG, "KeyH" => KeyCode::KeyH, "KeyI" => KeyCode::KeyI, "KeyJ" => KeyCode::KeyJ, "KeyK" => KeyCode::KeyK, "KeyL" => KeyCode::KeyL, "KeyM" => KeyCode::KeyM, "KeyN" => KeyCode::KeyN, "KeyO" => KeyCode::KeyO, "KeyP" => KeyCode::KeyP, "KeyQ" => KeyCode::KeyQ, "KeyR" => KeyCode::KeyR, "KeyS" => KeyCode::KeyS, "KeyT" => KeyCode::KeyT, "KeyU" => KeyCode::KeyU, "KeyV" => KeyCode::KeyV, "KeyW" => KeyCode::KeyW, "KeyX" => KeyCode::KeyX, "KeyY" => KeyCode::KeyY, "KeyZ" => KeyCode::KeyZ, "Minus" => KeyCode::Minus, "Period" => KeyCode::Period, "Quote" => KeyCode::Quote, "Semicolon" => KeyCode::Semicolon, "Slash" => KeyCode::Slash, "AltLeft" => KeyCode::AltLeft, "AltRight" => KeyCode::AltRight, "Backspace" => KeyCode::Backspace, "CapsLock" => KeyCode::CapsLock, "ContextMenu" => KeyCode::ContextMenu, "ControlLeft" => KeyCode::ControlLeft, "ControlRight" => KeyCode::ControlRight, "Enter" => KeyCode::Enter, "MetaLeft" => KeyCode::SuperLeft, "MetaRight" => KeyCode::SuperRight, "ShiftLeft" => KeyCode::ShiftLeft, "ShiftRight" => KeyCode::ShiftRight, "Space" => KeyCode::Space, "Tab" => KeyCode::Tab, "Convert" => KeyCode::Convert, "KanaMode" => KeyCode::KanaMode, "Lang1" => KeyCode::Lang1, "Lang2" => KeyCode::Lang2, "Lang3" => KeyCode::Lang3, "Lang4" => KeyCode::Lang4, "Lang5" => KeyCode::Lang5, "NonConvert" => KeyCode::NonConvert, "Delete" => KeyCode::Delete, "End" => KeyCode::End, "Help" => KeyCode::Help, "Home" => KeyCode::Home, "Insert" => KeyCode::Insert, "PageDown" => KeyCode::PageDown, "PageUp" => KeyCode::PageUp, "ArrowDown" => KeyCode::ArrowDown, "ArrowLeft" => KeyCode::ArrowLeft, "ArrowRight" => KeyCode::ArrowRight, "ArrowUp" => KeyCode::ArrowUp, "NumLock" => KeyCode::NumLock, "Numpad0" => KeyCode::Numpad0, "Numpad1" => KeyCode::Numpad1, "Numpad2" => KeyCode::Numpad2, "Numpad3" => KeyCode::Numpad3, "Numpad4" => KeyCode::Numpad4, "Numpad5" => KeyCode::Numpad5, "Numpad6" => KeyCode::Numpad6, "Numpad7" => KeyCode::Numpad7, "Numpad8" => KeyCode::Numpad8, "Numpad9" => KeyCode::Numpad9, "NumpadAdd" => KeyCode::NumpadAdd, "NumpadBackspace" => KeyCode::NumpadBackspace, "NumpadClear" => KeyCode::NumpadClear, "NumpadClearEntry" => KeyCode::NumpadClearEntry, "NumpadComma" => KeyCode::NumpadComma, "NumpadDecimal" => KeyCode::NumpadDecimal, "NumpadDivide" => KeyCode::NumpadDivide, "NumpadEnter" => KeyCode::NumpadEnter, "NumpadEqual" => KeyCode::NumpadEqual, "NumpadHash" => KeyCode::NumpadHash, "NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd, "NumpadMemoryClear" => KeyCode::NumpadMemoryClear, "NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall, "NumpadMemoryStore" => KeyCode::NumpadMemoryStore, "NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract, "NumpadMultiply" => KeyCode::NumpadMultiply, "NumpadParenLeft" => KeyCode::NumpadParenLeft, "NumpadParenRight" => KeyCode::NumpadParenRight, "NumpadStar" => KeyCode::NumpadStar, "NumpadSubtract" => KeyCode::NumpadSubtract, "Escape" => KeyCode::Escape, "Fn" => KeyCode::Fn, "FnLock" => KeyCode::FnLock, "PrintScreen" => KeyCode::PrintScreen, "ScrollLock" => KeyCode::ScrollLock, "Pause" => KeyCode::Pause, "BrowserBack" => KeyCode::BrowserBack, "BrowserFavorites" => KeyCode::BrowserFavorites, "BrowserForward" => KeyCode::BrowserForward, "BrowserHome" => KeyCode::BrowserHome, "BrowserRefresh" => KeyCode::BrowserRefresh, "BrowserSearch" => KeyCode::BrowserSearch, "BrowserStop" => KeyCode::BrowserStop, "Eject" => KeyCode::Eject, "LaunchApp1" => KeyCode::LaunchApp1, "LaunchApp2" => KeyCode::LaunchApp2, "LaunchMail" => KeyCode::LaunchMail, "MediaPlayPause" => KeyCode::MediaPlayPause, "MediaSelect" => KeyCode::MediaSelect, "MediaStop" => KeyCode::MediaStop, "MediaTrackNext" => KeyCode::MediaTrackNext, "MediaTrackPrevious" => KeyCode::MediaTrackPrevious, "Power" => KeyCode::Power, "Sleep" => KeyCode::Sleep, "AudioVolumeDown" => KeyCode::AudioVolumeDown, "AudioVolumeMute" => KeyCode::AudioVolumeMute, "AudioVolumeUp" => KeyCode::AudioVolumeUp, "WakeUp" => KeyCode::WakeUp, "Hyper" => KeyCode::Hyper, "Turbo" => KeyCode::Turbo, "Abort" => KeyCode::Abort, "Resume" => KeyCode::Resume, "Suspend" => KeyCode::Suspend, "Again" => KeyCode::Again, "Copy" => KeyCode::Copy, "Cut" => KeyCode::Cut, "Find" => KeyCode::Find, "Open" => KeyCode::Open, "Paste" => KeyCode::Paste, "Props" => KeyCode::Props, "Select" => KeyCode::Select, "Undo" => KeyCode::Undo, "Hiragana" => KeyCode::Hiragana, "Katakana" => KeyCode::Katakana, "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, _ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified), }) } } winit-0.29.15/src/platform_impl/web/mod.rs000064400000000000000000000040101046102023000164650ustar 00000000000000// Brief introduction to the internals of the web backend: // The web backend used to support both wasm-bindgen and stdweb as methods of binding to the // environment. Because they are both supporting the same underlying APIs, the actual web bindings // are cordoned off into backend abstractions, which present the thinnest unifying layer possible. // // When adding support for new events or interactions with the browser, first consult trusted // documentation (such as MDN) to ensure it is well-standardised and supported across many browsers. // Once you have decided on the relevant web APIs, add support to both backends. // // The backend is used by the rest of the module to implement Winit's business logic, which forms // the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures // for winit's cross-platform structures. They are all relatively simple translations. // // The event_loop module handles listening for and processing events. 'Proxy' implements // EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles // registering the event handlers. The 'Execution' struct in the 'runner' module handles taking // incoming events (from the registered handlers) and ensuring they are passed to the user in a // compliant way. // TODO: FP, remove when is fixed. #![allow(unknown_lints, non_local_definitions)] mod r#async; mod device; mod error; mod event_loop; mod keyboard; mod monitor; mod window; #[path = "web_sys/mod.rs"] mod backend; pub use self::device::DeviceId; pub use self::error::OsError; pub(crate) use self::event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }; pub use self::monitor::{MonitorHandle, VideoMode}; pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}; pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; winit-0.29.15/src/platform_impl/web/monitor.rs000064400000000000000000000020371046102023000174040ustar 00000000000000use std::iter::Empty; use crate::dpi::{PhysicalPosition, PhysicalSize}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MonitorHandle; impl MonitorHandle { pub fn scale_factor(&self) -> f64 { unreachable!() } pub fn position(&self) -> PhysicalPosition { unreachable!() } pub fn name(&self) -> Option { unreachable!() } pub fn refresh_rate_millihertz(&self) -> Option { unreachable!() } pub fn size(&self) -> PhysicalSize { unreachable!() } pub fn video_modes(&self) -> Empty { unreachable!() } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VideoMode; impl VideoMode { pub fn size(&self) -> PhysicalSize { unreachable!(); } pub fn bit_depth(&self) -> u16 { unreachable!(); } pub fn refresh_rate_millihertz(&self) -> u32 { unreachable!(); } pub fn monitor(&self) -> MonitorHandle { unreachable!(); } } winit-0.29.15/src/platform_impl/web/web_sys/animation_frame.rs000064400000000000000000000033731046102023000225250ustar 00000000000000use std::cell::Cell; use std::rc::Rc; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; pub struct AnimationFrameHandler { window: web_sys::Window, closure: Closure, handle: Rc>>, } impl AnimationFrameHandler { pub fn new(window: web_sys::Window) -> Self { let handle = Rc::new(Cell::new(None)); let closure = Closure::new({ let handle = handle.clone(); move || handle.set(None) }); Self { window, closure, handle, } } pub fn on_animation_frame(&mut self, mut f: F) where F: 'static + FnMut(), { let handle = self.handle.clone(); self.closure = Closure::new(move || { handle.set(None); f(); }) } pub fn request(&self) { if let Some(handle) = self.handle.take() { self.window .cancel_animation_frame(handle) .expect("Failed to cancel animation frame"); } let handle = self .window .request_animation_frame(self.closure.as_ref().unchecked_ref()) .expect("Failed to request animation frame"); self.handle.set(Some(handle)); } pub fn cancel(&mut self) { if let Some(handle) = self.handle.take() { self.window .cancel_animation_frame(handle) .expect("Failed to cancel animation frame"); } } } impl Drop for AnimationFrameHandler { fn drop(&mut self) { if let Some(handle) = self.handle.take() { self.window .cancel_animation_frame(handle) .expect("Failed to cancel animation frame"); } } } winit-0.29.15/src/platform_impl/web/web_sys/canvas.rs000064400000000000000000000462441046102023000206530ustar 00000000000000use std::cell::Cell; use std::rc::{Rc, Weak}; use std::sync::{Arc, Mutex}; use smol_str::SmolStr; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent, }; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, InnerSizeWriter, MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, PhysicalKey}; use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use crate::window::{WindowAttributes, WindowId as RootWindowId}; use super::super::WindowId; use super::animation_frame::AnimationFrameHandler; use super::event_handle::EventListenerHandle; use super::fullscreen::FullscreenHandler; use super::intersection_handle::IntersectionObserverHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; use super::{event, ButtonsState, ResizeScaleHandle}; #[allow(dead_code)] pub struct Canvas { common: Common, id: WindowId, pub has_focus: Rc>, pub is_intersecting: Option, on_touch_start: Option>, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, on_mouse_wheel: Option>, on_dark_mode: Option, pointer_handler: PointerHandler, on_resize_scale: Option, on_intersect: Option, animation_frame_handler: AnimationFrameHandler, on_touch_end: Option>, on_context_menu: Option>, } pub struct Common { pub window: web_sys::Window, pub document: Document, /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. pub raw: HtmlCanvasElement, style: CssStyleDeclaration, old_size: Rc>>, current_size: Rc>>, fullscreen_handler: Rc, } impl Canvas { pub fn create( id: WindowId, window: web_sys::Window, document: Document, attr: &WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, ) -> Result { let canvas = match platform_attr.canvas.0 { Some(canvas) => canvas, None => document .create_element("canvas") .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? .unchecked_into(), }; if platform_attr.append && !document.contains(Some(&canvas)) { document .body() .expect("Failed to get body from document") .append_child(&canvas) .expect("Failed to append canvas to body"); } // A tabindex is needed in order to capture local keyboard events. // A "0" value means that the element should be focusable in // sequential keyboard navigation, but its order is defined by the // document's source order. // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex if platform_attr.focusable { canvas .set_attribute("tabindex", "0") .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; } #[allow(clippy::disallowed_methods)] let style = window .get_computed_style(&canvas) .expect("Failed to obtain computed style") // this can't fail: we aren't using a pseudo-element .expect("Invalid pseudo-element"); let common = Common { window: window.clone(), document: document.clone(), raw: canvas.clone(), style, old_size: Rc::default(), current_size: Rc::default(), fullscreen_handler: Rc::new(FullscreenHandler::new(document.clone(), canvas.clone())), }; if let Some(size) = attr.inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_size(&common.document, &common.raw, &common.style, size); } if let Some(size) = attr.min_inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_min_size(&common.document, &common.raw, &common.style, Some(size)); } if let Some(size) = attr.max_inner_size { let size = size.to_logical(super::scale_factor(&common.window)); super::set_canvas_max_size(&common.document, &common.raw, &common.style, Some(size)); } if let Some(position) = attr.position { let position = position.to_logical(super::scale_factor(&common.window)); super::set_canvas_position(&common.document, &common.raw, &common.style, position); } if attr.fullscreen.0.is_some() { common.fullscreen_handler.request_fullscreen(); } if attr.active { let _ = common.raw.focus(); } Ok(Canvas { common, id, has_focus: Rc::new(Cell::new(false)), is_intersecting: None, on_touch_start: None, on_blur: None, on_focus: None, on_keyboard_release: None, on_keyboard_press: None, on_mouse_wheel: None, on_dark_mode: None, pointer_handler: PointerHandler::new(), on_resize_scale: None, on_intersect: None, animation_frame_handler: AnimationFrameHandler::new(window), on_touch_end: None, on_context_menu: None, }) } pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> { if lock { self.raw().request_pointer_lock(); } else { self.common.document.exit_pointer_lock(); } Ok(()) } pub fn set_attribute(&self, attribute: &str, value: &str) { self.common .raw .set_attribute(attribute, value) .unwrap_or_else(|err| panic!("error: {err:?}\nSet attribute: {attribute}")) } pub fn position(&self) -> LogicalPosition { let bounds = self.common.raw.get_bounding_client_rect(); let mut position = LogicalPosition { x: bounds.x(), y: bounds.y(), }; if self.document().contains(Some(self.raw())) && self.style().get_property_value("display").unwrap() != "none" { position.x += super::style_size_property(self.style(), "border-left-width") + super::style_size_property(self.style(), "padding-left"); position.y += super::style_size_property(self.style(), "border-top-width") + super::style_size_property(self.style(), "padding-top"); } position } #[inline] pub fn old_size(&self) -> PhysicalSize { self.common.old_size.get() } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.common.current_size.get() } #[inline] pub fn set_old_size(&self, size: PhysicalSize) { self.common.old_size.set(size) } #[inline] pub fn set_current_size(&self, size: PhysicalSize) { self.common.current_size.set(size) } #[inline] pub fn window(&self) -> &web_sys::Window { &self.common.window } #[inline] pub fn document(&self) -> &Document { &self.common.document } #[inline] pub fn raw(&self) -> &HtmlCanvasElement { &self.common.raw } #[inline] pub fn style(&self) -> &CssStyleDeclaration { &self.common.style } pub fn on_touch_start(&mut self, prevent_default: bool) { self.on_touch_start = Some(self.common.add_event("touchstart", move |event: Event| { if prevent_default { event.prevent_default(); } })); } pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), { self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { handler(); })); } pub fn on_focus(&mut self, mut handler: F) where F: 'static + FnMut(), { self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { handler(); })); } pub fn on_keyboard_release(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { self.on_keyboard_release = Some(self.common.add_event("keyup", move |event: KeyboardEvent| { if prevent_default { event.prevent_default(); } let key = event::key(&event); let modifiers = event::keyboard_modifiers(&event); handler( event::key_code(&event), key, event::key_text(&event), event::key_location(&event), event.repeat(), modifiers, ); })); } pub fn on_keyboard_press(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(PhysicalKey, Key, Option, KeyLocation, bool, ModifiersState), { self.on_keyboard_press = Some(self.common.add_transient_event( "keydown", move |event: KeyboardEvent| { if prevent_default { event.prevent_default(); } let key = event::key(&event); let modifiers = event::keyboard_modifiers(&event); handler( event::key_code(&event), key, event::key_text(&event), event::key_location(&event), event.repeat(), modifiers, ); }, )); } pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.pointer_handler.on_cursor_leave(&self.common, handler) } pub fn on_cursor_enter(&mut self, handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.pointer_handler.on_cursor_enter(&self.common, handler) } pub fn on_mouse_release( &mut self, modifier_handler: MOD, mouse_handler: M, touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_release( &self.common, modifier_handler, mouse_handler, touch_handler, ) } pub fn on_mouse_press( &mut self, modifier_handler: MOD, mouse_handler: M, touch_handler: T, prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_press( &self.common, modifier_handler, mouse_handler, touch_handler, prevent_default, ) } pub fn on_cursor_move( &mut self, modifier_handler: MOD, mouse_handler: M, touch_handler: T, button_handler: B, prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), { self.pointer_handler.on_cursor_move( &self.common, modifier_handler, mouse_handler, touch_handler, button_handler, prevent_default, ) } pub fn on_touch_cancel(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { self.pointer_handler.on_touch_cancel(&self.common, handler) } pub fn on_mouse_wheel(&mut self, mut handler: F, prevent_default: bool) where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { let window = self.common.window.clone(); self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { if prevent_default { event.prevent_default(); } if let Some(delta) = event::mouse_scroll_delta(&window, &event) { let modifiers = event::mouse_modifiers(&event); handler(0, delta, modifiers); } })); } pub fn on_dark_mode(&mut self, mut handler: F) where F: 'static + FnMut(bool), { self.on_dark_mode = Some(MediaQueryListHandle::new( &self.common.window, "(prefers-color-scheme: dark)", move |mql| handler(mql.matches()), )); } pub(crate) fn on_resize_scale(&mut self, scale_handler: S, size_handler: R) where S: 'static + FnMut(PhysicalSize, f64), R: 'static + FnMut(PhysicalSize), { self.on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), self.document().clone(), self.raw().clone(), self.style().clone(), scale_handler, size_handler, )); } pub(crate) fn on_intersection(&mut self, handler: F) where F: 'static + FnMut(bool), { self.on_intersect = Some(IntersectionObserverHandle::new(self.raw(), handler)); } pub(crate) fn on_animation_frame(&mut self, f: F) where F: 'static + FnMut(), { self.animation_frame_handler.on_animation_frame(f) } pub(crate) fn on_touch_end(&mut self) { self.on_touch_end = Some(self.common.add_transient_event("touchend", |_| {})); } pub(crate) fn on_context_menu(&mut self, prevent_default: bool) { self.on_context_menu = Some(self.common.add_event( "contextmenu", move |event: PointerEvent| { if prevent_default { event.prevent_default(); } }, )); } pub fn request_fullscreen(&self) { self.common.fullscreen_handler.request_fullscreen() } pub fn exit_fullscreen(&self) { self.common.fullscreen_handler.exit_fullscreen() } pub fn is_fullscreen(&self) -> bool { self.common.fullscreen_handler.is_fullscreen() } pub fn request_animation_frame(&self) { self.animation_frame_handler.request(); } pub(crate) fn handle_scale_change( &self, runner: &super::super::event_loop::runner::Shared, event_handler: impl FnOnce(crate::event::Event<()>), current_size: PhysicalSize, scale: f64, ) { // First, we send the `ScaleFactorChanged` event: self.set_current_size(current_size); let new_size = { let new_size = Arc::new(Mutex::new(current_size)); event_handler(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), event: crate::event::WindowEvent::ScaleFactorChanged { scale_factor: scale, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_size)), }, }); let new_size = *new_size.lock().unwrap(); new_size }; if current_size != new_size { // Then we resize the canvas to the new size, a new // `Resized` event will be sent by the `ResizeObserver`: let new_size = new_size.to_logical(scale); super::set_canvas_size(self.document(), self.raw(), self.style(), new_size); // Set the size might not trigger the event because the calculation is inaccurate. self.on_resize_scale .as_ref() .expect("expected Window to still be active") .notify_resize(); } else if self.old_size() != new_size { // Then we at least send a resized event. self.set_old_size(new_size); runner.send_event(crate::event::Event::WindowEvent { window_id: RootWindowId(self.id), event: crate::event::WindowEvent::Resized(new_size), }) } } pub(crate) fn transient_activation(&self) { self.common.fullscreen_handler.transient_activation() } pub fn remove_listeners(&mut self) { self.on_touch_start = None; self.on_focus = None; self.on_blur = None; self.on_keyboard_release = None; self.on_keyboard_press = None; self.on_mouse_wheel = None; self.on_dark_mode = None; self.pointer_handler.remove_listeners(); self.on_resize_scale = None; self.on_intersect = None; self.animation_frame_handler.cancel(); self.on_touch_end = None; self.common.fullscreen_handler.cancel(); self.on_context_menu = None; } } impl Common { pub fn add_event( &self, event_name: &'static str, handler: F, ) -> EventListenerHandle where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), { EventListenerHandle::new(self.raw.clone(), event_name, Closure::new(handler)) } // The difference between add_event and add_user_event is that the latter has a special meaning // for browser security. A user event is a deliberate action by the user (like a mouse or key // press) and is the only time things like a fullscreen request may be successfully completed.) pub fn add_transient_event( &self, event_name: &'static str, mut handler: F, ) -> EventListenerHandle where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), { let fullscreen_handler = Rc::downgrade(&self.fullscreen_handler); self.add_event(event_name, move |event: E| { handler(event); if let Some(fullscreen_handler) = Weak::upgrade(&fullscreen_handler) { fullscreen_handler.transient_activation() } }) } } winit-0.29.15/src/platform_impl/web/web_sys/event.rs000064400000000000000000000226621046102023000205170ustar 00000000000000use crate::dpi::LogicalPosition; use crate::event::{MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; use once_cell::unsync::OnceCell; use smol_str::SmolStr; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; bitflags! { // https://www.w3.org/TR/pointerevents3/#the-buttons-property #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ButtonsState: u16 { const LEFT = 0b00001; const RIGHT = 0b00010; const MIDDLE = 0b00100; const BACK = 0b01000; const FORWARD = 0b10000; } } impl From for MouseButton { fn from(value: ButtonsState) -> Self { match value { ButtonsState::LEFT => MouseButton::Left, ButtonsState::RIGHT => MouseButton::Right, ButtonsState::MIDDLE => MouseButton::Middle, ButtonsState::BACK => MouseButton::Back, ButtonsState::FORWARD => MouseButton::Forward, _ => MouseButton::Other(value.bits()), } } } impl From for ButtonsState { fn from(value: MouseButton) -> Self { match value { MouseButton::Left => ButtonsState::LEFT, MouseButton::Right => ButtonsState::RIGHT, MouseButton::Middle => ButtonsState::MIDDLE, MouseButton::Back => ButtonsState::BACK, MouseButton::Forward => ButtonsState::FORWARD, MouseButton::Other(value) => ButtonsState::from_bits_retain(value), } } } pub fn mouse_buttons(event: &MouseEvent) -> ButtonsState { ButtonsState::from_bits_retain(event.buttons()) } pub fn mouse_button(event: &MouseEvent) -> Option { // https://www.w3.org/TR/pointerevents3/#the-button-property match event.button() { -1 => None, 0 => Some(MouseButton::Left), 1 => Some(MouseButton::Middle), 2 => Some(MouseButton::Right), 3 => Some(MouseButton::Back), 4 => Some(MouseButton::Forward), i => Some(MouseButton::Other( i.try_into() .expect("unexpected negative mouse button value"), )), } } impl MouseButton { pub fn to_id(self) -> u32 { match self { MouseButton::Left => 0, MouseButton::Right => 1, MouseButton::Middle => 2, MouseButton::Back => 3, MouseButton::Forward => 4, MouseButton::Other(value) => value.into(), } } } pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { #[wasm_bindgen] extern "C" { type MouseEventExt; #[wasm_bindgen(method, getter, js_name = offsetX)] fn offset_x(this: &MouseEventExt) -> f64; #[wasm_bindgen(method, getter, js_name = offsetY)] fn offset_y(this: &MouseEventExt) -> f64; } let event: &MouseEventExt = event.unchecked_ref(); LogicalPosition { x: event.offset_x(), y: event.offset_y(), } } // TODO: Remove this when Firefox supports correct movement values in coalesced events. // See . pub struct MouseDelta(Option); pub struct MouseDeltaInner { old_position: LogicalPosition, old_delta: LogicalPosition, } impl MouseDelta { pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self { // Firefox has wrong movement values in coalesced events, we will detect that by checking // for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate` // should require correct movement values, otherwise uncoalesced events might be broken as // well. Self( (!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| { MouseDeltaInner { old_position: mouse_position(event), old_delta: LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64, }, } }), ) } pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition { if let Some(inner) = &mut self.0 { let new_position = mouse_position(event); let x = new_position.x - inner.old_position.x + inner.old_delta.x; let y = new_position.y - inner.old_position.y + inner.old_delta.y; inner.old_position = new_position; inner.old_delta = LogicalPosition::new(0., 0.); LogicalPosition::new(x, y) } else { LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64, } } } } pub fn mouse_scroll_delta( window: &web_sys::Window, event: &WheelEvent, ) -> Option { let x = -event.delta_x(); let y = -event.delta_y(); match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), WheelEvent::DOM_DELTA_PIXEL => { let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor(window)); Some(MouseScrollDelta::PixelDelta(delta)) } _ => None, } } pub fn key_code(event: &KeyboardEvent) -> PhysicalKey { let code = event.code(); PhysicalKey::from_key_code_attribute_value(&code) } pub fn key(event: &KeyboardEvent) -> Key { Key::from_key_attribute_value(&event.key()) } pub fn key_text(event: &KeyboardEvent) -> Option { let key = event.key(); let key = Key::from_key_attribute_value(&key); match &key { Key::Character(text) => Some(text.clone()), Key::Named(NamedKey::Tab) => Some(SmolStr::new("\t")), Key::Named(NamedKey::Enter) => Some(SmolStr::new("\r")), Key::Named(NamedKey::Space) => Some(SmolStr::new(" ")), _ => None, } .map(SmolStr::new) } pub fn key_location(event: &KeyboardEvent) -> KeyLocation { match event.location() { KeyboardEvent::DOM_KEY_LOCATION_LEFT => KeyLocation::Left, KeyboardEvent::DOM_KEY_LOCATION_RIGHT => KeyLocation::Right, KeyboardEvent::DOM_KEY_LOCATION_NUMPAD => KeyLocation::Numpad, KeyboardEvent::DOM_KEY_LOCATION_STANDARD => KeyLocation::Standard, location => { warn!("Unexpected key location: {location}"); KeyLocation::Standard } } } pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { let mut state = ModifiersState::empty(); if event.shift_key() { state |= ModifiersState::SHIFT; } if event.ctrl_key() { state |= ModifiersState::CONTROL; } if event.alt_key() { state |= ModifiersState::ALT; } if event.meta_key() { state |= ModifiersState::SUPER; } state } pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { let mut state = ModifiersState::empty(); if event.shift_key() { state |= ModifiersState::SHIFT; } if event.ctrl_key() { state |= ModifiersState::CONTROL; } if event.alt_key() { state |= ModifiersState::ALT; } if event.meta_key() { state |= ModifiersState::SUPER; } state } pub fn pointer_move_event(event: PointerEvent) -> impl Iterator { // make a single iterator depending on the availability of coalesced events if has_coalesced_events_support(&event) { None.into_iter().chain( Some( event .get_coalesced_events() .into_iter() .map(PointerEvent::unchecked_from_js), ) .into_iter() .flatten(), ) } else { Some(event).into_iter().chain(None.into_iter().flatten()) } } // TODO: Remove when all browsers implement it correctly. // See . pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool { thread_local! { static POINTER_RAW_SUPPORT: OnceCell = const { OnceCell::new() }; } POINTER_RAW_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type PointerRawSupport; #[wasm_bindgen(method, getter, js_name = onpointerrawupdate)] fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue; } let support: &PointerRawSupport = window.unchecked_ref(); !support.has_on_pointerrawupdate().is_undefined() }) }) } // TODO: Remove when Safari supports `getCoalescedEvents`. // See . pub fn has_coalesced_events_support(event: &PointerEvent) -> bool { thread_local! { static COALESCED_EVENTS_SUPPORT: OnceCell = const { OnceCell::new() }; } COALESCED_EVENTS_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type PointerCoalescedEventsSupport; #[wasm_bindgen(method, getter, js_name = getCoalescedEvents)] fn has_get_coalesced_events(this: &PointerCoalescedEventsSupport) -> JsValue; } let support: &PointerCoalescedEventsSupport = event.unchecked_ref(); !support.has_get_coalesced_events().is_undefined() }) }) } winit-0.29.15/src/platform_impl/web/web_sys/event_handle.rs000064400000000000000000000022161046102023000220230ustar 00000000000000use wasm_bindgen::{prelude::Closure, JsCast}; use web_sys::EventTarget; pub struct EventListenerHandle { target: EventTarget, event_type: &'static str, listener: Closure, } impl EventListenerHandle { pub fn new(target: U, event_type: &'static str, listener: Closure) -> Self where U: Into, { let target = target.into(); target .add_event_listener_with_callback(event_type, listener.as_ref().unchecked_ref()) .expect("Failed to add event listener"); EventListenerHandle { target, event_type, listener, } } } impl Drop for EventListenerHandle { fn drop(&mut self) { self.target .remove_event_listener_with_callback( self.event_type, self.listener.as_ref().unchecked_ref(), ) .unwrap_or_else(|e| { web_sys::console::error_2( &format!("Error removing event listener {}", self.event_type).into(), &e, ) }); } } winit-0.29.15/src/platform_impl/web/web_sys/fullscreen.rs000064400000000000000000000113611046102023000215320ustar 00000000000000use std::cell::Cell; use std::rc::Rc; use js_sys::Promise; use once_cell::unsync::OnceCell; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{Document, Element, HtmlCanvasElement}; use super::EventListenerHandle; thread_local! { static FULLSCREEN_API_SUPPORT: OnceCell = const { OnceCell::new() }; } pub struct FullscreenHandler { document: Document, canvas: HtmlCanvasElement, fullscreen_requested: Rc>, _fullscreen_change: EventListenerHandle, } impl FullscreenHandler { pub fn new(document: Document, canvas: HtmlCanvasElement) -> Self { let fullscreen_requested = Rc::new(Cell::new(false)); let fullscreen_change = EventListenerHandle::new( canvas.clone(), if has_fullscreen_api_support(&canvas) { "fullscreenchange" } else { "webkitfullscreenchange" }, Closure::new({ let fullscreen_requested = fullscreen_requested.clone(); move || { // It doesn't matter if the canvas entered or exitted fullscreen mode, // we don't want to request it again later. fullscreen_requested.set(false); } }), ); Self { document, canvas, fullscreen_requested, _fullscreen_change: fullscreen_change, } } fn internal_request_fullscreen(&self) { #[wasm_bindgen] extern "C" { type RequestFullscreen; #[wasm_bindgen(method, js_name = requestFullscreen)] fn request_fullscreen(this: &RequestFullscreen) -> Promise; #[wasm_bindgen(method, js_name = webkitRequestFullscreen)] fn webkit_request_fullscreen(this: &RequestFullscreen); } let canvas: &RequestFullscreen = self.canvas.unchecked_ref(); if has_fullscreen_api_support(&self.canvas) { thread_local! { static REJECT_HANDLER: Closure = Closure::new(|_| ()); } REJECT_HANDLER.with(|handler| { let _ = canvas.request_fullscreen().catch(handler); }); } else { canvas.webkit_request_fullscreen(); } } pub fn request_fullscreen(&self) { if !self.is_fullscreen() { self.internal_request_fullscreen(); self.fullscreen_requested.set(true); } } pub fn transient_activation(&self) { if self.fullscreen_requested.get() { self.internal_request_fullscreen() } } pub fn is_fullscreen(&self) -> bool { #[wasm_bindgen] extern "C" { type FullscreenElement; #[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)] fn webkit_fullscreen_element(this: &FullscreenElement) -> Option; } let element = if has_fullscreen_api_support(&self.canvas) { #[allow(clippy::disallowed_methods)] self.document.fullscreen_element() } else { let document: &FullscreenElement = self.document.unchecked_ref(); document.webkit_fullscreen_element() }; match element { Some(element) => { let canvas: &Element = &self.canvas; canvas == &element } None => false, } } pub fn exit_fullscreen(&self) { #[wasm_bindgen] extern "C" { type ExitFullscreen; #[wasm_bindgen(method, js_name = webkitExitFullscreen)] fn webkit_exit_fullscreen(this: &ExitFullscreen); } if has_fullscreen_api_support(&self.canvas) { #[allow(clippy::disallowed_methods)] self.document.exit_fullscreen() } else { let document: &ExitFullscreen = self.document.unchecked_ref(); document.webkit_exit_fullscreen() } self.fullscreen_requested.set(false); } pub fn cancel(&self) { self.fullscreen_requested.set(false); } } fn has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool { FULLSCREEN_API_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type CanvasFullScreenApiSupport; #[wasm_bindgen(method, getter, js_name = requestFullscreen)] fn has_request_fullscreen(this: &CanvasFullScreenApiSupport) -> JsValue; } let support: &CanvasFullScreenApiSupport = canvas.unchecked_ref(); !support.has_request_fullscreen().is_undefined() }) }) } winit-0.29.15/src/platform_impl/web/web_sys/intersection_handle.rs000064400000000000000000000017731046102023000234170ustar 00000000000000use js_sys::Array; use wasm_bindgen::{prelude::Closure, JsCast}; use web_sys::{Element, IntersectionObserver, IntersectionObserverEntry}; pub(super) struct IntersectionObserverHandle { observer: IntersectionObserver, _closure: Closure, } impl IntersectionObserverHandle { pub fn new(element: &Element, mut callback: F) -> Self where F: 'static + FnMut(bool), { let closure = Closure::new(move |entries: Array| { let entry: IntersectionObserverEntry = entries.get(0).unchecked_into(); callback(entry.is_intersecting()); }); let observer = IntersectionObserver::new(closure.as_ref().unchecked_ref()) // we don't provide any `options` .expect("Invalid `options`"); observer.observe(element); Self { observer, _closure: closure, } } } impl Drop for IntersectionObserverHandle { fn drop(&mut self) { self.observer.disconnect() } } winit-0.29.15/src/platform_impl/web/web_sys/media_query_handle.rs000064400000000000000000000027031046102023000232070ustar 00000000000000use wasm_bindgen::{prelude::Closure, JsCast}; use web_sys::MediaQueryList; pub(super) struct MediaQueryListHandle { mql: MediaQueryList, closure: Closure, } impl MediaQueryListHandle { pub fn new(window: &web_sys::Window, media_query: &str, mut listener: F) -> Self where F: 'static + FnMut(&MediaQueryList), { let mql = window .match_media(media_query) .expect("Failed to parse media query") .expect("Found empty media query"); let closure = Closure::new({ let mql = mql.clone(); move || listener(&mql) }); // TODO: Replace obsolete `addListener()` with `addEventListener()` and use // `MediaQueryListEvent` instead of cloning the `MediaQueryList`. // Requires Safari v14. mql.add_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) .expect("Invalid listener"); Self { mql, closure } } pub fn mql(&self) -> &MediaQueryList { &self.mql } } impl Drop for MediaQueryListHandle { fn drop(&mut self) { remove_listener(&self.mql, &self.closure); } } fn remove_listener(mql: &MediaQueryList, listener: &Closure) { mql.remove_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())) .unwrap_or_else(|e| { web_sys::console::error_2(&"Error removing media query listener".into(), &e) }); } winit-0.29.15/src/platform_impl/web/web_sys/mod.rs000064400000000000000000000142371046102023000201540ustar 00000000000000mod animation_frame; mod canvas; pub mod event; mod event_handle; mod fullscreen; mod intersection_handle; mod media_query_handle; mod pointer; mod resize_scaling; mod schedule; pub use self::canvas::Canvas; pub use self::event::ButtonsState; pub use self::event_handle::EventListenerHandle; pub use self::resize_scaling::ResizeScaleHandle; pub use self::schedule::Schedule; use crate::dpi::{LogicalPosition, LogicalSize}; use wasm_bindgen::closure::Closure; use web_sys::{ CssStyleDeclaration, Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState, }; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } pub struct PageTransitionEventHandle { _show_listener: event_handle::EventListenerHandle, _hide_listener: event_handle::EventListenerHandle, } pub fn on_page_transition( window: web_sys::Window, show_handler: impl FnMut(PageTransitionEvent) + 'static, hide_handler: impl FnMut(PageTransitionEvent) + 'static, ) -> PageTransitionEventHandle { let show_closure = Closure::new(show_handler); let hide_closure = Closure::new(hide_handler); let show_listener = event_handle::EventListenerHandle::new(window.clone(), "pageshow", show_closure); let hide_listener = event_handle::EventListenerHandle::new(window, "pagehide", hide_closure); PageTransitionEventHandle { _show_listener: show_listener, _hide_listener: hide_listener, } } pub fn scale_factor(window: &web_sys::Window) -> f64 { window.device_pixel_ratio() } fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize) -> LogicalSize { if style.get_property_value("box-sizing").unwrap() == "border-box" { size.width += style_size_property(style, "border-left-width") + style_size_property(style, "border-right-width") + style_size_property(style, "padding-left") + style_size_property(style, "padding-right"); size.height += style_size_property(style, "border-top-width") + style_size_property(style, "border-bottom-width") + style_size_property(style, "padding-top") + style_size_property(style, "padding-bottom"); } size } pub fn set_canvas_size( document: &Document, raw: &HtmlCanvasElement, style: &CssStyleDeclaration, new_size: LogicalSize, ) { if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { return; } let new_size = fix_canvas_size(style, new_size); set_canvas_style_property(raw, "width", &format!("{}px", new_size.width)); set_canvas_style_property(raw, "height", &format!("{}px", new_size.height)); } pub fn set_canvas_min_size( document: &Document, raw: &HtmlCanvasElement, style: &CssStyleDeclaration, dimensions: Option>, ) { if let Some(dimensions) = dimensions { if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { return; } let new_size = fix_canvas_size(style, dimensions); set_canvas_style_property(raw, "min-width", &format!("{}px", new_size.width)); set_canvas_style_property(raw, "min-height", &format!("{}px", new_size.height)); } else { style .remove_property("min-width") .expect("Property is read only"); style .remove_property("min-height") .expect("Property is read only"); } } pub fn set_canvas_max_size( document: &Document, raw: &HtmlCanvasElement, style: &CssStyleDeclaration, dimensions: Option>, ) { if let Some(dimensions) = dimensions { if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { return; } let new_size = fix_canvas_size(style, dimensions); set_canvas_style_property(raw, "max-width", &format!("{}px", new_size.width)); set_canvas_style_property(raw, "max-height", &format!("{}px", new_size.height)); } else { style .remove_property("max-width") .expect("Property is read only"); style .remove_property("max-height") .expect("Property is read only"); } } pub fn set_canvas_position( document: &Document, raw: &HtmlCanvasElement, style: &CssStyleDeclaration, mut position: LogicalPosition, ) { if document.contains(Some(raw)) && style.get_property_value("display").unwrap() != "none" { position.x -= style_size_property(style, "margin-left") + style_size_property(style, "border-left-width") + style_size_property(style, "padding-left"); position.y -= style_size_property(style, "margin-top") + style_size_property(style, "border-top-width") + style_size_property(style, "padding-top"); } set_canvas_style_property(raw, "position", "fixed"); set_canvas_style_property(raw, "left", &format!("{}px", position.x)); set_canvas_style_property(raw, "top", &format!("{}px", position.y)); } /// This function will panic if the element is not inserted in the DOM /// or is not a CSS property that represents a size in pixel. pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 { let prop = style .get_property_value(property) .expect("Found invalid property"); prop.strip_suffix("px") .expect("Element was not inserted into the DOM or is not a size in pixel") .parse() .expect("CSS property is not a size in pixel") } pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) { let style = raw.style(); style .set_property(property, value) .unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}")) } pub fn is_dark_mode(window: &web_sys::Window) -> Option { window .match_media("(prefers-color-scheme: dark)") .ok() .flatten() .map(|media| media.matches()) } pub fn is_visible(document: &Document) -> bool { document.visibility_state() == VisibilityState::Visible } pub type RawCanvasType = HtmlCanvasElement; winit-0.29.15/src/platform_impl/web/web_sys/pointer.rs000064400000000000000000000241751046102023000210570ustar 00000000000000use super::canvas::Common; use super::event; use super::event_handle::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{Force, MouseButton}; use crate::keyboard::ModifiersState; use event::ButtonsState; use web_sys::PointerEvent; #[allow(dead_code)] pub(super) struct PointerHandler { on_cursor_leave: Option>, on_cursor_enter: Option>, on_cursor_move: Option>, on_pointer_press: Option>, on_pointer_release: Option>, on_touch_cancel: Option>, } impl PointerHandler { pub fn new() -> Self { Self { on_cursor_leave: None, on_cursor_enter: None, on_cursor_move: None, on_pointer_press: None, on_pointer_release: None, on_touch_cancel: None, } } pub fn on_cursor_leave(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.on_cursor_leave = Some(canvas_common.add_event( "pointerout", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); handler(modifiers, pointer_id); }, )); } pub fn on_cursor_enter(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(ModifiersState, Option), { self.on_cursor_enter = Some(canvas_common.add_event( "pointerover", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); handler(modifiers, pointer_id); }, )); } pub fn on_mouse_release( &mut self, canvas_common: &Common, mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_pointer_release = Some(canvas_common.add_transient_event( "pointerup", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); match event.pointer_type().as_str() { "touch" => touch_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ), "mouse" => mouse_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_button(&event).expect("no mouse button released"), ), _ => modifier_handler(modifiers), } }, )); } pub fn on_mouse_press( &mut self, canvas_common: &Common, mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw.clone(); self.on_pointer_press = Some(canvas_common.add_transient_event( "pointerdown", move |event: PointerEvent| { if prevent_default { // prevent text selection event.prevent_default(); // but still focus element let _ = canvas.focus(); } let modifiers = event::mouse_modifiers(&event); match event.pointer_type().as_str() { "touch" => { touch_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); } "mouse" => { mouse_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_button(&event).expect("no mouse button pressed"), ); // Error is swallowed here since the error would occur every time the mouse is // clicked when the cursor is grabbed, and there is probably not a situation where // this could fail, that we care if it fails. let _e = canvas.set_pointer_capture(event.pointer_id()); } _ => modifier_handler(modifiers), } }, )); } pub fn on_cursor_move( &mut self, canvas_common: &Common, mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, mut button_handler: B, prevent_default: bool, ) where MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), B: 'static + FnMut(ModifiersState, i32, PhysicalPosition, ButtonsState, MouseButton), { let window = canvas_common.window.clone(); let canvas = canvas_common.raw.clone(); self.on_cursor_move = Some(canvas_common.add_event( "pointermove", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); let pointer_type = event.pointer_type(); if let "touch" | "mouse" = pointer_type.as_str() { } else { modifier_handler(modifiers); return; } let id = event.pointer_id(); // chorded button event if let Some(button) = event::mouse_button(&event) { debug_assert_eq!( pointer_type, "mouse", "expect pointer type of a chorded button event to be a mouse" ); if prevent_default { // prevent text selection event.prevent_default(); // but still focus element let _ = canvas.focus(); } button_handler( modifiers, id, event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_buttons(&event), button, ); return; } // pointer move event let scale = super::scale_factor(&window); match pointer_type.as_str() { "mouse" => mouse_handler( modifiers, id, &mut event::pointer_move_event(event) .map(|event| event::mouse_position(&event).to_physical(scale)), ), "touch" => touch_handler( modifiers, id, &mut event::pointer_move_event(event).map(|event| { ( event::mouse_position(&event).to_physical(scale), Force::Normalized(event.pressure() as f64), ) }), ), _ => unreachable!("didn't return early before"), }; }, )); } pub fn on_touch_cancel(&mut self, canvas_common: &Common, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition, Force), { let window = canvas_common.window.clone(); self.on_touch_cancel = Some(canvas_common.add_event( "pointercancel", move |event: PointerEvent| { if event.pointer_type() == "touch" { handler( event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ); } }, )); } pub fn remove_listeners(&mut self) { self.on_cursor_leave = None; self.on_cursor_enter = None; self.on_cursor_move = None; self.on_pointer_press = None; self.on_pointer_release = None; self.on_touch_cancel = None; } } winit-0.29.15/src/platform_impl/web/web_sys/resize_scaling.rs000064400000000000000000000245331046102023000223760ustar 00000000000000use js_sys::{Array, Object}; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{ CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window, }; use crate::dpi::{LogicalSize, PhysicalSize}; use super::super::backend; use super::media_query_handle::MediaQueryListHandle; use std::cell::{Cell, RefCell}; use std::rc::Rc; pub struct ResizeScaleHandle(Rc>); impl ResizeScaleHandle { pub(crate) fn new( window: Window, document: Document, canvas: HtmlCanvasElement, style: CssStyleDeclaration, scale_handler: S, resize_handler: R, ) -> Self where S: 'static + FnMut(PhysicalSize, f64), R: 'static + FnMut(PhysicalSize), { Self(ResizeScaleInternal::new( window, document, canvas, style, scale_handler, resize_handler, )) } pub(crate) fn notify_resize(&self) { self.0.borrow_mut().notify() } } /// This is a helper type to help manage the `MediaQueryList` used for detecting /// changes of the `devicePixelRatio`. struct ResizeScaleInternal { window: Window, document: Document, canvas: HtmlCanvasElement, style: CssStyleDeclaration, mql: MediaQueryListHandle, observer: ResizeObserver, _observer_closure: Closure, scale_handler: Box, f64)>, resize_handler: Box)>, notify_scale: Cell, } impl ResizeScaleInternal { fn new( window: Window, document: Document, canvas: HtmlCanvasElement, style: CssStyleDeclaration, scale_handler: S, resize_handler: R, ) -> Rc> where S: 'static + FnMut(PhysicalSize, f64), R: 'static + FnMut(PhysicalSize), { Rc::>::new_cyclic(|weak_self| { let mql = Self::create_mql(&window, { let weak_self = weak_self.clone(); move |mql| { if let Some(rc_self) = weak_self.upgrade() { Self::handle_scale(rc_self, mql); } } }); let weak_self = weak_self.clone(); let observer_closure = Closure::new(move |entries: Array, _| { if let Some(rc_self) = weak_self.upgrade() { let mut this = rc_self.borrow_mut(); let size = this.process_entry(entries); if this.notify_scale.replace(false) { let scale = backend::scale_factor(&this.window); (this.scale_handler)(size, scale) } else { (this.resize_handler)(size) } } }); let observer = Self::create_observer(&canvas, observer_closure.as_ref()); RefCell::new(Self { window, document, canvas, style, mql, observer, _observer_closure: observer_closure, scale_handler: Box::new(scale_handler), resize_handler: Box::new(resize_handler), notify_scale: Cell::new(false), }) }) } fn create_mql(window: &Window, closure: F) -> MediaQueryListHandle where F: 'static + FnMut(&MediaQueryList), { let current_scale = super::scale_factor(window); // TODO: Remove `-webkit-device-pixel-ratio`. Requires Safari v16. let media_query = format!( "(resolution: {current_scale}dppx), (-webkit-device-pixel-ratio: {current_scale})", ); let mql = MediaQueryListHandle::new(window, &media_query, closure); debug_assert!( mql.mql().matches(), "created media query doesn't match, {current_scale} != {}", super::scale_factor(window) ); mql } fn create_observer(canvas: &HtmlCanvasElement, closure: &JsValue) -> ResizeObserver { let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()) .expect("Failed to create `ResizeObserver`"); // Safari doesn't support `devicePixelContentBoxSize` if has_device_pixel_support() { observer.observe_with_options( canvas, ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox), ); } else { observer.observe(canvas); } observer } fn notify(&mut self) { if !self.document.contains(Some(&self.canvas)) || self.style.get_property_value("display").unwrap() == "none" { let size = PhysicalSize::new(0, 0); if self.notify_scale.replace(false) { let scale = backend::scale_factor(&self.window); (self.scale_handler)(size, scale) } else { (self.resize_handler)(size) } return; } // Safari doesn't support `devicePixelContentBoxSize` if has_device_pixel_support() { self.observer.unobserve(&self.canvas); self.observer.observe(&self.canvas); return; } let mut size = LogicalSize::new( backend::style_size_property(&self.style, "width"), backend::style_size_property(&self.style, "height"), ); if self.style.get_property_value("box-sizing").unwrap() == "border-box" { size.width -= backend::style_size_property(&self.style, "border-left-width") + backend::style_size_property(&self.style, "border-right-width") + backend::style_size_property(&self.style, "padding-left") + backend::style_size_property(&self.style, "padding-right"); size.height -= backend::style_size_property(&self.style, "border-top-width") + backend::style_size_property(&self.style, "border-bottom-width") + backend::style_size_property(&self.style, "padding-top") + backend::style_size_property(&self.style, "padding-bottom"); } let size = size.to_physical(backend::scale_factor(&self.window)); if self.notify_scale.replace(false) { let scale = backend::scale_factor(&self.window); (self.scale_handler)(size, scale) } else { (self.resize_handler)(size) } } fn handle_scale(this: Rc>, mql: &MediaQueryList) { let weak_self = Rc::downgrade(&this); let mut this = this.borrow_mut(); let scale = super::scale_factor(&this.window); // TODO: confirm/reproduce this problem, see: // . // This should never happen, but if it does then apparently the scale factor didn't change. if mql.matches() { warn!( "media query tracking scale factor was triggered without a change:\n\ Media Query: {}\n\ Current Scale: {scale}", mql.media(), ); return; } let new_mql = Self::create_mql(&this.window, move |mql| { if let Some(rc_self) = weak_self.upgrade() { Self::handle_scale(rc_self, mql); } }); this.mql = new_mql; this.notify_scale.set(true); this.notify(); } fn process_entry(&self, entries: Array) -> PhysicalSize { let entry: ResizeObserverEntry = entries.get(0).unchecked_into(); // Safari doesn't support `devicePixelContentBoxSize` if !has_device_pixel_support() { let rect = entry.content_rect(); return LogicalSize::new(rect.width(), rect.height()) .to_physical(backend::scale_factor(&self.window)); } let entry: ResizeObserverSize = entry .device_pixel_content_box_size() .get(0) .unchecked_into(); let writing_mode = self .style .get_property_value("writing-mode") .expect("`writing-mode` is a valid CSS property"); // means the canvas is not inserted into the DOM if writing_mode.is_empty() { debug_assert_eq!(entry.inline_size(), 0.); debug_assert_eq!(entry.block_size(), 0.); return PhysicalSize::new(0, 0); } let horizontal = match writing_mode.as_str() { _ if writing_mode.starts_with("horizontal") => true, _ if writing_mode.starts_with("vertical") | writing_mode.starts_with("sideways") => { false } // deprecated values "lr" | "lr-tb" | "rl" => true, "tb" | "tb-lr" | "tb-rl" => false, _ => { warn!("unrecognized `writing-mode`, assuming horizontal"); true } }; if horizontal { PhysicalSize::new(entry.inline_size() as u32, entry.block_size() as u32) } else { PhysicalSize::new(entry.block_size() as u32, entry.inline_size() as u32) } } } impl Drop for ResizeScaleInternal { fn drop(&mut self) { self.observer.disconnect(); } } // TODO: Remove when Safari supports `devicePixelContentBoxSize`. // See . pub fn has_device_pixel_support() -> bool { thread_local! { static DEVICE_PIXEL_SUPPORT: bool = { #[wasm_bindgen] extern "C" { type ResizeObserverEntryExt; #[wasm_bindgen(js_class = ResizeObserverEntry, static_method_of = ResizeObserverEntryExt, getter)] fn prototype() -> Object; } let prototype = ResizeObserverEntryExt::prototype(); let descriptor = Object::get_own_property_descriptor( &prototype, &JsValue::from_str("devicePixelContentBoxSize"), ); !descriptor.is_undefined() }; } DEVICE_PIXEL_SUPPORT.with(|support| *support) } winit-0.29.15/src/platform_impl/web/web_sys/schedule.rs000064400000000000000000000206241046102023000211660ustar 00000000000000use js_sys::{Function, Object, Promise, Reflect}; use once_cell::unsync::OnceCell; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort}; use crate::platform::web::PollStrategy; #[derive(Debug)] pub struct Schedule { _closure: Closure, inner: Inner, } #[derive(Debug)] enum Inner { Scheduler { controller: AbortController, }, IdleCallback { window: web_sys::Window, handle: u32, }, Timeout { window: web_sys::Window, handle: i32, port: MessagePort, _timeout_closure: Closure, }, } impl Schedule { pub fn new(strategy: PollStrategy, window: &web_sys::Window, f: F) -> Schedule where F: 'static + FnMut(), { if strategy == PollStrategy::Scheduler && has_scheduler_support(window) { Self::new_scheduler(window, f, None) } else if strategy == PollStrategy::IdleCallback && has_idle_callback_support(window) { Self::new_idle_callback(window.clone(), f) } else { Self::new_timeout(window.clone(), f, None) } } pub fn new_with_duration(window: &web_sys::Window, f: F, duration: Duration) -> Schedule where F: 'static + FnMut(), { if has_scheduler_support(window) { Self::new_scheduler(window, f, Some(duration)) } else { Self::new_timeout(window.clone(), f, Some(duration)) } } fn new_scheduler(window: &web_sys::Window, f: F, duration: Option) -> Schedule where F: 'static + FnMut(), { let window: &WindowSupportExt = window.unchecked_ref(); let scheduler = window.scheduler(); let closure = Closure::new(f); let mut options = SchedulerPostTaskOptions::new(); let controller = AbortController::new().expect("Failed to create `AbortController`"); options.signal(&controller.signal()); if let Some(duration) = duration { // `Duration::as_millis()` always rounds down (because of truncation), we want to round // up instead. This makes sure that the we never wake up **before** the given time. let duration = duration .as_secs() .checked_mul(1000) .and_then(|secs| secs.checked_add(duration_millis_ceil(duration).into())) .unwrap_or(u64::MAX); options.delay(duration as f64); } thread_local! { static REJECT_HANDLER: Closure = Closure::new(|_| ()); } REJECT_HANDLER.with(|handler| { let _ = scheduler .post_task_with_options(closure.as_ref().unchecked_ref(), &options) .catch(handler); }); Schedule { _closure: closure, inner: Inner::Scheduler { controller }, } } fn new_idle_callback(window: web_sys::Window, f: F) -> Schedule where F: 'static + FnMut(), { let closure = Closure::new(f); let handle = window .request_idle_callback(closure.as_ref().unchecked_ref()) .expect("Failed to request idle callback"); Schedule { _closure: closure, inner: Inner::IdleCallback { window, handle }, } } fn new_timeout(window: web_sys::Window, f: F, duration: Option) -> Schedule where F: 'static + FnMut(), { let channel = MessageChannel::new().unwrap(); let closure = Closure::new(f); let port_1 = channel.port1(); port_1.set_onmessage(Some(closure.as_ref().unchecked_ref())); port_1.start(); let port_2 = channel.port2(); let timeout_closure = Closure::new(move || { port_2 .post_message(&JsValue::UNDEFINED) .expect("Failed to send message") }); let handle = if let Some(duration) = duration { // `Duration::as_millis()` always rounds down (because of truncation), we want to round // up instead. This makes sure that the we never wake up **before** the given time. let duration = duration .as_secs() .try_into() .ok() .and_then(|secs: i32| secs.checked_mul(1000)) .and_then(|secs: i32| { let millis: i32 = duration_millis_ceil(duration) .try_into() .expect("millis are somehow bigger then 1K"); secs.checked_add(millis) }) .unwrap_or(i32::MAX); window.set_timeout_with_callback_and_timeout_and_arguments_0( timeout_closure.as_ref().unchecked_ref(), duration, ) } else { window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref()) } .expect("Failed to set timeout"); Schedule { _closure: closure, inner: Inner::Timeout { window, handle, port: port_1, _timeout_closure: timeout_closure, }, } } } impl Drop for Schedule { fn drop(&mut self) { match &self.inner { Inner::Scheduler { controller, .. } => controller.abort(), Inner::IdleCallback { window, handle, .. } => window.cancel_idle_callback(*handle), Inner::Timeout { window, handle, port, .. } => { window.clear_timeout_with_handle(*handle); port.close(); port.set_onmessage(None); } } } } // TODO: Replace with `u32::div_ceil()` when we hit Rust v1.73. fn duration_millis_ceil(duration: Duration) -> u32 { let micros = duration.subsec_micros(); // From . let d = micros / 1000; let r = micros % 1000; if r > 0 && 1000 > 0 { d + 1 } else { d } } fn has_scheduler_support(window: &web_sys::Window) -> bool { thread_local! { static SCHEDULER_SUPPORT: OnceCell = const { OnceCell::new() }; } SCHEDULER_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type SchedulerSupport; #[wasm_bindgen(method, getter, js_name = scheduler)] fn has_scheduler(this: &SchedulerSupport) -> JsValue; } let support: &SchedulerSupport = window.unchecked_ref(); !support.has_scheduler().is_undefined() }) }) } fn has_idle_callback_support(window: &web_sys::Window) -> bool { thread_local! { static IDLE_CALLBACK_SUPPORT: OnceCell = const { OnceCell::new() }; } IDLE_CALLBACK_SUPPORT.with(|support| { *support.get_or_init(|| { #[wasm_bindgen] extern "C" { type IdleCallbackSupport; #[wasm_bindgen(method, getter, js_name = requestIdleCallback)] fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue; } let support: &IdleCallbackSupport = window.unchecked_ref(); !support.has_request_idle_callback().is_undefined() }) }) } #[wasm_bindgen] extern "C" { type WindowSupportExt; #[wasm_bindgen(method, getter)] fn scheduler(this: &WindowSupportExt) -> Scheduler; type Scheduler; #[wasm_bindgen(method, js_name = postTask)] fn post_task_with_options( this: &Scheduler, callback: &Function, options: &SchedulerPostTaskOptions, ) -> Promise; type SchedulerPostTaskOptions; } impl SchedulerPostTaskOptions { fn new() -> Self { Object::new().unchecked_into() } fn delay(&mut self, val: f64) -> &mut Self { let r = Reflect::set(self, &JsValue::from("delay"), &val.into()); debug_assert!(r.is_ok(), "Failed to set `delay` property"); self } fn signal(&mut self, val: &AbortSignal) -> &mut Self { let r = Reflect::set(self, &JsValue::from("signal"), &val.into()); debug_assert!(r.is_ok(), "Failed to set `signal` property"); self } } winit-0.29.15/src/platform_impl/web/window.rs000064400000000000000000000317751046102023000172370ustar 00000000000000use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel, }; use crate::SendSyncWrapper; use web_sys::HtmlCanvasElement; use super::r#async::Dispatcher; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; pub struct Window { inner: Dispatcher, } pub struct Inner { id: WindowId, pub window: web_sys::Window, canvas: Rc>, previous_pointer: RefCell<&'static str>, destroy_fn: Option>, } impl Window { pub(crate) fn new( target: &EventLoopWindowTarget, attr: WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, ) -> Result { let id = target.generate_id(); let prevent_default = platform_attr.prevent_default; let window = target.runner.window(); let document = target.runner.document(); let canvas = backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?; let canvas = Rc::new(RefCell::new(canvas)); target.register(&canvas, id, prevent_default); let runner = target.runner.clone(); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); let inner = Inner { id, window: window.clone(), canvas, previous_pointer: RefCell::new("auto"), destroy_fn: Some(destroy_fn), }; inner.set_title(&attr.title); inner.set_maximized(attr.maximized); inner.set_visible(attr.visible); inner.set_window_icon(attr.window_icon); let canvas = Rc::downgrade(&inner.canvas); let (dispatcher, runner) = Dispatcher::new(inner).unwrap(); target.runner.add_canvas(RootWI(id), canvas, runner); Ok(Window { inner: dispatcher }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) { self.inner.dispatch(f) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Inner) -> R + Send) -> R { self.inner.queue(f) } pub fn canvas(&self) -> Option { self.inner .value() .map(|inner| inner.canvas.borrow().raw().clone()) } } impl Inner { pub fn set_title(&self, title: &str) { self.canvas.borrow().set_attribute("alt", title) } pub fn set_transparent(&self, _transparent: bool) {} pub fn set_blur(&self, _blur: bool) {} pub fn set_visible(&self, _visible: bool) { // Intentionally a no-op } #[inline] pub fn is_visible(&self) -> Option { None } pub fn request_redraw(&self) { self.canvas.borrow().request_animation_frame(); } pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { Ok(self .canvas .borrow() .position() .to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { // Note: the canvas element has no window decorations, so this is equal to `outer_position`. self.outer_position() } pub fn set_outer_position(&self, position: Position) { let canvas = self.canvas.borrow(); let position = position.to_logical::(self.scale_factor()); backend::set_canvas_position(canvas.document(), canvas.raw(), canvas.style(), position) } #[inline] pub fn inner_size(&self) -> PhysicalSize { self.canvas.borrow().inner_size() } #[inline] pub fn outer_size(&self) -> PhysicalSize { // Note: the canvas element has no window decorations, so this is equal to `inner_size`. self.inner_size() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let size = size.to_logical(self.scale_factor()); let canvas = self.canvas.borrow(); backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); None } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); let canvas = self.canvas.borrow(); backend::set_canvas_min_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { let dimensions = dimensions.map(|dimensions| dimensions.to_logical(self.scale_factor())); let canvas = self.canvas.borrow(); backend::set_canvas_max_size(canvas.document(), canvas.raw(), canvas.style(), dimensions) } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) { // Intentionally a no-op: users can't resize canvas elements } #[inline] pub fn set_resizable(&self, _resizable: bool) { // Intentionally a no-op: users can't resize canvas elements } pub fn is_resizable(&self) -> bool { true } #[inline] pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} #[inline] pub fn enabled_buttons(&self) -> WindowButtons { WindowButtons::all() } #[inline] pub fn scale_factor(&self) -> f64 { super::backend::scale_factor(&self.window) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { *self.previous_pointer.borrow_mut() = cursor.name(); backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name()); } #[inline] pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let lock = match mode { CursorGrabMode::None => false, CursorGrabMode::Locked => true, CursorGrabMode::Confined => { return Err(ExternalError::NotSupported(NotSupportedError::new())) } }; self.canvas .borrow() .set_cursor_lock(lock) .map_err(ExternalError::Os) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { if !visible { backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none"); } else { backend::set_canvas_style_property( self.canvas.borrow().raw(), "cursor", &self.previous_pointer.borrow(), ); } } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn show_window_menu(&self, _position: Position) {} #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_minimized(&self, _minimized: bool) { // Intentionally a no-op, as canvases cannot be 'minimized' } #[inline] pub fn is_minimized(&self) -> Option { // Canvas cannot be 'minimized' Some(false) } #[inline] pub fn set_maximized(&self, _maximized: bool) { // Intentionally a no-op, as canvases cannot be 'maximized' } #[inline] pub fn is_maximized(&self) -> bool { // Canvas cannot be 'maximized' false } #[inline] pub(crate) fn fullscreen(&self) -> Option { if self.canvas.borrow().is_fullscreen() { Some(Fullscreen::Borderless(None)) } else { None } } #[inline] pub(crate) fn set_fullscreen(&self, fullscreen: Option) { let canvas = &self.canvas.borrow(); if fullscreen.is_some() { canvas.request_fullscreen(); } else { canvas.exit_fullscreen() } } #[inline] pub fn set_decorations(&self, _decorations: bool) { // Intentionally a no-op, no canvas decorations } pub fn is_decorated(&self) -> bool { true } #[inline] pub fn set_window_level(&self, _level: WindowLevel) { // Intentionally a no-op, no window ordering } #[inline] pub fn set_window_icon(&self, _window_icon: Option) { // Currently an intentional no-op } #[inline] pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) { // Currently a no-op as it does not seem there is good support for this on web } #[inline] pub fn set_ime_allowed(&self, _allowed: bool) { // Currently not implemented } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) { // Currently not implemented } #[inline] pub fn focus_window(&self) { let _ = self.canvas.borrow().raw().focus(); } #[inline] pub fn request_user_attention(&self, _request_type: Option) { // Currently an intentional no-op } #[inline] pub fn current_monitor(&self) -> Option { None } #[inline] pub fn available_monitors(&self) -> VecDeque { VecDeque::new() } #[inline] pub fn primary_monitor(&self) -> Option { None } #[inline] pub fn id(&self) -> WindowId { self.id } #[cfg(feature = "rwh_04")] #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::WebHandle::empty(); window_handle.id = self.id.0; rwh_04::RawWindowHandle::Web(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::WebWindowHandle::empty(); window_handle.id = self.id.0; rwh_05::RawWindowHandle::Web(window_handle) } #[cfg(feature = "rwh_05")] #[inline] pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { rwh_05::RawDisplayHandle::Web(rwh_05::WebDisplayHandle::empty()) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_window_handle_rwh_06(&self) -> Result { let window_handle = rwh_06::WebWindowHandle::new(self.id.0); Ok(rwh_06::RawWindowHandle::Web(window_handle)) } #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Web( rwh_06::WebDisplayHandle::new(), )) } #[inline] pub fn set_theme(&self, _theme: Option) {} #[inline] pub fn theme(&self) -> Option { backend::is_dark_mode(&self.window).map(|is_dark_mode| { if is_dark_mode { Theme::Dark } else { Theme::Light } }) } pub fn set_content_protected(&self, _protected: bool) {} #[inline] pub fn has_focus(&self) -> bool { self.canvas.borrow().has_focus.get() } pub fn title(&self) -> String { String::new() } pub fn reset_dead_keys(&self) { // Not supported } } impl Drop for Inner { fn drop(&mut self) { if let Some(destroy_fn) = self.destroy_fn.take() { destroy_fn(); } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(pub(crate) u32); impl WindowId { pub const unsafe fn dummy() -> Self { Self(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as u32) } } #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub(crate) canvas: SendSyncWrapper>, pub(crate) prevent_default: bool, pub(crate) focusable: bool, pub(crate) append: bool, } impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { canvas: SendSyncWrapper(None), prevent_default: true, focusable: true, append: false, } } } winit-0.29.15/src/platform_impl/windows/dark_mode.rs000064400000000000000000000127121046102023000205600ustar 00000000000000/// 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 std::{ffi::c_void, ptr}; use once_cell::sync::Lazy; use windows_sys::{ core::PCSTR, Win32::{ Foundation::{BOOL, HWND, NTSTATUS, S_OK}, System::{ LibraryLoader::{GetProcAddress, LoadLibraryA}, SystemInformation::OSVERSIONINFOW, }, UI::{ Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA}, Controls::SetWindowTheme, WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST}, }, }, }; use crate::window::Theme; use super::util; static WIN10_BUILD_VERSION: Lazy> = Lazy::new(|| { type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; let handle = get_function!("ntdll.dll", RtlGetVersion); if let Some(rtl_get_version) = handle { unsafe { let mut vi = OSVERSIONINFOW { dwOSVersionInfoSize: 0, dwMajorVersion: 0, dwMinorVersion: 0, dwBuildNumber: 0, dwPlatformId: 0, szCSDVersion: [0; 128], }; let status = (rtl_get_version)(&mut vi); if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { Some(vi.dwBuildNumber) } else { None } } } else { None } }); static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) match *WIN10_BUILD_VERSION { Some(v) => v >= 17763, None => false, } }); static DARK_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("DarkMode_Explorer")); static LIGHT_THEME_NAME: Lazy> = Lazy::new(|| util::encode_wide("")); /// Attempt to set a theme on a window, if necessary. /// Returns the theme that was picked pub fn try_theme(hwnd: HWND, preferred_theme: Option) -> Theme { if *DARK_MODE_SUPPORTED { let is_dark_mode = match preferred_theme { Some(theme) => theme == Theme::Dark, None => should_use_dark_mode(), }; let theme = if is_dark_mode { Theme::Dark } else { Theme::Light }; let theme_name = match theme { Theme::Dark => DARK_THEME_NAME.as_ptr(), Theme::Light => LIGHT_THEME_NAME.as_ptr(), }; let status = unsafe { SetWindowTheme(hwnd, theme_name, ptr::null()) }; if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) { return theme; } } Theme::Light } fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { // Uses Windows undocumented API SetWindowCompositionAttribute, // as seen in win32-darkmode example linked at top of file. type SetWindowCompositionAttribute = unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; #[allow(clippy::upper_case_acronyms)] type WINDOWCOMPOSITIONATTRIB = u32; const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26; #[allow(non_snake_case)] #[allow(clippy::upper_case_acronyms)] #[repr(C)] struct WINDOWCOMPOSITIONATTRIBDATA { Attrib: WINDOWCOMPOSITIONATTRIB, pvData: *mut c_void, cbData: usize, } static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy> = Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute)); if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE { unsafe { // SetWindowCompositionAttribute needs a bigbool (i32), not bool. let mut is_dark_mode_bigbool = BOOL::from(is_dark_mode); let mut data = WINDOWCOMPOSITIONATTRIBDATA { Attrib: WCA_USEDARKMODECOLORS, pvData: &mut is_dark_mode_bigbool as *mut _ as _, cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _, }; let status = set_window_composition_attribute(hwnd, &mut data); status != false.into() } } else { false } } fn should_use_dark_mode() -> bool { should_apps_use_dark_mode() && !is_high_contrast() } fn should_apps_use_dark_mode() -> bool { type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); if module == 0 { return None; } let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL); handle.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 is_high_contrast() -> bool { let mut hc = HIGHCONTRASTA { cbSize: 0, dwFlags: 0, lpszDefaultScheme: ptr::null_mut(), }; let ok = unsafe { SystemParametersInfoA( SPI_GETHIGHCONTRAST, std::mem::size_of_val(&hc) as _, &mut hc as *mut _ as _, 0, ) }; ok != false.into() && util::has_flag(hc.dwFlags, HCF_HIGHCONTRASTON) } winit-0.29.15/src/platform_impl/windows/definitions.rs000064400000000000000000000106331046102023000211460ustar 00000000000000#![allow(non_snake_case)] #![allow(non_upper_case_globals)] use std::ffi::c_void; use windows_sys::{ core::{IUnknown, GUID, HRESULT}, Win32::{ Foundation::{BOOL, HWND, POINTL}, System::Com::{ IAdviseSink, IDataObject, IEnumFORMATETC, IEnumSTATDATA, FORMATETC, STGMEDIUM, }, }, }; #[repr(C)] pub struct IUnknownVtbl { pub QueryInterface: unsafe extern "system" fn( This: *mut IUnknown, riid: *const GUID, ppvObject: *mut *mut c_void, ) -> HRESULT, pub AddRef: unsafe extern "system" fn(This: *mut IUnknown) -> u32, pub Release: unsafe extern "system" fn(This: *mut IUnknown) -> u32, } #[repr(C)] pub struct IDataObjectVtbl { pub parent: IUnknownVtbl, pub GetData: unsafe extern "system" fn( This: *mut IDataObject, pformatetcIn: *const FORMATETC, pmedium: *mut STGMEDIUM, ) -> HRESULT, pub GetDataHere: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, pmedium: *mut STGMEDIUM, ) -> HRESULT, QueryGetData: unsafe extern "system" fn(This: *mut IDataObject, pformatetc: *const FORMATETC) -> HRESULT, pub GetCanonicalFormatEtc: unsafe extern "system" fn( This: *mut IDataObject, pformatetcIn: *const FORMATETC, pformatetcOut: *mut FORMATETC, ) -> HRESULT, pub SetData: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, pformatetcOut: *const FORMATETC, fRelease: BOOL, ) -> HRESULT, pub EnumFormatEtc: unsafe extern "system" fn( This: *mut IDataObject, dwDirection: u32, ppenumFormatEtc: *mut *mut IEnumFORMATETC, ) -> HRESULT, pub DAdvise: unsafe extern "system" fn( This: *mut IDataObject, pformatetc: *const FORMATETC, advf: u32, pAdvSInk: *const IAdviseSink, pdwConnection: *mut u32, ) -> HRESULT, pub DUnadvise: unsafe extern "system" fn(This: *mut IDataObject, dwConnection: u32) -> HRESULT, pub EnumDAdvise: unsafe extern "system" fn( This: *mut IDataObject, ppenumAdvise: *const *const IEnumSTATDATA, ) -> HRESULT, } #[repr(C)] pub struct IDropTargetVtbl { pub parent: IUnknownVtbl, pub DragEnter: unsafe extern "system" fn( This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragOver: unsafe extern "system" fn( This: *mut IDropTarget, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT, pub Drop: unsafe extern "system" fn( This: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: u32, pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT, } #[repr(C)] pub struct IDropTarget { pub lpVtbl: *const IDropTargetVtbl, } #[repr(C)] pub struct ITaskbarListVtbl { pub parent: IUnknownVtbl, pub HrInit: unsafe extern "system" fn(This: *mut ITaskbarList) -> HRESULT, pub AddTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub DeleteTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub ActivateTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, pub SetActiveAlt: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT, } #[repr(C)] pub struct ITaskbarList { pub lpVtbl: *const ITaskbarListVtbl, } #[repr(C)] pub struct ITaskbarList2Vtbl { pub parent: ITaskbarListVtbl, pub MarkFullscreenWindow: unsafe extern "system" fn( This: *mut ITaskbarList2, hwnd: HWND, fFullscreen: BOOL, ) -> HRESULT, } #[repr(C)] pub struct ITaskbarList2 { pub lpVtbl: *const ITaskbarList2Vtbl, } pub const CLSID_TaskbarList: GUID = GUID { data1: 0x56fdf344, data2: 0xfd6d, data3: 0x11d0, data4: [0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90], }; pub const IID_ITaskbarList: GUID = GUID { data1: 0x56FDF342, data2: 0xFD6D, data3: 0x11D0, data4: [0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90], }; pub const IID_ITaskbarList2: GUID = GUID { data1: 0x602d4995, data2: 0xb13a, data3: 0x429b, data4: [0xa6, 0x6e, 0x19, 0x35, 0xe4, 0x4f, 0x43, 0x17], }; winit-0.29.15/src/platform_impl/windows/dpi.rs000064400000000000000000000103731046102023000174100ustar 00000000000000#![allow(non_snake_case, unused_unsafe)] use std::sync::Once; use windows_sys::Win32::{ Foundation::{HWND, S_OK}, Graphics::Gdi::{ GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST, }, UI::{ HiDpi::{ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE, }, WindowsAndMessaging::IsProcessDPIAware, }, }; 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) == false.into() { // V2 only works with Windows 10 Creators Update (1703). Try using the older // V1 if we can't set V2. SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); } } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { // We are on Windows 8.1 or later. SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { // We are on Vista or later. SetProcessDPIAware(); } } }); } pub fn enable_non_client_dpi_scaling(hwnd: HWND) { unsafe { if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { 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) == S_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 const BASE_DPI: u32 = 96; pub fn dpi_to_scale_factor(dpi: u32) -> f64 { dpi as f64 / BASE_DPI as f64 } pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { let hdc = unsafe { GetDC(hwnd) }; if hdc == 0 { panic!("[winit] `GetDC` returned null!"); } if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { // We are on Windows 10 Anniversary Update (1607) or later. match unsafe { GetDpiForWindow(hwnd) } { 0 => BASE_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 = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }; if monitor == 0 { return BASE_DPI; } let mut dpi_x = 0; let mut dpi_y = 0; if unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) } == S_OK { dpi_x } else { BASE_DPI } } else { // We are on Vista or later. if unsafe { IsProcessDPIAware() } != false.into() { // If the process is DPI aware, then scaling must be handled by the application using // this DPI value. unsafe { GetDeviceCaps(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. BASE_DPI } } } winit-0.29.15/src/platform_impl/windows/drop_handler.rs000064400000000000000000000202001046102023000212630ustar 00000000000000use std::{ ffi::{c_void, OsString}, os::windows::ffi::OsStringExt, path::PathBuf, ptr, sync::atomic::{AtomicUsize, Ordering}, }; use windows_sys::{ core::{IUnknown, GUID, HRESULT}, Win32::{ Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK}, System::{ Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE}, }, UI::Shell::{DragFinish, DragQueryFileW, HDROP}, }, }; use crate::platform_impl::platform::{ definitions::{IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl}, WindowId, }; use crate::{event::Event, window::WindowId as RootWindowId}; #[repr(C)] pub struct FileDropHandlerData { pub interface: IDropTarget, refcount: AtomicUsize, window: HWND, send_event: Box)>, cursor_effect: u32, hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ } pub struct FileDropHandler { pub data: *mut FileDropHandlerData, } #[allow(non_snake_case)] impl FileDropHandler { pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { let data = Box::new(FileDropHandlerData { interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl, }, refcount: AtomicUsize::new(1), window, send_event, cursor_effect: DROPEFFECT_NONE, hovered_is_valid: false, }); FileDropHandler { data: Box::into_raw(data), } } // Implement IUnknown pub unsafe extern "system" fn QueryInterface( _this: *mut IUnknown, _riid: *const GUID, _ppvObject: *mut *mut c_void, ) -> HRESULT { // This function doesn't appear to be required for an `IDropTarget`. // An implementation would be nice however. unimplemented!(); } pub unsafe extern "system" fn AddRef(this: *mut IUnknown) -> u32 { let drop_handler_data = unsafe { Self::from_interface(this) }; let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1; count as u32 } pub unsafe extern "system" fn Release(this: *mut IUnknown) -> u32 { let drop_handler = unsafe { Self::from_interface(this) }; let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1; if count == 0 { // Destroy the underlying data drop(unsafe { Box::from_raw(drop_handler as *mut FileDropHandlerData) }); } count as u32 } pub unsafe extern "system" fn DragEnter( this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, _pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT { use crate::event::WindowEvent::HoveredFile; let drop_handler = unsafe { Self::from_interface(this) }; let hdrop = unsafe { Self::iterate_filenames(pDataObj, |filename| { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: HoveredFile(filename), }); }) }; drop_handler.hovered_is_valid = hdrop.is_some(); drop_handler.cursor_effect = if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE }; unsafe { *pdwEffect = drop_handler.cursor_effect; } S_OK } pub unsafe extern "system" fn DragOver( this: *mut IDropTarget, _grfKeyState: u32, _pt: *const POINTL, pdwEffect: *mut u32, ) -> HRESULT { let drop_handler = unsafe { Self::from_interface(this) }; unsafe { *pdwEffect = drop_handler.cursor_effect; } S_OK } pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT { use crate::event::WindowEvent::HoveredFileCancelled; let drop_handler = unsafe { Self::from_interface(this) }; if drop_handler.hovered_is_valid { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: HoveredFileCancelled, }); } S_OK } pub unsafe extern "system" fn Drop( this: *mut IDropTarget, pDataObj: *const IDataObject, _grfKeyState: u32, _pt: *const POINTL, _pdwEffect: *mut u32, ) -> HRESULT { use crate::event::WindowEvent::DroppedFile; let drop_handler = unsafe { Self::from_interface(this) }; let hdrop = unsafe { Self::iterate_filenames(pDataObj, |filename| { drop_handler.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(drop_handler.window)), event: DroppedFile(filename), }); }) }; if let Some(hdrop) = hdrop { unsafe { DragFinish(hdrop) }; } S_OK } unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData { unsafe { &mut *(this as *mut _) } } unsafe fn iterate_filenames(data_obj: *const IDataObject, callback: F) -> Option where F: Fn(PathBuf), { let drop_format = FORMATETC { cfFormat: CF_HDROP, ptd: ptr::null_mut(), dwAspect: DVASPECT_CONTENT, lindex: -1, tymed: TYMED_HGLOBAL as u32, }; let mut medium = unsafe { std::mem::zeroed() }; let get_data_fn = unsafe { (*(*data_obj).cast::()).GetData }; let get_data_result = unsafe { get_data_fn(data_obj as *mut _, &drop_format, &mut medium) }; if get_data_result >= 0 { let hdrop = unsafe { medium.Anonymous.hGlobal }; // The second parameter (0xFFFFFFFF) instructs the function to return the item count let item_count = unsafe { DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0) }; 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 = unsafe { DragQueryFileW(hdrop, i, ptr::null_mut(), 0) 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); unsafe { DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as u32); path_buf.set_len(str_len); } callback(OsString::from_wide(&path_buf[0..character_count]).into()); } Some(hdrop) } else if get_data_result == 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. debug!("Error occured while processing dropped/hovered item: item is not a file."); None } else { debug!("Unexpected error occured while processing dropped/hovered item."); None } } } impl FileDropHandlerData { fn send_event(&self, event: Event<()>) { (self.send_event)(event); } } impl Drop for FileDropHandler { fn drop(&mut self) { unsafe { FileDropHandler::Release(self.data as *mut IUnknown); } } } static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { parent: IUnknownVtbl { QueryInterface: FileDropHandler::QueryInterface, AddRef: FileDropHandler::AddRef, Release: FileDropHandler::Release, }, DragEnter: FileDropHandler::DragEnter, DragOver: FileDropHandler::DragOver, DragLeave: FileDropHandler::DragLeave, Drop: FileDropHandler::Drop, }; winit-0.29.15/src/platform_impl/windows/event_loop/runner.rs000064400000000000000000000355151046102023000223240ustar 00000000000000use std::{ any::Any, cell::{Cell, RefCell}, collections::VecDeque, mem, panic, rc::Rc, sync::{Arc, Mutex}, time::Instant, }; use windows_sys::Win32::Foundation::HWND; use crate::{ dpi::PhysicalSize, event::{Event, InnerSizeWriter, StartCause, WindowEvent}, platform_impl::platform::{ event_loop::{WindowData, GWL_USERDATA}, get_window_long, }, window::WindowId, }; use super::ControlFlow; pub(crate) type EventLoopRunnerShared = Rc>; type EventHandler = Cell)>>>; pub(crate) struct EventLoopRunner { // The event loop's win32 handles pub(super) thread_msg_target: HWND, // Setting this will ensure pump_events will return to the external // loop asap. E.g. set after each RedrawRequested to ensure pump_events // can't stall an external loop beyond a frame pub(super) interrupt_msg_dispatch: Cell, control_flow: Cell, exit: Cell>, runner_state: Cell, last_events_cleared: Cell, event_handler: EventHandler, event_buffer: RefCell>>, 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)] pub(crate) 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 `AboutToWait` hasn't. HandlingMainEvents, /// The event loop has been destroyed. No other events will be emitted. Destroyed, } enum BufferedEvent { Event(Event), ScaleFactorChanged(WindowId, f64, PhysicalSize), } impl EventLoopRunner { pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner { EventLoopRunner { thread_msg_target, interrupt_msg_dispatch: Cell::new(false), runner_state: Cell::new(RunnerState::Uninitialized), control_flow: Cell::new(ControlFlow::default()), exit: Cell::new(None), panic_error: Cell::new(None), last_events_cleared: Cell::new(Instant::now()), event_handler: Cell::new(None), event_buffer: RefCell::new(VecDeque::new()), } } /// Associate the application's event handler with the runner /// /// # Safety /// This is ignoring the lifetime of the application handler (which may not /// outlive the EventLoopRunner) and can lead to undefined behaviour if /// the handler is not cleared before the end of real lifetime. /// /// All public APIs that take an event handler (`run`, `run_on_demand`, /// `pump_events`) _must_ pair a call to `set_event_handler` with /// a call to `clear_event_handler` before returning to avoid /// undefined behaviour. pub(crate) unsafe fn set_event_handler(&self, f: F) where F: FnMut(Event), { // Erase closure lifetime. // SAFETY: Caller upholds that the lifetime of the closure is upheld. let f = unsafe { mem::transmute::)>, Box)>>(Box::new(f)) }; let old_event_handler = self.event_handler.replace(Some(f)); assert!(old_event_handler.is_none()); } pub(crate) fn clear_event_handler(&self) { self.event_handler.set(None); } pub(crate) fn reset_runner(&self) { let EventLoopRunner { thread_msg_target: _, interrupt_msg_dispatch, runner_state, panic_error, control_flow: _, exit, last_events_cleared: _, event_handler, event_buffer: _, } = self; interrupt_msg_dispatch.set(false); runner_state.set(RunnerState::Uninitialized); panic_error.set(None); exit.set(None); event_handler.set(None); } } /// State retrieval functions. impl EventLoopRunner { #[allow(unused)] pub fn thread_msg_target(&self) -> HWND { self.thread_msg_target } pub fn take_panic_error(&self) -> Result<(), PanicError> { match self.panic_error.take() { Some(err) => Err(err), None => Ok(()), } } pub fn state(&self) -> RunnerState { self.runner_state.get() } pub fn set_control_flow(&self, control_flow: ControlFlow) { self.control_flow.set(control_flow) } pub fn control_flow(&self) -> ControlFlow { self.control_flow.get() } pub fn set_exit_code(&self, code: i32) { self.exit.set(Some(code)) } pub fn exit_code(&self) -> Option { self.exit.get() } pub fn clear_exit(&self) { self.exit.set(None); } 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 } } } /// Event dispatch functions. impl EventLoopRunner { pub(crate) fn prepare_wait(&self) { self.move_state_to(RunnerState::Idle); } pub(crate) fn wakeup(&self) { self.move_state_to(RunnerState::HandlingMainEvents); } pub(crate) fn send_event(&self, event: Event) { if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event { self.call_event_handler(event); // As a rule, to ensure that `pump_events` can't block an external event loop // for too long, we always guarantee that `pump_events` will return control to // the external loop asap after a `RedrawRequested` event is dispatched. self.interrupt_msg_dispatch.set(true); } 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.call_event_handler(event); self.dispatch_buffered_events(); } } pub(crate) fn loop_destroyed(&self) { self.move_state_to(RunnerState::Destroyed); } fn call_event_handler(&self, event: Event) { self.catch_unwind(|| { let mut event_handler = self.event_handler.take() .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); event_handler(event); assert!(self.event_handler.replace(Some(event_handler)).is_none()); }); } 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`, `AboutToWait`, and /// `LoopExiting`) as necessary to bring the internal `RunnerState` to the /// new runner state. /// /// The state transitions are defined as follows: /// /// ```text /// Uninitialized /// | /// V /// Idle /// ^ | /// | V /// HandlingMainEvents /// | /// V /// Destroyed /// ``` /// /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to /// transition *from* `Destroyed` will also result 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. fn move_state_to(&self, new_runner_state: RunnerState) { use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized}; match ( self.runner_state.replace(new_runner_state), new_runner_state, ) { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { self.call_new_events(true); } (Uninitialized, Idle) => { self.call_new_events(true); self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); } (Uninitialized, Destroyed) => { self.call_new_events(true); self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); self.call_event_handler(Event::LoopExiting); } (_, Uninitialized) => panic!("cannot move state to Uninitialized"), // State transitions that start the event handling process. (Idle, HandlingMainEvents) => { self.call_new_events(false); } (Idle, Destroyed) => { self.call_event_handler(Event::LoopExiting); } (HandlingMainEvents, Idle) => { // This is always the last event we dispatch before waiting for new events self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); } (HandlingMainEvents, Destroyed) => { self.call_event_handler(Event::AboutToWait); self.last_events_cleared.set(Instant::now()); self.call_event_handler(Event::LoopExiting); } (Destroyed, _) => panic!("cannot move state from Destroyed"), } } fn call_new_events(&self, init: bool) { let start_cause = match (init, self.control_flow(), self.exit.get()) { (true, _, _) => StartCause::Init, (false, ControlFlow::Poll, None) => StartCause::Poll, (false, _, Some(_)) | (false, ControlFlow::Wait, None) => StartCause::WaitCancelled { requested_resume: None, start: self.last_events_cleared.get(), }, (false, ControlFlow::WaitUntil(requested_resume), None) => { 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)); // NB: For consistency all platforms must emit a 'resumed' event even though Windows // applications don't themselves have a formal suspend/resume lifecycle. if init { self.call_event_handler(Event::Resumed); } self.dispatch_buffered_events(); } } impl BufferedEvent { pub fn from_event(event: Event) -> BufferedEvent { match event { Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer, }, window_id, } => BufferedEvent::ScaleFactorChanged( window_id, scale_factor, *inner_size_writer .new_inner_size .upgrade() .unwrap() .lock() .unwrap(), ), event => BufferedEvent::Event(event), } } pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) { match self { Self::Event(event) => dispatch(event), Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => { let user_new_innner_size = Arc::new(Mutex::new(new_inner_size)); dispatch(Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade( &user_new_innner_size, )), }, }); let inner_size = *user_new_innner_size.lock().unwrap(); drop(user_new_innner_size); if inner_size != new_inner_size { let window_flags = unsafe { let userdata = get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData; (*userdata).window_state_lock().window_flags }; window_flags.set_size((window_id.0).0, inner_size); } } } } } winit-0.29.15/src/platform_impl/windows/event_loop.rs000064400000000000000000002767261046102023000210260ustar 00000000000000#![allow(non_snake_case)] mod runner; use std::{ cell::Cell, collections::VecDeque, ffi::c_void, marker::PhantomData, mem, panic, ptr, rc::Rc, sync::{ atomic::{AtomicU32, Ordering}, mpsc::{self, Receiver, Sender}, Arc, Mutex, MutexGuard, }, time::{Duration, Instant}, }; use once_cell::sync::Lazy; use windows_sys::Win32::{ Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, Graphics::Gdi::{ GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE, }, System::{ Ole::RevokeDragDrop, Threading::{GetCurrentThreadId, INFINITE}, }, UI::{ Controls::{HOVER_DEFAULT, WM_MOUSELEAVE}, Input::{ Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW}, KeyboardAndMouse::{ ReleaseCapture, SetCapture, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }, Pointer::{POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE}, Touch::{ CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE, TOUCHEVENTF_UP, TOUCHINPUT, }, RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos, GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, }, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, error::EventLoopError, event::{ DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, WindowEvent, }, event_loop::{ControlFlow, DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW}, keyboard::ModifiersState, platform::pump_events::PumpStatus, platform_impl::platform::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor}, drop_handler::FileDropHandler, ime::ImeContext, keyboard::KeyEventBuilder, keyboard_layout::LAYOUT_CACHE, monitor::{self, MonitorHandle}, raw_input, util, window::InitData, window_state::{CursorFlags, ImeState, WindowFlags, WindowState}, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }, window::WindowId as RootWindowId, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; use self::runner::RunnerState; use super::window::set_skip_taskbar; 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, } impl WindowData { fn send_event(&self, event: Event) { self.event_loop_runner.send_event(event); } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } } struct ThreadMsgTargetData { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, } impl ThreadMsgTargetData { fn send_event(&self, event: Event) { self.event_loop_runner.send_event(event); } } /// The result of a subclass procedure (the message handling callback) #[derive(Clone, Copy)] pub(crate) enum ProcResult { DefWindowProc(WPARAM), Value(isize), } 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>>, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { Self { any_thread: false, dpi_aware: true, msg_hook: None, } } } pub struct EventLoopWindowTarget { thread_id: u32, thread_msg_target: HWND, pub(crate) runner_shared: EventLoopRunnerShared, } impl EventLoop { pub(crate) fn new( attributes: &mut PlatformSpecificEventLoopAttributes, ) -> Result { 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::(); let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target)); 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(), ); Ok(EventLoop { thread_msg_sender, window_target: RootELW { p: EventLoopWindowTarget { thread_id, thread_msg_target, runner_shared, }, _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) -> Result<(), EventLoopError> where F: FnMut(Event, &RootELW), { self.run_on_demand(event_handler) } pub fn run_on_demand(&mut self, mut event_handler: F) -> Result<(), EventLoopError> where F: FnMut(Event, &RootELW), { { let runner = &self.window_target.p.runner_shared; if runner.state() != RunnerState::Uninitialized { return Err(EventLoopError::AlreadyRunning); } let event_loop_windows_ref = &self.window_target; // # Safety // We make sure to call runner.clear_event_handler() before // returning unsafe { runner.set_event_handler(move |event| event_handler(event, event_loop_windows_ref)); } } let exit_code = loop { self.wait_and_dispatch_message(None); if let Some(code) = self.exit_code() { break code; } self.dispatch_peeked_messages(); if let Some(code) = self.exit_code() { break code; } }; let runner = &self.window_target.p.runner_shared; runner.loop_destroyed(); // # Safety // We assume that this will effectively call `runner.clear_event_handler()` // to meet the safety requirements for calling `runner.set_event_handler()` above. runner.reset_runner(); if exit_code == 0 { Ok(()) } else { Err(EventLoopError::ExitFailure(exit_code)) } } pub fn pump_events(&mut self, timeout: Option, mut event_handler: F) -> PumpStatus where F: FnMut(Event, &RootELW), { { let runner = &self.window_target.p.runner_shared; let event_loop_windows_ref = &self.window_target; // # Safety // We make sure to call runner.clear_event_handler() before // returning // // Note: we're currently assuming nothing can panic and unwind // to leave the runner in an unsound state with an associated // event handler. unsafe { runner.set_event_handler(move |event| event_handler(event, event_loop_windows_ref)); runner.wakeup(); } } self.wait_and_dispatch_message(timeout); if self.exit_code().is_none() { self.dispatch_peeked_messages(); } let runner = &self.window_target.p.runner_shared; let status = if let Some(code) = runner.exit_code() { runner.loop_destroyed(); // Immediately reset the internal state for the loop to allow // the loop to be run more than once. runner.reset_runner(); PumpStatus::Exit(code) } else { runner.prepare_wait(); PumpStatus::Continue }; // We wait until we've checked for an exit status before clearing the // application callback, in case we need to dispatch a LoopExiting event // // # Safety // This pairs up with our call to `runner.set_event_handler` and ensures // the application's callback can't be held beyond its lifetime. runner.clear_event_handler(); status } /// Wait for one message and dispatch it, optionally with a timeout fn wait_and_dispatch_message(&mut self, timeout: Option) { fn get_msg_with_timeout(msg: &mut MSG, timeout: Option) -> PumpStatus { unsafe { // A timeout of None means wait indefinitely (so we don't need to call SetTimer) let timer_id = timeout.map(|timeout| SetTimer(0, 0, dur2timeout(timeout), None)); let get_status = GetMessageW(msg, 0, 0, 0); if let Some(timer_id) = timer_id { KillTimer(0, timer_id); } // A return value of 0 implies `WM_QUIT` if get_status == 0 { PumpStatus::Exit(0) } else { PumpStatus::Continue } } } /// Fetch the next MSG either via PeekMessage or GetMessage depending on whether the /// requested timeout is `ZERO` (and so we don't want to block) /// /// Returns `None` if if no MSG was read, else a `Continue` or `Exit` status fn wait_for_msg(msg: &mut MSG, timeout: Option) -> Option { if timeout == Some(Duration::ZERO) { unsafe { if PeekMessageW(msg, 0, 0, 0, PM_REMOVE) != 0 { Some(PumpStatus::Continue) } else { None } } } else { Some(get_msg_with_timeout(msg, timeout)) } } let runner = &self.window_target.p.runner_shared; // We aim to be consistent with the MacOS backend which has a RunLoop // observer that will dispatch AboutToWait when about to wait for // events, and NewEvents after the RunLoop wakes up. // // We emulate similar behaviour by treating `GetMessage` as our wait // point and wake up point (when it returns) and we drain all other // pending messages via `PeekMessage` until we come back to "wait" via // `GetMessage` // runner.prepare_wait(); let control_flow_timeout = match runner.control_flow() { ControlFlow::Wait => None, ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::WaitUntil(wait_deadline) => { let start = Instant::now(); Some(wait_deadline.saturating_duration_since(start)) } }; let timeout = min_timeout(control_flow_timeout, timeout); // # Safety // The Windows API has no documented requirement for bitwise // initializing a `MSG` struct (it can be uninitialized memory for the C // API) and there's no API to construct or initialize a `MSG`. This // is the simplest way avoid unitialized memory in Rust let mut msg = unsafe { mem::zeroed() }; let msg_status = wait_for_msg(&mut msg, timeout); // Before we potentially exit, make sure to consistently emit an event for the wake up runner.wakeup(); match msg_status { None => {} // No MSG to dispatch Some(PumpStatus::Exit(code)) => { runner.set_exit_code(code); } Some(PumpStatus::Continue) => { unsafe { let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { callback(&mut msg as *mut _ as *mut _) } else { false }; if !handled { TranslateMessage(&msg); DispatchMessageW(&msg); } } if let Err(payload) = runner.take_panic_error() { runner.reset_runner(); panic::resume_unwind(payload); } } } } /// Dispatch all queued messages via `PeekMessageW` fn dispatch_peeked_messages(&mut self) { let runner = &self.window_target.p.runner_shared; // We generally want to continue dispatching all pending messages // but we also allow dispatching to be interrupted as a means to // ensure the `pump_events` won't indefinitely block an external // event loop if there are too many pending events. This interrupt // flag will be set after dispatching `RedrawRequested` events. runner.interrupt_msg_dispatch.set(false); // # Safety // The Windows API has no documented requirement for bitwise // initializing a `MSG` struct (it can be uninitialized memory for the C // API) and there's no API to construct or initialize a `MSG`. This // is the simplest way avoid unitialized memory in Rust let mut msg = unsafe { mem::zeroed() }; loop { unsafe { if PeekMessageW(&mut msg, 0, 0, 0, PM_REMOVE) == false.into() { break; } let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { callback(&mut msg as *mut _ as *mut _) } else { false }; if !handled { TranslateMessage(&msg); DispatchMessageW(&msg); } } if let Err(payload) = runner.take_panic_error() { runner.reset_runner(); panic::resume_unwind(payload); } if let Some(_code) = runner.exit_code() { break; } if runner.interrupt_msg_dispatch.get() { break; } } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { target_window: self.window_target.p.thread_msg_target, event_send: self.thread_msg_sender.clone(), } } fn exit_code(&self) -> Option { self.window_target.p.exit_code() } } 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(monitor) } #[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 listen_device_events(&self, allowed: DeviceEvents) { raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed); } pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner_shared.set_control_flow(control_flow) } pub(crate) fn control_flow(&self) -> ControlFlow { self.runner_shared.control_flow() } pub(crate) fn exit(&self) { self.runner_shared.set_exit_code(0) } pub(crate) fn exiting(&self) -> bool { self.runner_shared.exit_code().is_some() } pub(crate) fn clear_exit(&self) { self.runner_shared.clear_exit(); } fn exit_code(&self) -> Option { self.runner_shared.exit_code() } } /// Returns the id of the main thread. /// /// Windows has no real API to check if the current executing thread is the "main thread", unlike /// macOS. /// /// Windows will let us look up the current thread's id, but there's no API that lets us check what /// the id of the main thread is. We would somehow need to get the main thread's id before a /// developer could spin off any other threads inside of the main entrypoint in order to emulate the /// capabilities of other platforms. /// /// We can get the id of the main thread by using CRT initialization. CRT initialization can be used /// to setup global state within a program. The OS will call a list of function pointers which /// assign values to a static variable. To have get a hold of the main thread id, we need to place /// our function pointer inside of the `.CRT$XCU` section so it is called before the main /// entrypoint. /// /// Full details of CRT initialization can be found here: /// fn main_thread_id() -> u32 { static mut MAIN_THREAD_ID: u32 = 0; /// Function pointer used in CRT initialization section to set the above static field's value. // Mark as used so this is not removable. #[used] #[allow(non_upper_case_globals)] // Place the function pointer inside of CRT initialization section so it is loaded before // main entrypoint. // // See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute #[link_section = ".CRT$XCU"] static INIT_MAIN_THREAD_ID: unsafe fn() = { unsafe fn initer() { unsafe { MAIN_THREAD_ID = GetCurrentThreadId() }; } initer }; unsafe { MAIN_THREAD_ID } } /// Returns the minimum `Option`, taking into account that `None` /// equates to an infinite timeout, not a zero timeout (so can't just use /// `Option::min`) fn min_timeout(a: Option, b: Option) -> Option { a.map_or(b, |a_timeout| { b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))) }) } // 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| { if dur.subsec_nanos() % 1_000_000 > 0 { ms.checked_add(1) } else { Some(ms) } }) .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 { 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 boxed2: ThreadExecFn = Box::new(Box::new(function)); let raw = Box::into_raw(boxed2); let res = PostMessageW(self.target_window, EXEC_MSG_ID.get(), raw as usize, 0); assert!( res != false.into(), "PostMessage failed; is the messages queue full?" ); } } } } type ThreadExecFn = Box>; pub struct EventLoopProxy { target_window: HWND, event_send: Sender, } unsafe impl Send 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> { self.event_send .send(event) .map(|result| { unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) }; result }) .map_err(|e| EventLoopClosed(e.0)) } } /// A lazily-initialized window message ID. pub struct LazyMessageId { /// The ID. id: AtomicU32, /// The name of the message. name: &'static str, } /// An invalid custom window ID. const INVALID_ID: u32 = 0x0; impl LazyMessageId { /// Create a new `LazyId`. const fn new(name: &'static str) -> Self { Self { id: AtomicU32::new(INVALID_ID), name, } } /// Get the message ID. pub fn get(&self) -> u32 { // Load the ID. let id = self.id.load(Ordering::Relaxed); if id != INVALID_ID { return id; } // Register the message. // SAFETY: We are sure that the pointer is a valid C string ending with '\0'. assert!(self.name.ends_with('\0')); let new_id = unsafe { RegisterWindowMessageA(self.name.as_ptr()) }; assert_ne!( new_id, 0, "RegisterWindowMessageA returned zero for '{}': {}", self.name, std::io::Error::last_os_error() ); // Store the new ID. Since `RegisterWindowMessageA` returns the same value for any given string, // the target value will always either be a). `INVALID_ID` or b). the correct ID. Therefore a // compare-and-swap operation here (or really any consideration) is never necessary. self.id.store(new_id, Ordering::Relaxed); new_id } } // Message sent by the `EventLoopProxy` when we want to wake up the thread. // WPARAM and LPARAM are unused. static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0"); // 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: LazyMessageId = LazyMessageId::new("Winit::ExecMsg\0"); // 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: LazyMessageId = LazyMessageId::new("Winit::DestroyMsg\0"); // 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: LazyMessageId = LazyMessageId::new("Winit::SetRetainMaximized\0"); static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = Lazy::new(|| util::encode_wide("Winit Thread Event Target")); /// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows /// pub static TASKBAR_CREATED: LazyMessageId = LazyMessageId::new("TaskbarCreated\0"); fn create_event_target_window() -> HWND { use windows_sys::Win32::UI::WindowsAndMessaging::CS_HREDRAW; use windows_sys::Win32::UI::WindowsAndMessaging::CS_VREDRAW; unsafe { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(thread_event_target_callback::), cbClsExtra: 0, cbWndExtra: 0, hInstance: util::get_instance_handle(), hIcon: 0, hCursor: 0, // must be null in order for cursor state to work properly hbrBackground: 0, lpszMenuName: ptr::null(), lpszClassName: THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), hIconSm: 0, }; RegisterClassExW(&class); } unsafe { let window = 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.as_ptr(), ptr::null(), WS_OVERLAPPED, 0, 0, 0, 0, 0, 0, util::get_instance_handle(), ptr::null(), ); super::set_window_long( 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) as isize, ); window } } fn insert_event_target_window_data( thread_msg_target: HWND, event_loop_runner: EventLoopRunnerShared, ) -> Sender { let (tx, rx) = mpsc::channel(); let userdata = ThreadMsgTargetData { event_loop_runner, user_event_receiver: rx, }; let input_ptr = Box::into_raw(Box::new(userdata)); unsafe { super::set_window_long(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; unsafe { 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: 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); unsafe { ReleaseCapture() }; } } fn normalize_pointer_pressure(pressure: u32) -> Option { match pressure { 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), _ => None, } } /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state fn update_modifiers(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::ModifiersChanged; let modifiers = { let mut layouts = LAYOUT_CACHE.lock().unwrap(); layouts.get_agnostic_mods() }; let mut window_state = userdata.window_state.lock().unwrap(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; // Drop lock drop(window_state); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ModifiersChanged(modifiers.into()), }); } } 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)), 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)), event: ModifiersChanged(ModifiersState::empty().into()), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), 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 = unsafe { super::get_window_long(window, GWL_USERDATA) }; let userdata_ptr = match (userdata, msg) { (0, WM_NCCREATE) => { let createstruct = unsafe { &mut *(lparam as *mut CREATESTRUCTW) }; let initdata = unsafe { &mut *(createstruct.lpCreateParams as *mut InitData<'_, T>) }; let result = match unsafe { initdata.on_nccreate(window) } { Some(userdata) => unsafe { super::set_window_long(window, GWL_USERDATA, userdata as _); DefWindowProcW(window, msg, wparam, lparam) }, None => -1, // failed to create the window }; return result; } // Getting here should quite frankly be impossible, // but we'll make window creation fail here just in case. (0, WM_CREATE) => return -1, (_, WM_CREATE) => unsafe { let createstruct = &mut *(lparam 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 unsafe { DefWindowProcW(window, msg, wparam, lparam) }, _ => userdata as *mut WindowData, }; let (result, userdata_removed, recurse_depth) = { let userdata = unsafe { &*(userdata_ptr) }; userdata.recurse_depth.set(userdata.recurse_depth.get() + 1); let result = unsafe { 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(unsafe { Box::from_raw(userdata_ptr) }); } result } unsafe fn public_window_callback_inner( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, userdata: &WindowData, ) -> LRESULT { let mut result = ProcResult::DefWindowProc(wparam); // Send new modifiers before sending key events. let mods_changed_callback = || match msg { WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => { update_modifiers(window, userdata); result = ProcResult::Value(0); } _ => (), }; userdata .event_loop_runner .catch_unwind(mods_changed_callback) .unwrap_or_else(|| result = ProcResult::Value(-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)), 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(-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 { WM_NCCALCSIZE => { let window_flags = userdata.window_state_lock().window_flags; if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { result = ProcResult::DefWindowProc(wparam); return; } let params = unsafe { &mut *(lparam as *mut NCCALCSIZE_PARAMS) }; if util::is_maximized(window) { // Limit the window size when maximized to the current monitor. // Otherwise it would include the non-existent decorations. // // Use `MonitorFromRect` instead of `MonitorFromWindow` to select // the correct monitor here. // See https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549 let monitor = unsafe { MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL) }; if let Ok(monitor_info) = monitor::get_monitor_info(monitor) { params.rgrc[0] = monitor_info.monitorInfo.rcWork; } } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { // Extend the client area to cover the whole non-client area. // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks // // HACK(msiglreith): To add the drop shadow we slightly tweak the non-client area. // This leads to a small black 1px border on the top. Adding a margin manually // on all 4 borders would result in the caption getting drawn by the DWM. // // Another option would be to allow the DWM to paint inside the client area. // Unfortunately this results in janky resize behavior, where the compositor is // ahead of the window surface. Currently, there seems no option to achieve this // with the Windows API. params.rgrc[0].top += 1; params.rgrc[0].bottom += 1; } result = ProcResult::Value(0); } WM_ENTERSIZEMOVE => { userdata .window_state_lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); result = ProcResult::Value(0); } WM_EXITSIZEMOVE => { let mut state = userdata.window_state_lock(); if state.dragging { state.dragging = false; unsafe { PostMessageW(window, WM_LBUTTONUP, 0, lparam) }; } state.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); result = ProcResult::Value(0); } WM_NCLBUTTONDOWN => { if wparam == HTCAPTION as _ { unsafe { PostMessageW(window, WM_MOUSEMOVE, 0, lparam) }; } result = ProcResult::DefWindowProc(wparam); } WM_CLOSE => { use crate::event::WindowEvent::CloseRequested; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); result = ProcResult::Value(0); } WM_DESTROY => { use crate::event::WindowEvent::Destroyed; unsafe { RevokeDragDrop(window) }; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Destroyed, }); result = ProcResult::Value(0); } WM_NCDESTROY => { unsafe { super::set_window_long(window, GWL_USERDATA, 0) }; userdata.userdata_removed.set(true); result = ProcResult::Value(0); } WM_PAINT => { userdata.window_state_lock().redraw_requested = userdata.event_loop_runner.should_buffer(); // We'll buffer only in response to `UpdateWindow`, if win32 decides to redraw the // window outside the normal flow of the event loop. This way mark event as handled // and request a normal redraw with `RedrawWindow`. if !userdata.event_loop_runner.should_buffer() { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::RedrawRequested, }); } // NOTE: calling `RedrawWindow` during `WM_PAINT` does nothing, since to mark // `WM_PAINT` as handled we should call the `DefWindowProcW`. Call it and check whether // user asked for redraw during `RedrawRequested` event handling and request it again // after marking `WM_PAINT` as handled. result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) }); if std::mem::take(&mut userdata.window_state_lock().redraw_requested) { unsafe { RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT) }; } } WM_WINDOWPOSCHANGING => { let mut window_state = userdata.window_state_lock(); if let Some(ref mut fullscreen) = window_state.fullscreen { let window_pos = unsafe { &mut *(lparam 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: u32 = SWP_NOMOVE | SWP_NOSIZE; let new_rect = if window_pos.flags & NOMOVE_OR_NOSIZE != 0 { let cur_rect = util::WindowArea::Outer.get_rect(window) .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit"); 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 = unsafe { MonitorFromRect(&new_rect, MONITOR_DEFAULTTONULL) }; match fullscreen { Fullscreen::Borderless(ref mut fullscreen_monitor) => { if new_monitor != 0 && fullscreen_monitor .as_ref() .map(|monitor| new_monitor != monitor.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(MonitorHandle::new(new_monitor)); } } Fullscreen::Exclusive(ref video_mode) => { let old_monitor = 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; } } } } } result = ProcResult::Value(0); } // WM_MOVE supplies client area positions, so we send Moved here instead. WM_WINDOWPOSCHANGED => { use crate::event::WindowEvent::Moved; let windowpos = lparam as *const WINDOWPOS; if unsafe { (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE } { let physical_position = unsafe { PhysicalPosition::new((*windowpos).x, (*windowpos).y) }; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Moved(physical_position), }); } // This is necessary for us to still get sent WM_SIZE. result = ProcResult::DefWindowProc(wparam); } WM_SIZE => { use crate::event::WindowEvent::Resized; let w = super::loword(lparam as u32) as u32; let h = super::hiword(lparam as u32) as u32; let physical_size = PhysicalSize::new(w, h); let event = Event::WindowEvent { window_id: RootWindowId(WindowId(window)), 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 == SIZE_MAXIMIZED as usize; w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); } } userdata.send_event(event); result = ProcResult::Value(0); } WM_MENUCHAR => { result = ProcResult::Value((MNC_CLOSE << 16) as isize); } WM_IME_STARTCOMPOSITION => { let ime_allowed = userdata.window_state_lock().ime_allowed; if ime_allowed { userdata.window_state_lock().ime_state = ImeState::Enabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Enabled), }); } result = ProcResult::DefWindowProc(wparam); } WM_IME_COMPOSITION => { let ime_allowed_and_composing = { let w = userdata.window_state_lock(); w.ime_allowed && w.ime_state != ImeState::Disabled }; // Windows Hangul IME sends WM_IME_COMPOSITION after WM_IME_ENDCOMPOSITION, so // check whether composing. if ime_allowed_and_composing { let ime_context = unsafe { ImeContext::current(window) }; if lparam == 0 { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); } // Google Japanese Input and ATOK have both flags, so // first, receive composing result if exist. if (lparam as u32 & GCS_RESULTSTR) != 0 { if let Some(text) = unsafe { ime_context.get_composed_text() } { userdata.window_state_lock().ime_state = ImeState::Enabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Commit(text)), }); } } // Next, receive preedit range for next composing if exist. if (lparam as u32 & GCS_COMPSTR) != 0 { if let Some((text, first, last)) = unsafe { ime_context.get_composing_text_and_cursor() } { userdata.window_state_lock().ime_state = ImeState::Preedit; let cursor_range = first.map(|f| (f, last.unwrap_or(f))); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Preedit(text, cursor_range)), }); } } } // Not calling DefWindowProc to hide composing text drawn by IME. result = ProcResult::Value(0); } WM_IME_ENDCOMPOSITION => { let ime_allowed_or_composing = { let w = userdata.window_state_lock(); w.ime_allowed || w.ime_state != ImeState::Disabled }; if ime_allowed_or_composing { if userdata.window_state_lock().ime_state == ImeState::Preedit { // Windows Hangul IME sends WM_IME_COMPOSITION after WM_IME_ENDCOMPOSITION, so // trying receiving composing result and commit if exists. 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)), event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Commit(text)), }); } } userdata.window_state_lock().ime_state = ImeState::Disabled; userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Ime(Ime::Disabled), }); } result = ProcResult::DefWindowProc(wparam); } WM_IME_SETCONTEXT => { // Hide composing text drawn by IME. let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize); result = ProcResult::DefWindowProc(wparam); } // this is necessary for us to maintain minimize/restore state WM_SYSCOMMAND => { if wparam == SC_RESTORE as usize { let mut w = userdata.window_state_lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); } if wparam == SC_MINIMIZE as usize { 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 == SC_SCREENSAVE as usize { let window_state = userdata.window_state_lock(); if window_state.fullscreen.is_some() { result = ProcResult::Value(0); return; } } result = ProcResult::DefWindowProc(wparam); } WM_MOUSEMOVE => { use crate::event::WindowEvent::{CursorEntered, CursorLeft, CursorMoved}; let x = super::get_x_lparam(lparam as u32) as i32; let y = super::get_y_lparam(lparam as u32) as i32; let position = PhysicalPosition::new(x as f64, y as f64); let cursor_moved; { let mut w = userdata.window_state_lock(); let mouse_was_inside_window = w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); match get_pointer_move_kind(window, mouse_was_inside_window, x, y) { PointerMoveKind::Enter => { w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)) .ok(); drop(w); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorEntered { device_id: DEVICE_ID, }, }); // Calling TrackMouseEvent in order to receive mouse leave events. unsafe { TrackMouseEvent(&mut TRACKMOUSEEVENT { cbSize: mem::size_of::() as u32, dwFlags: TME_LEAVE, hwndTrack: window, dwHoverTime: HOVER_DEFAULT, }) }; } PointerMoveKind::Leave => { w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) .ok(); drop(w); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorLeft { device_id: DEVICE_ID, }, }); } PointerMoveKind::None => drop(w), } // 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 { update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { device_id: DEVICE_ID, position, }, }); } result = ProcResult::Value(0); } 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)), event: CursorLeft { device_id: DEVICE_ID, }, }); result = ProcResult::Value(0); } WM_MOUSEWHEEL => { use crate::event::MouseScrollDelta::LineDelta; let value = (wparam >> 16) as i16; let value = value as f32 / WHEEL_DELTA as f32; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, }, }); result = ProcResult::Value(0); } WM_MOUSEHWHEEL => { use crate::event::MouseScrollDelta::LineDelta; let value = (wparam >> 16) as i16; let value = -value as f32 / WHEEL_DELTA as f32; // NOTE: inverted! See https://github.com/rust-windowing/winit/pull/2105/ update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, }, }); result = ProcResult::Value(0); } WM_KEYDOWN | WM_SYSKEYDOWN => { if msg == WM_SYSKEYDOWN { result = ProcResult::DefWindowProc(wparam); } } WM_KEYUP | WM_SYSKEYUP => { if msg == WM_SYSKEYUP && unsafe { GetMenu(window) != 0 } { // let Windows handle event if the window has a native menu, a modal event loop // is started here on Alt key up. result = ProcResult::DefWindowProc(wparam); } } WM_LBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left, }, }); result = ProcResult::Value(0); } WM_LBUTTONUP => { use crate::event::{ ElementState::Released, MouseButton::Left, WindowEvent::MouseInput, }; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left, }, }); result = ProcResult::Value(0); } WM_RBUTTONDOWN => { use crate::event::{ ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput, }; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right, }, }); result = ProcResult::Value(0); } WM_RBUTTONUP => { use crate::event::{ ElementState::Released, MouseButton::Right, WindowEvent::MouseInput, }; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right, }, }); result = ProcResult::Value(0); } WM_MBUTTONDOWN => { use crate::event::{ ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput, }; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle, }, }); result = ProcResult::Value(0); } WM_MBUTTONUP => { use crate::event::{ ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput, }; unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle, }, }); result = ProcResult::Value(0); } WM_XBUTTONDOWN => { use crate::event::{ ElementState::Pressed, MouseButton::Back, MouseButton::Forward, MouseButton::Other, WindowEvent::MouseInput, }; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), }, }, }); result = ProcResult::Value(0); } WM_XBUTTONUP => { use crate::event::{ ElementState::Released, MouseButton::Back, MouseButton::Forward, MouseButton::Other, WindowEvent::MouseInput, }; let xbutton = super::get_xbutton_wparam(wparam as u32); unsafe { release_mouse(userdata.window_state_lock()) }; update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { device_id: DEVICE_ID, state: Released, button: match xbutton { 1 => Back, 2 => Forward, _ => Other(xbutton), }, }, }); result = ProcResult::Value(0); } 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 != window { userdata.window_state_lock().mouse.capture_count = 0; } result = ProcResult::Value(0); } WM_TOUCH => { let pcount = super::loword(wparam as u32) as usize; let mut inputs = Vec::with_capacity(pcount); let htouch = lparam; if unsafe { GetTouchInputInfo( htouch, pcount as u32, inputs.as_mut_ptr(), mem::size_of::() as i32, ) > 0 } { unsafe { inputs.set_len(pcount) }; for input in &inputs { let mut location = POINT { x: input.x / 100, y: input.y / 100, }; if unsafe { ScreenToClient(window, &mut location) } == false.into() { 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)), event: WindowEvent::Touch(Touch { phase: if util::has_flag(input.dwFlags, TOUCHEVENTF_DOWN) { TouchPhase::Started } else if util::has_flag(input.dwFlags, TOUCHEVENTF_UP) { TouchPhase::Ended } else if util::has_flag(input.dwFlags, TOUCHEVENTF_MOVE) { TouchPhase::Moved } else { continue; }, location, force: None, // WM_TOUCH doesn't support pressure information id: input.dwID as u64, device_id: DEVICE_ID, }), }); } } unsafe { CloseTouchInputHandle(htouch) }; result = ProcResult::Value(0); } WM_POINTERDOWN | WM_POINTERUPDATE | WM_POINTERUP => { if let ( Some(GetPointerFrameInfoHistory), Some(SkipPointerFrameMessages), Some(GetPointerDeviceRects), ) = ( *util::GET_POINTER_FRAME_INFO_HISTORY, *util::SKIP_POINTER_FRAME_MESSAGES, *util::GET_POINTER_DEVICE_RECTS, ) { let pointer_id = super::loword(wparam as u32) as u32; let mut entries_count = 0u32; let mut pointers_count = 0u32; if unsafe { GetPointerFrameInfoHistory( pointer_id, &mut entries_count, &mut pointers_count, ptr::null_mut(), ) } == false.into() { result = ProcResult::Value(0); return; } let pointer_info_count = (entries_count * pointers_count) as usize; let mut pointer_infos = Vec::with_capacity(pointer_info_count); if unsafe { GetPointerFrameInfoHistory( pointer_id, &mut entries_count, &mut pointers_count, pointer_infos.as_mut_ptr(), ) } == false.into() { result = ProcResult::Value(0); return; } unsafe { 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 unsafe { GetPointerDeviceRects( pointer_info.sourceDevice, device_rect.as_mut_ptr(), display_rect.as_mut_ptr(), ) } == false.into() { continue; } let device_rect = unsafe { device_rect.assume_init() }; let display_rect = unsafe { 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 unsafe { ScreenToClient(window, &mut location) } == false.into() { continue; } let force = match pointer_info.pointerType { PT_TOUCH => { let mut touch_info = mem::MaybeUninit::uninit(); util::GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { match unsafe { GetPointerTouchInfo( pointer_info.pointerId, touch_info.as_mut_ptr(), ) } { 0 => None, _ => normalize_pointer_pressure(unsafe { touch_info.assume_init().pressure }), } }) } PT_PEN => { let mut pen_info = mem::MaybeUninit::uninit(); util::GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| { match unsafe { GetPointerPenInfo(pointer_info.pointerId, pen_info.as_mut_ptr()) } { 0 => None, _ => normalize_pointer_pressure(unsafe { pen_info.assume_init().pressure }), } }) } _ => 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)), event: WindowEvent::Touch(Touch { phase: if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_DOWN) { TouchPhase::Started } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UP) { TouchPhase::Ended } else if util::has_flag(pointer_info.pointerFlags, POINTER_FLAG_UPDATE) { TouchPhase::Moved } else { continue; }, location, force, id: pointer_info.pointerId as u64, device_id: DEVICE_ID, }), }); } unsafe { SkipPointerFrameMessages(pointer_id) }; } result = ProcResult::Value(0); } WM_NCACTIVATE => { let is_active = wparam != false.into(); let active_focus_changed = userdata.window_state_lock().set_active(is_active); if active_focus_changed { if is_active { unsafe { gain_active_focus(window, userdata) }; } else { unsafe { lose_active_focus(window, userdata) }; } } result = ProcResult::DefWindowProc(wparam); } WM_SETFOCUS => { let active_focus_changed = userdata.window_state_lock().set_focused(true); if active_focus_changed { unsafe { gain_active_focus(window, userdata) }; } result = ProcResult::Value(0); } WM_KILLFOCUS => { let active_focus_changed = userdata.window_state_lock().set_focused(false); if active_focus_changed { unsafe { lose_active_focus(window, userdata) }; } result = ProcResult::Value(0); } 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 = super::loword(lparam as u32) as u32 == HTCLIENT; if in_client_area { Some(window_state.mouse.cursor) } else { None } }; match set_cursor_to { Some(cursor) => { let cursor = unsafe { LoadCursorW(0, util::to_windows_cursor(cursor)) }; unsafe { SetCursor(cursor) }; result = ProcResult::Value(0); } None => result = ProcResult::DefWindowProc(wparam), } } WM_GETMINMAXINFO => { let mmi = lparam as *mut MINMAXINFO; let window_state = userdata.window_state_lock(); let window_flags = window_state.window_flags; if window_state.min_size.is_some() || window_state.max_size.is_some() { if let Some(min_size) = window_state.min_size { let min_size = min_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = window_flags.adjust_size(window, min_size).into(); unsafe { (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32, } }; } if let Some(max_size) = window_state.max_size { let max_size = max_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = window_flags.adjust_size(window, max_size).into(); unsafe { (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32, } }; } } result = ProcResult::Value(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. 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 = super::loword(wparam as u32) as u32; let new_scale_factor = dpi_to_scale_factor(new_dpi_x); let old_scale_factor: f64; let (allow_resize, window_flags) = { 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 { result = ProcResult::Value(0); return; } let allow_resize = window_state.fullscreen.is_none() && !window_state.window_flags().contains(WindowFlags::MAXIMIZED); (allow_resize, window_state.window_flags) }; // New size as suggested by Windows. let suggested_rect = unsafe { *(lparam 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 = window_flags .adjust_rect(window, 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 = util::WindowArea::Inner .get_rect(window) .expect("failed to query (old) inner window area"); 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 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, }; let new_inner_size = Arc::new(Mutex::new(new_physical_inner_size)); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ScaleFactorChanged { scale_factor: new_scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), }, }); let new_physical_inner_size = *new_inner_size.lock().unwrap(); drop(new_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; { 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 = window_flags .adjust_rect(window, 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 = unsafe { mem::zeroed() }; unsafe { 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 = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; let conservative_rect_monitor = unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { conservative_rect } else { let get_monitor_rect = |monitor| { let mut monitor_info = MONITORINFO { cbSize: mem::size_of::() as _, ..unsafe { mem::zeroed() } }; unsafe { 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. #[allow(clippy::bool_to_int_with_if)] 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 unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } == new_dpi_monitor { break; } } conservative_rect }; } unsafe { SetWindowPos( window, 0, 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(0); } WM_SETTINGCHANGE => { use crate::event::WindowEvent::ThemeChanged; let preferred_theme = userdata.window_state_lock().preferred_theme; if preferred_theme.is_none() { let new_theme = try_theme(window, preferred_theme); let mut window_state = userdata.window_state_lock(); if window_state.current_theme != new_theme { window_state.current_theme = new_theme; drop(window_state); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ThemeChanged(new_theme), }); } } result = ProcResult::DefWindowProc(wparam); } _ => { if msg == DESTROY_MSG_ID.get() { unsafe { DestroyWindow(window) }; result = ProcResult::Value(0); } else if msg == SET_RETAIN_STATE_ON_SIZE_MSG_ID.get() { 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) }); result = ProcResult::Value(0); } else if msg == TASKBAR_CREATED.get() { let window_state = userdata.window_state_lock(); unsafe { set_skip_taskbar(window, window_state.skip_taskbar) }; result = ProcResult::DefWindowProc(wparam); } else { result = ProcResult::DefWindowProc(wparam); } } }; userdata .event_loop_runner .catch_unwind(callback) .unwrap_or_else(|| result = ProcResult::Value(-1)); match result { ProcResult::DefWindowProc(wparam) => unsafe { DefWindowProcW(window, msg, wparam, lparam) }, ProcResult::Value(val) => val, } } unsafe extern "system" fn thread_event_target_callback( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { let userdata_ptr = unsafe { super::get_window_long(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 unsafe { DefWindowProcW(window, msg, wparam, lparam) }; } let userdata = unsafe { Box::from_raw(userdata_ptr) }; if msg != WM_PAINT { unsafe { RedrawWindow(window, ptr::null(), 0, RDW_INTERNALPAINT) }; } 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 { WM_NCDESTROY => { unsafe { super::set_window_long(window, GWL_USERDATA, 0) }; userdata_removed = true; 0 } WM_PAINT => unsafe { ValidateRect(window, ptr::null()); // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. DefWindowProcW(window, msg, wparam, lparam) }, WM_INPUT_DEVICE_CHANGE => { let event = match wparam as u32 { GIDC_ARRIVAL => DeviceEvent::Added, GIDC_REMOVAL => DeviceEvent::Removed, _ => unreachable!(), }; userdata.send_event(Event::DeviceEvent { device_id: wrap_device_id(lparam as u32), event, }); 0 } WM_INPUT => { if let Some(data) = raw_input::get_raw_input_data(lparam as _) { unsafe { handle_raw_input(&userdata, data) }; } unsafe { DefWindowProcW(window, msg, wparam, lparam) } } _ if msg == USER_EVENT_MSG_ID.get() => { if let Ok(event) = userdata.user_event_receiver.recv() { userdata.send_event(Event::UserEvent(event)); } 0 } _ if msg == EXEC_MSG_ID.get() => { let mut function: ThreadExecFn = unsafe { Box::from_raw(wparam as *mut _) }; function(); 0 } _ => unsafe { DefWindowProcW(window, msg, wparam, lparam) }, }; let result = userdata .event_loop_runner .catch_unwind(callback) .unwrap_or(-1); if userdata_removed { drop(userdata); } else { Box::into_raw(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 as _); if data.header.dwType == RIM_TYPEMOUSE { let mouse = unsafe { data.data.mouse }; if util::has_flag(mouse.usFlags as u32, MOUSE_MOVE_RELATIVE) { 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) }, }); } } let button_flags = unsafe { mouse.Anonymous.Anonymous.usButtonFlags }; if util::has_flag(button_flags as u32, RI_MOUSE_WHEEL) { let button_data = unsafe { mouse.Anonymous.Anonymous.usButtonData } as i16; let delta = button_data as f32 / WHEEL_DELTA as f32; userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(0.0, delta), }, }); } if util::has_flag(button_flags as u32, RI_MOUSE_HWHEEL) { let button_data = unsafe { mouse.Anonymous.Anonymous.usButtonData } as i16; let delta = -button_data as f32 / WHEEL_DELTA as f32; userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(delta, 0.0), }, }); } let button_state = raw_input::get_raw_mouse_button_state(button_flags as u32); for (button, state) in button_state.iter().enumerate() { if let Some(state) = *state { userdata.send_event(Event::DeviceEvent { device_id, event: Button { button: button as _, state, }, }); } } } else if data.header.dwType == RIM_TYPEKEYBOARD { let keyboard = unsafe { 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; } if let Some(physical_key) = raw_input::get_keyboard_physical_key(keyboard) { let state = if pressed { Pressed } else { Released }; userdata.send_event(Event::DeviceEvent { device_id, event: Key(RawKeyEvent { physical_key, state, }), }); } } } enum PointerMoveKind { /// Pointer enterd to the window. Enter, /// Pointer leaved the window client area. Leave, /// Pointer is inside the window or `GetClientRect` failed. None, } fn get_pointer_move_kind( window: HWND, mouse_was_inside_window: bool, x: i32, y: i32, ) -> PointerMoveKind { let rect: RECT = unsafe { let mut rect: RECT = mem::zeroed(); if GetClientRect(window, &mut rect) == false.into() { return PointerMoveKind::None; // exit early if GetClientRect failed } rect }; let x = (rect.left..rect.right).contains(&x); let y = (rect.top..rect.bottom).contains(&y); if !mouse_was_inside_window && x && y { PointerMoveKind::Enter } else if mouse_was_inside_window && !(x && y) { PointerMoveKind::Leave } else { PointerMoveKind::None } } winit-0.29.15/src/platform_impl/windows/icon.rs000064400000000000000000000103551046102023000175640ustar 00000000000000use std::{fmt, io, mem, path::Path, sync::Arc}; use windows_sys::{ core::PCWSTR, Win32::{ Foundation::HWND, UI::WindowsAndMessaging::{ CreateIcon, DestroyIcon, LoadImageW, SendMessageW, HICON, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE, WM_SETICON, }, }, }; use crate::dpi::PhysicalSize; use crate::icon::*; use super::util; impl Pixel { fn convert_to_bgra(&mut self) { mem::swap(&mut self.r, &mut self.b); } } impl RgbaIcon { fn into_windows_icon(self) -> Result { let 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_ptr() as *mut Pixel, pixel_count) }; for pixel in pixels { and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel pixel.convert_to_bgra(); } assert_eq!(and_mask.len(), pixel_count); let handle = unsafe { CreateIcon( 0, self.width as i32, self.height as i32, 1, (PIXEL_SIZE * 8) as u8, and_mask.as_ptr(), rgba.as_ptr(), ) }; if handle != 0 { Ok(WinIcon::from_handle(handle)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } } #[derive(Debug)] pub enum IconType { Small = ICON_SMALL as isize, Big = ICON_BIG as isize, } #[derive(Debug)] struct RaiiIcon { handle: HICON, } #[derive(Clone)] pub struct WinIcon { inner: Arc, } unsafe impl Send for WinIcon {} impl WinIcon { pub fn as_raw_handle(&self) -> HICON { self.inner.handle } pub fn from_path>( path: P, 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 wide_path = util::encode_wide(path.as_ref()); let handle = unsafe { LoadImageW( 0, wide_path.as_ptr(), IMAGE_ICON, width, height, LR_DEFAULTSIZE | LR_LOADFROMFILE, ) }; if handle != 0 { Ok(WinIcon::from_handle(handle as HICON)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } 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( util::get_instance_handle(), resource_id as PCWSTR, IMAGE_ICON, width, height, LR_DEFAULTSIZE, ) }; if handle != 0 { Ok(WinIcon::from_handle(handle as HICON)) } else { Err(BadIcon::OsError(io::Error::last_os_error())) } } 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, icon_type as usize, self.as_raw_handle()); } } fn from_handle(handle: HICON) -> Self { Self { inner: Arc::new(RaiiIcon { handle }), } } } impl Drop for RaiiIcon { fn drop(&mut self) { unsafe { DestroyIcon(self.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, icon_type as usize, 0); } } winit-0.29.15/src/platform_impl/windows/ime.rs000064400000000000000000000123441046102023000174060ustar 00000000000000use std::{ ffi::{c_void, OsString}, os::windows::prelude::OsStringExt, ptr::null_mut, }; use windows_sys::Win32::{ Foundation::{POINT, RECT}, Globalization::HIMC, UI::{ Input::Ime::{ ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT, }, WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED}, }, }; use crate::{ dpi::{Position, Size}, platform::windows::HWND, }; 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; for (attr, chr) in attrs.into_iter().zip(text.chars()) { let char_is_targetted = attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; if first.is_none() && char_is_targetted { first = Some(boundary_before_char); } else if first.is_some() && last.is_none() && !char_is_targetted { last = Some(boundary_before_char); } boundary_before_char += chr.len_utf8(); } 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, null_mut(), 0) }; (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) } unsafe fn get_composition_string(&self, gcs_mode: u32) -> 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: u32) -> Option> { let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, null_mut(), 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, 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, 0, IACE_DEFAULT) }; } else { unsafe { ImmAssociateContextEx(hwnd, 0, IACE_CHILDREN) }; } } unsafe fn system_has_ime() -> bool { unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } } } impl Drop for ImeContext { fn drop(&mut self) { unsafe { ImmReleaseContext(self.hwnd, self.himc) }; } } winit-0.29.15/src/platform_impl/windows/keyboard.rs000064400000000000000000001502561046102023000204410ustar 00000000000000use std::{ char, ffi::OsString, mem::MaybeUninit, os::windows::ffi::OsStringExt, sync::{ atomic::{AtomicU32, Ordering::Relaxed}, Mutex, MutexGuard, }, }; use windows_sys::Win32::{ Foundation::{HWND, LPARAM, WPARAM}, System::SystemServices::LANG_KOREAN, UI::{ Input::KeyboardAndMouse::{ GetAsyncKeyState, GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyExW, 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, }, TextServices::HKL, 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 smol_str::SmolStr; use unicode_segmentation::UnicodeSegmentation; use crate::{ event::{ElementState, KeyEvent}, keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey}, platform::scancode::PhysicalKeyExtScancode, platform_impl::platform::{ event_loop::ProcResult, keyboard_layout::{Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, loword, primarylangid, KeyEventExtra, }, }; pub type ExScancode = u16; pub struct MessageAsKeyEvent { pub event: KeyEvent, pub is_synthetic: bool, } /// Stores information required to make `KeyEvent`s. /// /// A single Winit `KeyEvent` contains information which the Windows API passes to the application /// in multiple window messages. In other words: a Winit `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 => { // sythesize 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 as VIRTUAL_KEY == VK_F4 { // 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(); *result = ProcResult::Value(0); let next_msg = next_kbd_msg(hwnd); let mut layouts = LAYOUT_CACHE.lock().unwrap(); let mut finished_event_info = Some(PartialKeyEventInfo::from_message( wparam, lparam, ElementState::Pressed, &mut layouts, )); let mut event_info = self.event_info.lock().unwrap(); *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(); 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(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().unwrap().take().unwrap(); let ev = event_info.finalize(); 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().unwrap(); 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(0); let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam); let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam); 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 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 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 appended to this keyobard event MatchResult::TokenToRemove(pending_token) } else { let mut event_info = self.event_info.lock().unwrap(); 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().unwrap(); // 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[VK_NUMLOCK as usize] & 1 != 0; let vkey = event_info.vkey; let physical_key = &event_info.physical_key; let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, physical_key); event_info.text = PartialText::Text(key.to_text().map(SmolStr::new)); } let ev = event_info.finalize(); 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(0); let mut layouts = LAYOUT_CACHE.lock().unwrap(); 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().unwrap(); 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(); 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(), } } // Alowing nominimal_bool lint because the `is_key_pressed` macro triggers this warning // and I don't know of another way to resolve it and also keeping the macro #[allow(clippy::nonminimal_bool)] fn synthesize_kbd_state( key_state: ElementState, kbd_state: &[u8; 256], ) -> Vec { let mut key_events = Vec::new(); let mut layouts = LAYOUT_CACHE.lock().unwrap(); let (locale_id, _) = layouts.get_current_layout(); macro_rules! is_key_pressed { ($vk:expr) => { kbd_state[$vk as usize] & 0x80 != 0 }; } // Is caps-lock active? Note that this is different from caps-lock // being held down. let caps_lock_on = kbd_state[VK_CAPITAL as usize] & 1 != 0; let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 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 as HKL, &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 { 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 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( 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(vk as u32, MAPVK_VK_TO_VSC_EX, locale_id) }; if scancode == 0 { return None; } let scancode = scancode as ExScancode; let physical_key = PhysicalKey::from_scancode(scancode as u32); let mods = if caps_lock_on { WindowsModifiers::CAPS_LOCK } else { WindowsModifiers::empty() }; let layout = layouts.layouts.get(&(locale_id as u64)).unwrap(); let logical_key = layout.get_key(mods, num_lock_on, vk, &physical_key); let key_without_modifiers = layout.get_key(WindowsModifiers::empty(), false, vk, &physical_key); let text = if key_state == ElementState::Pressed { logical_key.to_text().map(SmolStr::new) } else { None }; let event_info = PartialKeyEventInfo { vkey: vk, logical_key: PartialLogicalKey::This(logical_key.clone()), key_without_modifiers, key_state, is_repeat: false, physical_key, location: get_location(scancode, locale_id), utf16parts: Vec::with_capacity(8), text: PartialText::Text(text.clone()), }; let mut event = event_info.finalize(); event.logical_key = logical_key; event.platform_specific.text_with_all_modifers = text; Some(MessageAsKeyEvent { event, is_synthetic: true, }) } } enum PartialText { // Unicode System(Vec), Text(Option), } 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), /// Use the value directly provided by this variant This(Key), } struct PartialKeyEventInfo { vkey: VIRTUAL_KEY, key_state: ElementState, is_repeat: bool, physical_key: PhysicalKey, location: KeyLocation, logical_key: PartialLogicalKey, key_without_modifiers: Key, /// 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 = wparam as VIRTUAL_KEY; 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(vkey as u32, MAPVK_VK_TO_VSC_EX, layout.hkl as HKL) as u16 } } else { new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) }; let physical_key = PhysicalKey::from_scancode(scancode as u32); let location = get_location(scancode, layout.hkl as HKL); 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[VK_NUMLOCK as usize] & 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 physical_key { PhysicalKey::Code(KeyCode::NumLock) => Some(Key::Named(NamedKey::NumLock)), PhysicalKey::Code(KeyCode::Pause) => Some(Key::Named(NamedKey::Pause)), _ => None, } } else { None }; let preliminary_logical_key = layout.get_key(mods_without_ctrl, num_lock_on, vkey, &physical_key); 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, &physical_key) { // 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); Key::Character(SmolStr::new(s)) } else { Key::Unidentified(NativeKey::Unidentified) } } key => key, } }; PartialKeyEventInfo { vkey, key_state: state, logical_key, key_without_modifiers, is_repeat: lparam_struct.is_repeat, physical_key, location, utf16parts: Vec::with_capacity(8), text: PartialText::System(Vec::new()), } } fn finalize(self) -> 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() { char_with_all_modifiers = Some(SmolStr::new(string)); } } // 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() { text = Some(SmolStr::new(string)); } } } PartialText::Text(s) => { text = s.map(SmolStr::new); } } let logical_key = match self.logical_key { PartialLogicalKey::TextOr(fallback) => match text.as_ref() { Some(s) => { if s.grapheme_indices(true).count() > 1 { fallback } else { Key::Character(s.clone()) } } None => Key::Unidentified(NativeKey::Windows(self.vkey)), }, PartialLogicalKey::This(v) => v, }; KeyEvent { physical_key: self.physical_key, logical_key, text, location: self.location, state: self.key_state, repeat: self.is_repeat, platform_specific: KeyEventExtra { text_with_all_modifers: 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 >> 30) & 0x01; let transition_state = (lparam >> 31) & 0x01; KeyLParam { scancode: ((lparam >> 16) & 0xFF) as u8, extended: ((lparam >> 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(); GetKeyboardState(kbd_state.as_mut_ptr() as *mut u8); 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 #[allow(clippy::uninit_assumed_init)] 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 = vk as VIRTUAL_KEY; let async_state = GetAsyncKeyState(vk as i32); 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(vk as i32); 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) 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::Named(NamedKey::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().unwrap(); 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().unwrap(); 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().unwrap(); 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().unwrap(); 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 winit.") } } }).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 fn next_kbd_msg(hwnd: HWND) -> Option { unsafe { let mut next_msg = MaybeUninit::uninit(); let peek_retval = PeekMessageW( next_msg.as_mut_ptr(), hwnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE, ); (peek_retval != 0).then(|| next_msg.assume_init()) } } fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { const ABNT_C2: VIRTUAL_KEY = VK_ABNT_C2 as VIRTUAL_KEY; let extension = 0xE000; let extended = (scancode & extension) == extension; let vkey = unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, hkl) as VIRTUAL_KEY }; // 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 | ABNT_C2 => KeyLocation::Numpad, _ => KeyLocation::Standard, } } impl PhysicalKeyExtScancode for PhysicalKey { fn to_scancode(self) -> Option { // See `from_scancode` for more info let hkl = unsafe { GetKeyboardLayout(0) }; let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let code = match self { PhysicalKey::Code(code) => code, PhysicalKey::Unidentified(code) => { return match code { NativeKeyCode::Windows(scancode) => Some(scancode as u32), _ => None, }; } }; 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), _ => None, } } fn from_scancode(scancode: u32) -> PhysicalKey { // 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 PhysicalKey::Code(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, _ => return PhysicalKey::Unidentified(NativeKeyCode::Windows(scancode as u16)), }) } } winit-0.29.15/src/platform_impl/windows/keyboard_layout.rs000064400000000000000000001241701046102023000220320ustar 00000000000000use std::{ collections::{hash_map::Entry, HashMap, HashSet}, ffi::OsString, os::windows::ffi::OsStringExt, sync::Mutex, }; use once_cell::sync::Lazy; use smol_str::SmolStr; use windows_sys::Win32::{ System::SystemServices::{LANG_JAPANESE, LANG_KOREAN}, UI::{ Input::KeyboardAndMouse::{ GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, MAPVK_VK_TO_VSC_EX, VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK, VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW, VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00, VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1, VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN, VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP, VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB, VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH, VK_OEM_FJ_LOYA, VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP, VK_OEM_MINUS, VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, VK_OEM_PLUS, VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, VK_PRINT, VK_PRIOR, VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM, }, TextServices::HKL, }, }; use crate::{ keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey}, platform::scancode::PhysicalKeyExtScancode, platform_impl::{loword, primarylangid}, }; pub(crate) static LAYOUT_CACHE: Lazy> = Lazy::new(|| Mutex::new(LayoutCache::default())); fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(vkey 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, PartialEq, Eq, 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[VK_SHIFT as usize] & 0x80 != 0; let lshift = key_state[VK_LSHIFT as usize] & 0x80 != 0; let rshift = key_state[VK_RSHIFT as usize] & 0x80 != 0; let control = key_state[VK_CONTROL as usize] & 0x80 != 0; let lcontrol = key_state[VK_LCONTROL as usize] & 0x80 != 0; let rcontrol = key_state[VK_RCONTROL as usize] & 0x80 != 0; let alt = key_state[VK_MENU as usize] & 0x80 != 0; let lalt = key_state[VK_LMENU as usize] & 0x80 != 0; let ralt = key_state[VK_RMENU as usize] & 0x80 != 0; let caps = key_state[VK_CAPITAL as usize] & 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[VK_SHIFT as usize] |= 0x80; } else { key_state[VK_SHIFT as usize] &= !0x80; key_state[VK_LSHIFT as usize] &= !0x80; key_state[VK_RSHIFT as usize] &= !0x80; } if self.intersects(Self::CONTROL) { key_state[VK_CONTROL as usize] |= 0x80; } else { key_state[VK_CONTROL as usize] &= !0x80; key_state[VK_LCONTROL as usize] &= !0x80; key_state[VK_RCONTROL as usize] &= !0x80; } if self.intersects(Self::ALT) { key_state[VK_MENU as usize] |= 0x80; } else { key_state[VK_MENU as usize] &= !0x80; key_state[VK_LMENU as usize] &= !0x80; key_state[VK_RMENU as usize] &= !0x80; } if self.intersects(Self::CAPS_LOCK) { key_state[VK_CAPITAL as usize] |= 0x01; } else { key_state[VK_CAPITAL as usize] &= !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: u64, /// 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, physical_key: &PhysicalKey, ) -> Key { let native_code = NativeKey::Windows(vkey); 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.clone(), self.hkl, 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) { return key.clone(); } } else if let Some(key) = self.numlock_off_keys.get(&vkey) { return key.clone(); } if let PhysicalKey::Code(code) = physical_key { if let Some(keys) = self.keys.get(&mods) { if let Some(key) = keys.get(code) { return key.clone(); } } } Key::Unidentified(native_code) } } #[derive(Default)] pub(crate) struct LayoutCache { /// Maps locale identifiers (HKL) to layouts pub layouts: HashMap, } 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(&mut self) -> (u64, &Layout) { let locale_id = unsafe { GetKeyboardLayout(0) } as u64; match self.layouts.entry(locale_id) { Entry::Occupied(entry) => (locale_id, entry.into_mut()), Entry::Vacant(entry) => { let layout = Self::prepare_layout(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(locale_id: u64) -> Layout { let mut layout = Layout { hkl: locale_id, 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..256 { let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; if scancode == 0 { continue; } let keycode = match PhysicalKey::from_scancode(scancode) { PhysicalKey::Code(code) => code, // TODO: validate that we can skip on unidentified keys (probably never occurs?) _ => continue, }; if !is_numpad_specific(vk as VIRTUAL_KEY) && NUMPAD_KEYCODES.contains(&keycode) { let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); let map_vkey = keycode_to_vkey(keycode, locale_id); if map_vkey == 0 { continue; } let map_value = vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false); if matches!(map_value, Key::Unidentified(_)) { continue; } layout.numlock_off_keys.insert(map_vkey, map_value); } } layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); for vk in NUMPAD_VKEYS.iter() { let vk = (*vk) as u32; let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); if let ToUnicodeResult::Str(s) = unicode { layout .numlock_on_keys .insert(vk as VIRTUAL_KEY, Key::Character(SmolStr::new(s))); } } // 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_retain(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..256 { let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; if scancode == 0 { continue; } let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); let key_code = match PhysicalKey::from_scancode(scancode) { PhysicalKey::Code(code) => code, // TODO: validate that we can skip on unidentified keys (probably never occurs?) _ => continue, }; // 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 as VIRTUAL_KEY, 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) => Key::Character(SmolStr::new(str)), ToUnicodeResult::Dead(dead_char) => { //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(SmolStr::new("/")) } 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 { 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_retain(mod_state); if let Some(keys) = layout.keys.get_mut(&mod_state) { if let Some(key) = keys.get_mut(&KeyCode::AltRight) { *key = Key::Named(NamedKey::AltGraph); } } } } layout } fn to_unicode_string( key_state: &[u8; 256], vkey: u32, scancode: u32, locale_id: u64, ) -> ToUnicodeResult { unsafe { let mut label_wide = [0u16; 8]; let mut wide_len = ToUnicodeEx( vkey, scancode, (&key_state[0]) as *const _, (&mut label_wide[0]) as *mut _, label_wide.len() as i32, 0, locale_id as HKL, ); if wide_len < 0 { // If it's dead, we run `ToUnicode` again to consume the dead-key wide_len = ToUnicodeEx( vkey, scancode, (&key_state[0]) as *const _, (&mut label_wide[0]) as *mut _, label_wide.len() as i32, 0, locale_id as HKL, ); 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 } } #[derive(Debug, Clone, Eq, PartialEq)] enum ToUnicodeResult { Str(String), Dead(Option), None, } fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { matches!( vk, VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_ADD | VK_SUBTRACT | VK_DIVIDE | VK_DECIMAL | VK_SEPARATOR ) } fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY { let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; match keycode { KeyCode::Backquote => 0, KeyCode::Backslash => 0, KeyCode::BracketLeft => 0, KeyCode::BracketRight => 0, KeyCode::Comma => 0, KeyCode::Digit0 => 0, KeyCode::Digit1 => 0, KeyCode::Digit2 => 0, KeyCode::Digit3 => 0, KeyCode::Digit4 => 0, KeyCode::Digit5 => 0, KeyCode::Digit6 => 0, KeyCode::Digit7 => 0, KeyCode::Digit8 => 0, KeyCode::Digit9 => 0, KeyCode::Equal => 0, KeyCode::IntlBackslash => 0, KeyCode::IntlRo => 0, KeyCode::IntlYen => 0, KeyCode::KeyA => 0, KeyCode::KeyB => 0, KeyCode::KeyC => 0, KeyCode::KeyD => 0, KeyCode::KeyE => 0, KeyCode::KeyF => 0, KeyCode::KeyG => 0, KeyCode::KeyH => 0, KeyCode::KeyI => 0, KeyCode::KeyJ => 0, KeyCode::KeyK => 0, KeyCode::KeyL => 0, KeyCode::KeyM => 0, KeyCode::KeyN => 0, KeyCode::KeyO => 0, KeyCode::KeyP => 0, KeyCode::KeyQ => 0, KeyCode::KeyR => 0, KeyCode::KeyS => 0, KeyCode::KeyT => 0, KeyCode::KeyU => 0, KeyCode::KeyV => 0, KeyCode::KeyW => 0, KeyCode::KeyX => 0, KeyCode::KeyY => 0, KeyCode::KeyZ => 0, KeyCode::Minus => 0, KeyCode::Period => 0, KeyCode::Quote => 0, KeyCode::Semicolon => 0, KeyCode::Slash => 0, 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 => 0, KeyCode::Lang3 if is_japanese => VK_OEM_FINISH, KeyCode::Lang4 if is_japanese => 0, KeyCode::Lang5 if is_japanese => 0, 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 => 0, KeyCode::NumpadComma => VK_SEPARATOR, KeyCode::NumpadDecimal => VK_DECIMAL, KeyCode::NumpadDivide => VK_DIVIDE, KeyCode::NumpadEnter => VK_RETURN, KeyCode::NumpadEqual => 0, KeyCode::NumpadHash => 0, KeyCode::NumpadMemoryAdd => 0, KeyCode::NumpadMemoryClear => 0, KeyCode::NumpadMemoryRecall => 0, KeyCode::NumpadMemoryStore => 0, KeyCode::NumpadMemorySubtract => 0, KeyCode::NumpadMultiply => VK_MULTIPLY, KeyCode::NumpadParenLeft => 0, KeyCode::NumpadParenRight => 0, KeyCode::NumpadStar => 0, KeyCode::NumpadSubtract => VK_SUBTRACT, KeyCode::Escape => VK_ESCAPE, KeyCode::Fn => 0, KeyCode::FnLock => 0, 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 => 0, 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 => 0, KeyCode::Sleep => 0, KeyCode::AudioVolumeDown => VK_VOLUME_DOWN, KeyCode::AudioVolumeMute => VK_VOLUME_MUTE, KeyCode::AudioVolumeUp => VK_VOLUME_UP, KeyCode::WakeUp => 0, KeyCode::Hyper => 0, KeyCode::Turbo => 0, KeyCode::Abort => 0, KeyCode::Resume => 0, KeyCode::Suspend => 0, KeyCode::Again => 0, KeyCode::Copy => 0, KeyCode::Cut => 0, KeyCode::Find => 0, KeyCode::Open => 0, KeyCode::Paste => 0, KeyCode::Props => 0, KeyCode::Select => VK_SELECT, KeyCode::Undo => 0, KeyCode::Hiragana => 0, KeyCode::Katakana => 0, 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 => 0, KeyCode::F26 => 0, KeyCode::F27 => 0, KeyCode::F28 => 0, KeyCode::F29 => 0, KeyCode::F30 => 0, KeyCode::F31 => 0, KeyCode::F32 => 0, KeyCode::F33 => 0, KeyCode::F34 => 0, KeyCode::F35 => 0, _ => 0, } } /// 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: NativeKey, hkl: u64, has_alt_graph: bool, ) -> Key { // 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 = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; match vkey { VK_LBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_RBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse // I don't think this can be represented with a Key VK_CANCEL => Key::Unidentified(native_code), VK_MBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_XBUTTON1 => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_XBUTTON2 => Key::Unidentified(NativeKey::Unidentified), // Mouse VK_BACK => Key::Named(NamedKey::Backspace), VK_TAB => Key::Named(NamedKey::Tab), VK_CLEAR => Key::Named(NamedKey::Clear), VK_RETURN => Key::Named(NamedKey::Enter), VK_SHIFT => Key::Named(NamedKey::Shift), VK_CONTROL => Key::Named(NamedKey::Control), VK_MENU => Key::Named(NamedKey::Alt), VK_PAUSE => Key::Named(NamedKey::Pause), VK_CAPITAL => Key::Named(NamedKey::CapsLock), //VK_HANGEUL => Key::Named(NamedKey::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 VK_HANGUL if is_korean => Key::Named(NamedKey::HangulMode), VK_KANA if is_japanese => Key::Named(NamedKey::KanaMode), VK_JUNJA => Key::Named(NamedKey::JunjaMode), VK_FINAL => Key::Named(NamedKey::FinalMode), // VK_HANJA and VK_KANJI are defined as the same constant, therefore // we use appropriate conditions to differentate between them VK_HANJA if is_korean => Key::Named(NamedKey::HanjaMode), VK_KANJI if is_japanese => Key::Named(NamedKey::KanjiMode), VK_ESCAPE => Key::Named(NamedKey::Escape), VK_CONVERT => Key::Named(NamedKey::Convert), VK_NONCONVERT => Key::Named(NamedKey::NonConvert), VK_ACCEPT => Key::Named(NamedKey::Accept), VK_MODECHANGE => Key::Named(NamedKey::ModeChange), VK_SPACE => Key::Named(NamedKey::Space), VK_PRIOR => Key::Named(NamedKey::PageUp), VK_NEXT => Key::Named(NamedKey::PageDown), VK_END => Key::Named(NamedKey::End), VK_HOME => Key::Named(NamedKey::Home), VK_LEFT => Key::Named(NamedKey::ArrowLeft), VK_UP => Key::Named(NamedKey::ArrowUp), VK_RIGHT => Key::Named(NamedKey::ArrowRight), VK_DOWN => Key::Named(NamedKey::ArrowDown), VK_SELECT => Key::Named(NamedKey::Select), VK_PRINT => Key::Named(NamedKey::Print), VK_EXECUTE => Key::Named(NamedKey::Execute), VK_SNAPSHOT => Key::Named(NamedKey::PrintScreen), VK_INSERT => Key::Named(NamedKey::Insert), VK_DELETE => Key::Named(NamedKey::Delete), VK_HELP => Key::Named(NamedKey::Help), VK_LWIN => Key::Named(NamedKey::Super), VK_RWIN => Key::Named(NamedKey::Super), VK_APPS => Key::Named(NamedKey::ContextMenu), VK_SLEEP => Key::Named(NamedKey::Standby), // Numpad keys produce characters VK_NUMPAD0 => Key::Unidentified(native_code), VK_NUMPAD1 => Key::Unidentified(native_code), VK_NUMPAD2 => Key::Unidentified(native_code), VK_NUMPAD3 => Key::Unidentified(native_code), VK_NUMPAD4 => Key::Unidentified(native_code), VK_NUMPAD5 => Key::Unidentified(native_code), VK_NUMPAD6 => Key::Unidentified(native_code), VK_NUMPAD7 => Key::Unidentified(native_code), VK_NUMPAD8 => Key::Unidentified(native_code), VK_NUMPAD9 => Key::Unidentified(native_code), VK_MULTIPLY => Key::Unidentified(native_code), VK_ADD => Key::Unidentified(native_code), VK_SEPARATOR => Key::Unidentified(native_code), VK_SUBTRACT => Key::Unidentified(native_code), VK_DECIMAL => Key::Unidentified(native_code), VK_DIVIDE => Key::Unidentified(native_code), VK_F1 => Key::Named(NamedKey::F1), VK_F2 => Key::Named(NamedKey::F2), VK_F3 => Key::Named(NamedKey::F3), VK_F4 => Key::Named(NamedKey::F4), VK_F5 => Key::Named(NamedKey::F5), VK_F6 => Key::Named(NamedKey::F6), VK_F7 => Key::Named(NamedKey::F7), VK_F8 => Key::Named(NamedKey::F8), VK_F9 => Key::Named(NamedKey::F9), VK_F10 => Key::Named(NamedKey::F10), VK_F11 => Key::Named(NamedKey::F11), VK_F12 => Key::Named(NamedKey::F12), VK_F13 => Key::Named(NamedKey::F13), VK_F14 => Key::Named(NamedKey::F14), VK_F15 => Key::Named(NamedKey::F15), VK_F16 => Key::Named(NamedKey::F16), VK_F17 => Key::Named(NamedKey::F17), VK_F18 => Key::Named(NamedKey::F18), VK_F19 => Key::Named(NamedKey::F19), VK_F20 => Key::Named(NamedKey::F20), VK_F21 => Key::Named(NamedKey::F21), VK_F22 => Key::Named(NamedKey::F22), VK_F23 => Key::Named(NamedKey::F23), VK_F24 => Key::Named(NamedKey::F24), VK_NAVIGATION_VIEW => Key::Unidentified(native_code), VK_NAVIGATION_MENU => Key::Unidentified(native_code), VK_NAVIGATION_UP => Key::Unidentified(native_code), VK_NAVIGATION_DOWN => Key::Unidentified(native_code), VK_NAVIGATION_LEFT => Key::Unidentified(native_code), VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), VK_NUMLOCK => Key::Named(NamedKey::NumLock), VK_SCROLL => Key::Named(NamedKey::ScrollLock), VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), //VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), VK_OEM_FJ_LOYA => Key::Unidentified(native_code), VK_OEM_FJ_ROYA => Key::Unidentified(native_code), VK_LSHIFT => Key::Named(NamedKey::Shift), VK_RSHIFT => Key::Named(NamedKey::Shift), VK_LCONTROL => Key::Named(NamedKey::Control), VK_RCONTROL => Key::Named(NamedKey::Control), VK_LMENU => Key::Named(NamedKey::Alt), VK_RMENU => { if has_alt_graph { Key::Named(NamedKey::AltGraph) } else { Key::Named(NamedKey::Alt) } } VK_BROWSER_BACK => Key::Named(NamedKey::BrowserBack), VK_BROWSER_FORWARD => Key::Named(NamedKey::BrowserForward), VK_BROWSER_REFRESH => Key::Named(NamedKey::BrowserRefresh), VK_BROWSER_STOP => Key::Named(NamedKey::BrowserStop), VK_BROWSER_SEARCH => Key::Named(NamedKey::BrowserSearch), VK_BROWSER_FAVORITES => Key::Named(NamedKey::BrowserFavorites), VK_BROWSER_HOME => Key::Named(NamedKey::BrowserHome), VK_VOLUME_MUTE => Key::Named(NamedKey::AudioVolumeMute), VK_VOLUME_DOWN => Key::Named(NamedKey::AudioVolumeDown), VK_VOLUME_UP => Key::Named(NamedKey::AudioVolumeUp), VK_MEDIA_NEXT_TRACK => Key::Named(NamedKey::MediaTrackNext), VK_MEDIA_PREV_TRACK => Key::Named(NamedKey::MediaTrackPrevious), VK_MEDIA_STOP => Key::Named(NamedKey::MediaStop), VK_MEDIA_PLAY_PAUSE => Key::Named(NamedKey::MediaPlayPause), VK_LAUNCH_MAIL => Key::Named(NamedKey::LaunchMail), VK_LAUNCH_MEDIA_SELECT => Key::Named(NamedKey::LaunchMediaPlayer), VK_LAUNCH_APP1 => Key::Named(NamedKey::LaunchApplication1), VK_LAUNCH_APP2 => Key::Named(NamedKey::LaunchApplication2), // This function only converts "non-printable" VK_OEM_1 => Key::Unidentified(native_code), VK_OEM_PLUS => Key::Unidentified(native_code), VK_OEM_COMMA => Key::Unidentified(native_code), VK_OEM_MINUS => Key::Unidentified(native_code), VK_OEM_PERIOD => Key::Unidentified(native_code), VK_OEM_2 => Key::Unidentified(native_code), VK_OEM_3 => Key::Unidentified(native_code), VK_GAMEPAD_A => Key::Unidentified(native_code), VK_GAMEPAD_B => Key::Unidentified(native_code), VK_GAMEPAD_X => Key::Unidentified(native_code), VK_GAMEPAD_Y => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_MENU => Key::Unidentified(native_code), VK_GAMEPAD_VIEW => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), // This function only converts "non-printable" VK_OEM_4 => Key::Unidentified(native_code), VK_OEM_5 => Key::Unidentified(native_code), VK_OEM_6 => Key::Unidentified(native_code), VK_OEM_7 => Key::Unidentified(native_code), VK_OEM_8 => Key::Unidentified(native_code), VK_OEM_AX => Key::Unidentified(native_code), VK_OEM_102 => Key::Unidentified(native_code), VK_ICO_HELP => Key::Unidentified(native_code), VK_ICO_00 => Key::Unidentified(native_code), VK_PROCESSKEY => Key::Named(NamedKey::Process), VK_ICO_CLEAR => Key::Unidentified(native_code), VK_PACKET => Key::Unidentified(native_code), VK_OEM_RESET => Key::Unidentified(native_code), VK_OEM_JUMP => Key::Unidentified(native_code), VK_OEM_PA1 => Key::Unidentified(native_code), VK_OEM_PA2 => Key::Unidentified(native_code), VK_OEM_PA3 => Key::Unidentified(native_code), VK_OEM_WSCTRL => Key::Unidentified(native_code), VK_OEM_CUSEL => Key::Unidentified(native_code), VK_OEM_ATTN => Key::Named(NamedKey::Attn), VK_OEM_FINISH => { if is_japanese { Key::Named(NamedKey::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 `NamedKey::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) } } VK_OEM_COPY => Key::Named(NamedKey::Copy), VK_OEM_AUTO => Key::Named(NamedKey::Hankaku), VK_OEM_ENLW => Key::Named(NamedKey::Zenkaku), VK_OEM_BACKTAB => Key::Named(NamedKey::Romaji), VK_ATTN => Key::Named(NamedKey::KanaMode), VK_CRSEL => Key::Named(NamedKey::CrSel), VK_EXSEL => Key::Named(NamedKey::ExSel), VK_EREOF => Key::Named(NamedKey::EraseEof), VK_PLAY => Key::Named(NamedKey::Play), VK_ZOOM => Key::Named(NamedKey::ZoomToggle), VK_NONAME => Key::Unidentified(native_code), VK_PA1 => Key::Unidentified(native_code), VK_OEM_CLEAR => Key::Named(NamedKey::Clear), _ => Key::Unidentified(native_code), } } winit-0.29.15/src/platform_impl/windows/minimal_ime.rs000064400000000000000000000041061046102023000211110ustar 00000000000000use std::sync::{ atomic::{AtomicBool, Ordering::Relaxed}, Mutex, }; use winapi::{ shared::{ minwindef::{LPARAM, WPARAM}, windef::HWND, }, um::winuser, }; use crate::platform_impl::platform::{event_loop::ProcResult, keyboard::next_kbd_msg}; pub struct MinimalIme { // True if we're currently receiving messages belonging to a finished IME session. getting_ime_text: AtomicBool, utf16parts: Mutex>, } impl Default for MinimalIme { fn default() -> Self { MinimalIme { getting_ime_text: AtomicBool::new(false), utf16parts: Mutex::new(Vec::with_capacity(16)), } } } impl MinimalIme { pub(crate) fn process_message( &self, hwnd: HWND, msg_kind: u32, wparam: WPARAM, _lparam: LPARAM, result: &mut ProcResult, ) -> Option { match msg_kind { winuser::WM_IME_ENDCOMPOSITION => { self.getting_ime_text.store(true, Relaxed); } winuser::WM_CHAR | winuser::WM_SYSCHAR => { if self.getting_ime_text.load(Relaxed) { *result = ProcResult::Value(0); self.utf16parts.lock().unwrap().push(wparam as u16); // It's important that we push the new character and release the lock // before getting the next message let next_msg = next_kbd_msg(hwnd); let more_char_coming = next_msg .map(|m| matches!(m.message, winuser::WM_CHAR | winuser::WM_SYSCHAR)) .unwrap_or(false); if !more_char_coming { let mut utf16parts = self.utf16parts.lock().unwrap(); let result = String::from_utf16(&utf16parts).ok(); utf16parts.clear(); self.getting_ime_text.store(false, Relaxed); return result; } } } _ => (), } None } } winit-0.29.15/src/platform_impl/windows/mod.rs000064400000000000000000000104671046102023000174170ustar 00000000000000#![cfg(windows_platform)] use smol_str::SmolStr; use windows_sys::Win32::{ Foundation::{HANDLE, HWND}, UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX}, }; pub(crate) use self::{ event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }, icon::WinIcon, monitor::{MonitorHandle, VideoMode}, window::Window, }; pub use self::icon::WinIcon as PlatformIcon; use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; use crate::keyboard::Key; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub owner: Option, pub menu: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, pub skip_taskbar: bool, pub class_name: String, pub decoration_shadow: bool, } impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { owner: None, menu: None, taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, skip_taskbar: false, class_name: "Window Class".to_string(), decoration_shadow: false, } } } unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(u32); impl DeviceId { pub const 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(self.0 as HANDLE) } else { None } } } // 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: u32) -> RootDeviceId { RootDeviceId(DeviceId(id)) } pub type OsError = std::io::Error; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeyEventExtra { pub text_with_all_modifers: Option, pub key_without_modifiers: Key, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} impl WindowId { pub const unsafe fn dummy() -> Self { WindowId(0) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0 as u64 } } impl From for HWND { fn from(window_id: WindowId) -> Self { window_id.0 } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as HWND) } } #[inline(always)] const fn get_xbutton_wparam(x: u32) -> u16 { hiword(x) } #[inline(always)] const fn get_x_lparam(x: u32) -> i16 { loword(x) as _ } #[inline(always)] const fn get_y_lparam(x: u32) -> i16 { hiword(x) as _ } #[inline(always)] pub(crate) const fn primarylangid(lgid: u16) -> u16 { lgid & 0x3FF } #[inline(always)] pub(crate) const fn loword(x: u32) -> u16 { (x & 0xFFFF) as u16 } #[inline(always)] const fn hiword(x: u32) -> u16 { ((x >> 16) & 0xFFFF) as u16 } #[inline(always)] unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize { #[cfg(target_pointer_width = "64")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW(hwnd, nindex) }; #[cfg(target_pointer_width = "32")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongW(hwnd, nindex) as isize }; } #[inline(always)] unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize { #[cfg(target_pointer_width = "64")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW(hwnd, nindex, dwnewlong) }; #[cfg(target_pointer_width = "32")] return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize }; } #[macro_use] mod util; mod dark_mode; mod definitions; 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; winit-0.29.15/src/platform_impl/windows/monitor.rs000064400000000000000000000201421046102023000203160ustar 00000000000000use std::{ collections::{BTreeSet, VecDeque}, hash::Hash, io, mem, ptr, }; use windows_sys::Win32::{ Foundation::{BOOL, HWND, LPARAM, POINT, RECT}, Graphics::Gdi::{ EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, }, }; use super::util::decode_wide; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::VideoMode as RootVideoMode, platform_impl::platform::{ dpi::{dpi_to_scale_factor, get_monitor_dpi}, util::has_flag, window::Window, }, }; #[derive(Clone)] pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, // DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen pub(crate) native_video_mode: Box, } impl PartialEq for VideoMode { fn eq(&self, other: &Self) -> bool { self.size == other.size && self.bit_depth == other.bit_depth && self.refresh_rate_millihertz == other.refresh_rate_millihertz && 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_millihertz.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_millihertz", &self.refresh_rate_millihertz) .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_millihertz(&self) -> u32 { self.refresh_rate_millihertz } pub fn monitor(&self) -> MonitorHandle { self.monitor.clone() } } #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct MonitorHandle(HMONITOR); // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 unsafe impl Send for MonitorHandle {} unsafe extern "system" fn monitor_enum_proc( hmonitor: HMONITOR, _hdc: HDC, _place: *mut RECT, data: LPARAM, ) -> BOOL { let monitors = data as *mut VecDeque; unsafe { (*monitors).push_back(MonitorHandle::new(hmonitor)) }; true.into() // continue enumeration } pub fn available_monitors() -> VecDeque { let mut monitors: VecDeque = VecDeque::new(); unsafe { EnumDisplayMonitors( 0, ptr::null(), Some(monitor_enum_proc), &mut monitors as *mut _ as LPARAM, ); } 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) } impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() } pub fn primary_monitor(&self) -> Option { let monitor = primary_monitor(); Some(monitor) } } pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() }; 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 == false.into() { Err(io::Error::last_os_error()) } else { Ok(monitor_info) } } impl MonitorHandle { pub(crate) fn new(hmonitor: HMONITOR) -> Self { MonitorHandle(hmonitor) } #[inline] pub fn name(&self) -> Option { let monitor_info = get_monitor_info(self.0).unwrap(); Some( decode_wide(&monitor_info.szDevice) .to_string_lossy() .to_string(), ) } #[inline] pub fn native_identifier(&self) -> String { self.name().unwrap() } #[inline] pub fn hmonitor(&self) -> HMONITOR { self.0 } #[inline] pub fn size(&self) -> PhysicalSize { let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor; PhysicalSize { width: (rc_monitor.right - rc_monitor.left) as u32, height: (rc_monitor.bottom - rc_monitor.top) as u32, } } #[inline] pub fn refresh_rate_millihertz(&self) -> Option { let monitor_info = get_monitor_info(self.0).ok()?; let device_name = monitor_info.szDevice.as_ptr(); unsafe { let mut mode: DEVMODEW = mem::zeroed(); mode.dmSize = mem::size_of_val(&mode) as u16; if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0) == false.into() { None } else { Some(mode.dmDisplayFrequency * 1000) } } } #[inline] pub fn position(&self) -> PhysicalPosition { get_monitor_info(self.0) .map(|info| { let rc_monitor = info.monitorInfo.rcMonitor; PhysicalPosition { x: rc_monitor.left, y: rc_monitor.top, } }) .unwrap_or(PhysicalPosition { x: 0, y: 0 }) } #[inline] pub fn scale_factor(&self) -> f64 { dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } #[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 mod_map = |mode: RootVideoMode| mode.video_mode; let monitor_info = match get_monitor_info(self.0) { Ok(monitor_info) => monitor_info, Err(error) => { log::warn!("Error from get_monitor_info: {error}"); return modes.into_iter().map(mod_map); } }; let device_name = monitor_info.szDevice.as_ptr(); let mut i = 0; loop { let mut mode: DEVMODEW = unsafe { mem::zeroed() }; mode.dmSize = mem::size_of_val(&mode) as u16; if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() { break; } const REQUIRED_FIELDS: u32 = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; assert!(has_flag(mode.dmFields, REQUIRED_FIELDS)); // Use Ord impl of RootVideoMode modes.insert(RootVideoMode { video_mode: VideoMode { size: (mode.dmPelsWidth, mode.dmPelsHeight), bit_depth: mode.dmBitsPerPel as u16, refresh_rate_millihertz: mode.dmDisplayFrequency * 1000, monitor: self.clone(), native_video_mode: Box::new(mode), }, }); i += 1; } modes.into_iter().map(mod_map) } } winit-0.29.15/src/platform_impl/windows/raw_input.rs000064400000000000000000000266631046102023000206550ustar 00000000000000use std::{ mem::{self, size_of}, ptr, }; use windows_sys::Win32::{ Devices::HumanInterfaceDevice::{ HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC, }, Foundation::{HANDLE, HWND}, UI::{ Input::{ GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList, KeyboardAndMouse::{MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT}, RegisterRawInputDevices, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, RAWKEYBOARD, RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, WindowsAndMessaging::{ RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP, }, }, }; use crate::{ event::ElementState, event_loop::DeviceEvents, keyboard::{KeyCode, PhysicalKey}, platform::scancode::PhysicalKeyExtScancode, 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(ptr::null_mut(), &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(buffer.as_mut_ptr(), &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) } #[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 { RIM_TYPEMOUSE => RawDeviceInfo::Mouse(info.Anonymous.mouse), RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard), 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( handle, RIDI_DEVICEINFO, &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(handle, RIDI_DEVICENAME, ptr::null_mut(), &mut minimum_size) }; if status != 0 { return None; } let mut name: Vec = Vec::with_capacity(minimum_size as _); let status = unsafe { GetRawInputDeviceInfoW( handle, RIDI_DEVICENAME, 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 _) }; util::decode_wide(&name).into_string().ok() } pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { let device_size = size_of::() as u32; unsafe { RegisterRawInputDevices(devices.as_ptr(), devices.len() as u32, device_size) == true.into() } } pub fn register_all_mice_and_keyboards_for_raw_input( mut window_handle: HWND, filter: DeviceEvents, ) -> 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 { DeviceEvents::Never => { window_handle = 0; RIDEV_REMOVE } DeviceEvents::WhenFocused => RIDEV_DEVNOTIFY, DeviceEvents::Always => 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, &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: u32, 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) { Some(ElementState::Pressed) } else if util::has_flag(button_flags, up_flag) { Some(ElementState::Released) } else { None } } pub fn get_raw_mouse_button_state(button_flags: u32) -> [Option; 5] { [ button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP), button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP), ] } pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option { 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. unsafe { 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 None; } let physical_key = if 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 PhysicalKey::Code(KeyCode::NumLock) } else { PhysicalKey::from_scancode(scancode as u32) }; if keyboard.VKey == VK_SHIFT { if let PhysicalKey::Code(code) = physical_key { 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 None; } _ => (), } } } Some(physical_key) } winit-0.29.15/src/platform_impl/windows/util.rs000064400000000000000000000240051046102023000176060ustar 00000000000000use std::{ ffi::{c_void, OsStr, OsString}, io, iter::once, mem, ops::BitAnd, os::windows::prelude::{OsStrExt, OsStringExt}, ptr, sync::atomic::{AtomicBool, Ordering}, }; use once_cell::sync::Lazy; use windows_sys::{ core::{HRESULT, PCWSTR}, Win32::{ Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT}, Graphics::Gdi::{ClientToScreen, HMONITOR}, System::{ LibraryLoader::{GetProcAddress, LoadLibraryA}, SystemServices::IMAGE_DOS_HEADER, }, UI::{ HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS}, Input::{ KeyboardAndMouse::GetActiveWindow, Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO}, }, WindowsAndMessaging::{ ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement, GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE, WINDOWPLACEMENT, }, }, }, }; use crate::window::CursorIcon; pub fn encode_wide(string: impl AsRef) -> Vec { string.as_ref().encode_wide().chain(once(0)).collect() } pub fn decode_wide(mut wide_c_string: &[u16]) -> OsString { if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) { wide_c_string = &wide_c_string[..null_pos]; } OsString::from_wide(wide_c_string) } pub fn has_flag(bitset: T, flag: T) -> bool where T: Copy + PartialEq + BitAnd, { bitset & flag == flag } pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> { if result != false.into() { Ok(()) } else { Err(io::Error::last_os_error()) } } pub enum WindowArea { Outer, Inner, } impl WindowArea { pub fn get_rect(self, hwnd: HWND) -> Result { let mut rect = unsafe { mem::zeroed() }; match self { WindowArea::Outer => { win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?; } WindowArea::Inner => unsafe { let mut top_left = mem::zeroed(); win_to_err(ClientToScreen(hwnd, &mut top_left))?; win_to_err(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 is_maximized(window: HWND) -> bool { unsafe { let mut placement: WINDOWPLACEMENT = mem::zeroed(); placement.length = mem::size_of::() as u32; GetWindowPlacement(window, &mut placement); placement.showCmd == SW_MAXIMIZE } } 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(BOOL::from(!hidden)) }; } } pub fn get_cursor_clip() -> Result { unsafe { let mut rect: RECT = mem::zeroed(); win_to_err(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) -> Result<(), io::Error> { unsafe { let rect_ptr = rect .as_ref() .map(|r| r as *const RECT) .unwrap_or(ptr::null()); win_to_err(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_minimized(window: HWND) -> bool { unsafe { IsIconic(window) != false.into() } } pub fn get_instance_handle() -> 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: IMAGE_DOS_HEADER; } unsafe { &__ImageBase as *const _ as _ } } pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR { match cursor { CursorIcon::Default => IDC_ARROW, CursorIcon::Pointer => 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 as some functions // may not be available on all Windows platforms supported by winit. // // `library` and `function` must be zero-terminated. pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { assert_eq!(library.chars().last(), Some('\0')); 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 { LoadLibraryA(library.as_ptr()) }; if module == 0 { return None; } unsafe { GetProcAddress(module, function.as_ptr()) }.map(|function_ptr| function_ptr as _) } macro_rules! get_function { ($lib:expr, $func:ident) => { crate::platform_impl::platform::util::get_function_impl( concat!($lib, '\0'), concat!(stringify!($func), '\0'), ) .map(|f| unsafe { std::mem::transmute::<*const _, $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; pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( rect: *mut RECT, dwStyle: u32, bMenu: BOOL, dwExStyle: u32, dpi: u32, ) -> BOOL; pub type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: u32, entriesCount: *mut u32, pointerCount: *mut u32, pointerInfo: *mut POINTER_INFO, ) -> BOOL; pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL; pub type GetPointerDeviceRects = unsafe extern "system" fn( device: HANDLE, pointerDeviceRect: *mut RECT, displayRect: *mut RECT, ) -> BOOL; pub type GetPointerTouchInfo = unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL; pub type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> 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 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)); pub static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); pub static SKIP_POINTER_FRAME_MESSAGES: Lazy> = Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); pub static GET_POINTER_DEVICE_RECTS: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); pub static GET_POINTER_TOUCH_INFO: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); pub static GET_POINTER_PEN_INFO: Lazy> = Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); winit-0.29.15/src/platform_impl/windows/window.rs000064400000000000000000001531341046102023000201460ustar 00000000000000#![cfg(windows_platform)] use std::{ cell::Cell, ffi::c_void, io, mem::{self, MaybeUninit}, panic, ptr, sync::{mpsc::channel, Arc, Mutex, MutexGuard}, }; use windows_sys::Win32::{ Foundation::{ HWND, LPARAM, OLE_E_WRONGCOMPOBJ, POINT, POINTS, RECT, RPC_E_CHANGED_MODE, S_OK, WPARAM, }, Graphics::{ Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, Gdi::{ ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DeleteObject, InvalidateRgn, RedrawWindow, CDS_FULLSCREEN, DISP_CHANGE_BADFLAGS, DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL, RDW_INTERNALPAINT, }, }, System::{ Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED, }, Ole::{OleInitialize, RegisterDragDrop}, }, UI::{ Input::{ KeyboardAndMouse::{ EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, ToUnicode, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, VIRTUAL_KEY, VK_LMENU, VK_MENU, VK_SPACE, }, Touch::{RegisterTouchWindow, TWF_WANTPALM}, }, WindowsAndMessaging::{ CreateWindowExW, EnableMenuItem, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor, SetCursorPos, SetForegroundWindow, SetMenuDefaultItem, SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos, SetWindowTextW, TrackPopupMenu, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, NID_READY, PM_NOREMOVE, SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, TPM_RETURNCMD, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WNDCLASSEXW, }, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, platform_impl::platform::{ dark_mode::try_theme, definitions::{ CLSID_TaskbarList, IID_ITaskbarList, IID_ITaskbarList2, ITaskbarList, ITaskbarList2, }, 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}, ime::ImeContext, keyboard::KeyEventBuilder, monitor::{self, MonitorHandle}, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, Fullscreen, PlatformSpecificWindowBuilderAttributes, WindowId, }, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }, }; /// The Win32 implementation of the main `Window` object. pub(crate) struct Window { /// Main handle for the window. window: HWND, /// The current window state. window_state: Arc>, // The events loop proxy. thread_executor: event_loop::EventLoopThreadExecutor, } impl Window { pub(crate) 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(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { // TODO: Use `thread_executor` here f(self) } pub(crate) fn maybe_wait_on_main(&self, f: impl FnOnce(&Self) -> R + Send) -> R { // TODO: Use `thread_executor` here f(self) } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } pub fn set_title(&self, text: &str) { let wide_text = util::encode_wide(text); unsafe { SetWindowTextW(self.hwnd(), wide_text.as_ptr()); } } pub fn set_transparent(&self, transparent: 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().unwrap(), window, |f| { f.set(WindowFlags::TRANSPARENT, transparent) }); }); } pub fn set_blur(&self, _blur: bool) {} #[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 || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::VISIBLE, visible) }); }); } #[inline] pub fn is_visible(&self) -> Option { Some(unsafe { IsWindowVisible(self.window) == 1 }) } #[inline] pub fn request_redraw(&self) { // NOTE: mark that we requested a redraw to handle requests during `WM_PAINT` handling. self.window_state.lock().unwrap().redraw_requested = true; unsafe { RedrawWindow(self.hwnd(), ptr::null(), 0, RDW_INTERNALPAINT); } } #[inline] pub fn pre_present_notify(&self) {} #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { util::WindowArea::Outer.get_rect(self.hwnd()) .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit") } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { ClientToScreen(self.hwnd(), &mut position) } == false.into() { panic!("Unexpected ClientToScreen failure: please report this error to https://github.com/rust-windowing/winit") } 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 || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); unsafe { SetWindowPos( self.hwnd(), 0, x, y, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, ); InvalidateRgn(self.hwnd(), 0, false.into()); } } #[inline] pub fn inner_size(&self) -> PhysicalSize { let mut rect: RECT = unsafe { mem::zeroed() }; if unsafe { GetClientRect(self.hwnd(), &mut rect) } == false.into() { panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit") } PhysicalSize::new( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, ) } #[inline] pub fn outer_size(&self) -> PhysicalSize { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| { PhysicalSize::new( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, ) }) .unwrap() } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let physical_size = size.to_physical::(scale_factor); let window_flags = self.window_state_lock().window_flags; window_flags.set_size(self.hwnd(), physical_size); if physical_size != self.inner_size() { let window_state = Arc::clone(&self.window_state); let window = self.window; self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); } None } #[inline] pub fn set_min_inner_size(&self, size: Option) { self.window_state_lock().min_size = size; // Make windows re-check the window size bounds. let size = self.inner_size(); self.request_inner_size(size.into()); } #[inline] pub fn set_max_inner_size(&self, size: Option) { self.window_state_lock().max_size = size; // Make windows re-check the window size bounds. let size = self.inner_size(); self.request_inner_size(size.into()); } #[inline] pub fn resize_increments(&self) -> Option> { None } #[inline] pub fn set_resize_increments(&self, _increments: Option) {} #[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 || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); } #[inline] pub fn is_resizable(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::RESIZABLE) } #[inline] pub fn set_enabled_buttons(&self, buttons: WindowButtons) { 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().unwrap(), window, |f| { f.set( WindowFlags::MINIMIZABLE, buttons.contains(WindowButtons::MINIMIZE), ); f.set( WindowFlags::MAXIMIZABLE, buttons.contains(WindowButtons::MAXIMIZE), ); f.set( WindowFlags::CLOSABLE, buttons.contains(WindowButtons::CLOSE), ) }); }); } pub fn enabled_buttons(&self) -> WindowButtons { let mut buttons = WindowButtons::empty(); let window_state = self.window_state_lock(); if window_state.window_flags.contains(WindowFlags::MINIMIZABLE) { buttons |= WindowButtons::MINIMIZE; } if window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) { buttons |= WindowButtons::MAXIMIZE; } if window_state.window_flags.contains(WindowFlags::CLOSABLE) { buttons |= WindowButtons::CLOSE; } buttons } /// Returns the `hwnd` of this window. #[inline] pub fn hwnd(&self) -> HWND { self.window } #[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 as *mut _; let hinstance = unsafe { super::get_window_long(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 as *mut _; let hinstance = unsafe { super::get_window_long(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. std::num::NonZeroIsize::new_unchecked(self.window) }); let hinstance = unsafe { super::get_window_long(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(0, util::to_windows_cursor(cursor)); SetCursor(cursor); }); } #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let confine = match mode { CursorGrabMode::None => false, CursorGrabMode::Confined => true, CursorGrabMode::Locked => { return Err(ExternalError::NotSupported(NotSupportedError::new())) } }; let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine)) .map_err(|e| ExternalError::Os(os_error!(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(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); rx.recv().unwrap().ok(); } #[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) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } if SetCursorPos(point.x, point.y) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } } Ok(()) } unsafe fn handle_os_dragging(&self, wparam: WPARAM) { let window = self.window; let window_state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || { { let mut guard = window_state.lock().unwrap(); if !guard.dragging { guard.dragging = true; } else { return; } } 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, }; // ReleaseCapture needs to execute on the main thread unsafe { ReleaseCapture() }; unsafe { PostMessageW( window, WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM, ) }; }); } #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(HTCAPTION as WPARAM); } Ok(()) } #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(match direction { ResizeDirection::East => HTRIGHT, ResizeDirection::North => HTTOP, ResizeDirection::NorthEast => HTTOPRIGHT, ResizeDirection::NorthWest => HTTOPLEFT, ResizeDirection::South => HTBOTTOM, ResizeDirection::SouthEast => HTBOTTOMRIGHT, ResizeDirection::SouthWest => HTBOTTOMLEFT, ResizeDirection::West => HTLEFT, } as WPARAM); } Ok(()) } unsafe fn handle_showing_window_menu(&self, position: Position) { unsafe { let point = { let mut point = POINT { x: 0, y: 0 }; let scale_factor = self.scale_factor(); let (x, y) = position.to_physical::(scale_factor).into(); point.x = x; point.y = y; if ClientToScreen(self.hwnd(), &mut point) == false.into() { warn!("Can't convert client-area coordinates to screen coordinates when showing window menu."); return; } point }; // get the current system menu let h_menu = GetSystemMenu(self.hwnd(), 0); if h_menu == 0 { warn!("The corresponding window doesn't have a system menu"); // This situation should not be treated as an error so just return without showing menu. return; } fn enable(b: bool) -> MENU_ITEM_STATE { if b { MFS_ENABLED } else { MFS_DISABLED } } // Change the menu items according to the current window status. let restore_btn = enable(self.is_maximized() && self.is_resizable()); let size_btn = enable(!self.is_maximized() && self.is_resizable()); let maximize_btn = enable(!self.is_maximized() && self.is_resizable()); EnableMenuItem(h_menu, SC_RESTORE, MF_BYCOMMAND | restore_btn); EnableMenuItem(h_menu, SC_MOVE, MF_BYCOMMAND | enable(!self.is_maximized())); EnableMenuItem(h_menu, SC_SIZE, MF_BYCOMMAND | size_btn); EnableMenuItem(h_menu, SC_MINIMIZE, MF_BYCOMMAND | MFS_ENABLED); EnableMenuItem(h_menu, SC_MAXIMIZE, MF_BYCOMMAND | maximize_btn); EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MFS_ENABLED); // Set the default menu item. SetMenuDefaultItem(h_menu, SC_CLOSE, 0); // Popup the system menu at the position. let result = TrackPopupMenu( h_menu, TPM_RETURNCMD | TPM_LEFTALIGN, // for now im using LTR, but we have to use user layout direction point.x, point.y, 0, self.hwnd(), std::ptr::null_mut(), ); if result == 0 { // User canceled the menu, no need to continue. return; } // Send the command that the user select to the corresponding window. if PostMessageW(self.hwnd(), WM_SYSCOMMAND, result as _, 0) == 0 { warn!("Can't post the system menu message to the window."); } } } #[inline] pub fn show_window_menu(&self, position: Position) { unsafe { self.handle_showing_window_menu(position); } } #[inline] pub fn set_cursor_hittest(&self, hittest: 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().unwrap(), window, |f| { f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest) }); }); Ok(()) } #[inline] pub fn id(&self) -> WindowId { WindowId(self.hwnd()) } #[inline] pub fn set_minimized(&self, minimized: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); let is_minimized = util::is_minimized(self.hwnd()); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| { f.set(WindowFlags::MINIMIZED, is_minimized) }); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MINIMIZED, minimized) }); }); } #[inline] pub fn is_minimized(&self) -> Option { Some(util::is_minimized(self.hwnd())) } #[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 || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |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 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().unwrap(); let old_fullscreen = window_state_lock.fullscreen.clone(); match (&old_fullscreen, &fullscreen) { // Return if we already are 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 == monitor::current_monitor(window) => { return } _ => {} } window_state_lock.fullscreen = fullscreen.clone(); drop(window_state_lock); self.thread_executor.execute_in_thread(move || { let _ = &window; // Change video mode if we're transitioning to or from exclusive // fullscreen match (&old_fullscreen, &fullscreen) { (_, Some(Fullscreen::Exclusive(video_mode))) => { let monitor = video_mode.monitor(); let monitor_info = monitor::get_monitor_info(monitor.hmonitor()).unwrap(); let res = unsafe { ChangeDisplaySettingsExW( monitor_info.szDevice.as_ptr(), &*video_mode.native_video_mode, 0, CDS_FULLSCREEN, ptr::null(), ) }; 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(_)), _) => { let res = unsafe { ChangeDisplaySettingsExW( ptr::null(), ptr::null(), 0, CDS_FULLSCREEN, ptr::null(), ) }; 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 = mem::zeroed(); PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE); } // Update window style WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Exclusive(_))), ); f.set( WindowFlags::MARKER_BORDERLESS_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Borderless(_))), ); }); // Mark as fullscreen window wrt to z-order // // this needs to be called before the below fullscreen SetWindowPos as this itself // will generate WM_SIZE messages of the old window size that can race with what we set below unsafe { taskbar_mark_fullscreen(window, fullscreen.is_some()); } // Update window bounds match &fullscreen { Some(fullscreen) => { // Save window bounds before entering fullscreen let placement = unsafe { let mut placement = mem::zeroed(); GetWindowPlacement(window, &mut placement); placement }; window_state.lock().unwrap().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) => monitor::current_monitor(window), }; let position: (i32, i32) = monitor.position().into(); let size: (u32, u32) = monitor.size().into(); unsafe { SetWindowPos( window, 0, position.0, position.1, size.0 as i32, size.1 as i32, SWP_ASYNCWINDOWPOS | SWP_NOZORDER, ); InvalidateRgn(window, 0, false.into()); } } None => { let mut window_state_lock = window_state.lock().unwrap(); if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { drop(window_state_lock); unsafe { SetWindowPlacement(window, &placement); InvalidateRgn(window, 0, false.into()); } } } } }); } #[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 || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); } #[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 set_window_level(&self, level: WindowLevel) { 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().unwrap(), window, |f| { f.set( WindowFlags::ALWAYS_ON_TOP, level == WindowLevel::AlwaysOnTop, ); f.set( WindowFlags::ALWAYS_ON_BOTTOM, level == WindowLevel::AlwaysOnBottom, ); }); }); } #[inline] pub fn current_monitor(&self) -> Option { Some(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_enable(&self, enabled: bool) { unsafe { EnableWindow(self.hwnd(), enabled.into()) }; } #[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; } #[inline] pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { let scale_factor = state.lock().unwrap().scale_factor; ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor); }); } #[inline] pub fn set_ime_allowed(&self, allowed: bool) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { state.lock().unwrap().ime_allowed = allowed; ImeContext::set_ime_allowed(window, allowed); }) } #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] pub fn request_user_attention(&self, request_type: Option) { let window = self.window; let active_window_handle = unsafe { GetActiveWindow() }; if window == active_window_handle { return; } 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 | FLASHW_TIMERNOFG, 0), }) .unwrap_or((FLASHW_STOP, 0)); let flash_info = FLASHWINFO { cbSize: mem::size_of::() as u32, hwnd: window, dwFlags: flags, uCount: count, dwTimeout: 0, }; FlashWindowEx(&flash_info); }); } #[inline] pub fn set_theme(&self, theme: Option) { try_theme(self.window, theme); } #[inline] pub fn theme(&self) -> Option { Some(self.window_state_lock().current_theme) } #[inline] pub fn has_focus(&self) -> bool { let window_state = self.window_state.lock().unwrap(); window_state.has_active_focus() } pub fn title(&self) -> String { let len = unsafe { GetWindowTextLengthW(self.window) } + 1; let mut buf = vec![0; len as usize]; unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) }; util::decode_wide(&buf).to_string_lossy().to_string() } #[inline] pub fn set_skip_taskbar(&self, skip: bool) { self.window_state_lock().skip_taskbar = skip; unsafe { set_skip_taskbar(self.hwnd(), skip) }; } #[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().unwrap(), window, |f| { f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) }); }); } #[inline] pub fn focus_window(&self) { let window_flags = self.window_state_lock().window_flags(); let is_visible = window_flags.contains(WindowFlags::VISIBLE); let is_minimized = util::is_minimized(self.hwnd()); let is_foreground = self.window == unsafe { GetForegroundWindow() }; if is_visible && !is_minimized && !is_foreground { unsafe { force_window_active(self.window) }; } } #[inline] pub fn set_content_protected(&self, protected: bool) { unsafe { SetWindowDisplayAffinity( self.hwnd(), if protected { WDA_EXCLUDEFROMCAPTURE } else { WDA_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 = VK_SPACE as VIRTUAL_KEY; let scancode = MapVirtualKeyW(vk as u32, MAPVK_VK_TO_VSC); let kbd_state = [0; 256]; let mut char_buff = [MaybeUninit::uninit(); 8]; ToUnicode( vk as u32, scancode, kbd_state.as_ptr(), char_buff[0].as_mut_ptr(), char_buff.len() as i32, 0, ); } } } 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. PostMessageW(self.hwnd(), DESTROY_MSG_ID.get(), 0, 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<'a, T: 'static> InitData<'a, T> { unsafe fn create_window(&self, window: HWND) -> Window { // Register for touch events if applicable { let digitizer = unsafe { GetSystemMetrics(SM_DIGITIZER) as u32 }; if digitizer & NID_READY != 0 { unsafe { RegisterTouchWindow(window, TWF_WANTPALM) }; } } let dpi = unsafe { 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_theme(window, self.attributes.preferred_theme); let window_state = { let window_state = WindowState::new( &self.attributes, scale_factor, current_theme, self.attributes.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { *f = self.window_flags }); window_state }; enable_non_client_dpi_scaling(window); unsafe { ImeContext::set_ime_allowed(window, false) }; Window { 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 { let ole_init_result = unsafe { OleInitialize(ptr::null_mut()) }; // It is ok if the initialize result is `S_FALSE` because it might happen that // multiple windows are created on the same thread. if ole_init_result == OLE_E_WRONGCOMPOBJ { panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); } else if ole_init_result == 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 = FileDropHandler::new( win.window, Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { file_drop_runner.send_event(e) } }), ); let handler_interface_ptr = unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void }; assert_eq!( unsafe { RegisterDragDrop(win.window, handler_interface_ptr) }, S_OK ); Some(file_drop_handler) } else { None }; event_loop::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), } } // 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 result = runner.catch_unwind(|| { let window = unsafe { self.create_window(window) }; let window_data = unsafe { self.create_window_data(&window) }; (window, window_data) }); result.map(|(win, userdata)| { self.window = Some(win); let userdata = Box::into_raw(Box::new(userdata)); 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 = unsafe { 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 hr = unsafe { DwmEnableBlurBehindWindow(win.hwnd(), &bb) }; if hr < 0 { warn!( "Setting transparent window is failed. HRESULT Code: 0x{:X}", hr ); } unsafe { DeleteObject(region) }; } win.set_skip_taskbar(self.pl_attribs.skip_taskbar); win.set_window_icon(self.attributes.window_icon.clone()); win.set_taskbar_icon(self.pl_attribs.taskbar_icon.clone()); let attributes = self.attributes.clone(); if attributes.content_protected { win.set_content_protected(true); } // Set visible before setting the size to ensure the // attribute is correctly applied. win.set_visible(attributes.visible); win.set_enabled_buttons(attributes.enabled_buttons); let size = attributes .inner_size .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); let max_size = attributes .max_inner_size .unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into()); let min_size = attributes .min_inner_size .unwrap_or_else(|| PhysicalSize::new(0, 0).into()); let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor()); win.request_inner_size(clamped_size); // let margins = MARGINS { // cxLeftWidth: 1, // cxRightWidth: 1, // cyTopHeight: 1, // cyBottomHeight: 1, // }; // dbg!(DwmExtendFrameIntoClientArea(win.hwnd(), &margins as *const _)); if let Some(position) = attributes.position { win.set_outer_position(position); } } } unsafe fn init( attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, event_loop: &EventLoopWindowTarget, ) -> Result where T: 'static, { let title = util::encode_wide(&attributes.title); let class_name = util::encode_wide(&pl_attribs.class_name); unsafe { register_window_class::(&class_name) }; 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_TOP, attributes.window_level == WindowLevel::AlwaysOnTop, ); window_flags.set( WindowFlags::ALWAYS_ON_BOTTOM, attributes.window_level == WindowLevel::AlwaysOnBottom, ); window_flags.set( WindowFlags::NO_BACK_BUFFER, pl_attribs.no_redirection_bitmap, ); window_flags.set(WindowFlags::MARKER_ACTIVATE, attributes.active); 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); // Will be changed later using `window.set_enabled_buttons` but we need to set a default here // so the diffing later can work. window_flags.set(WindowFlags::CLOSABLE, true); let mut fallback_parent = || match pl_attribs.owner { Some(parent) => { window_flags.set(WindowFlags::POPUP, true); Some(parent) } None => { window_flags.set(WindowFlags::ON_TASKBAR, true); None } }; #[cfg(feature = "rwh_06")] let parent = match attributes.parent_window.0 { Some(rwh_06::RawWindowHandle::Win32(handle)) => { window_flags.set(WindowFlags::CHILD, true); if pl_attribs.menu.is_some() { warn!("Setting a menu on a child window is unsupported"); } Some(handle.hwnd.get() as HWND) } Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"), None => fallback_parent(), }; #[cfg(not(feature = "rwh_06"))] let parent = fallback_parent(); let fullscreen = attributes.fullscreen.clone(); let maximized = attributes.maximized; let mut initdata = InitData { event_loop, attributes, pl_attribs: pl_attribs.clone(), window_flags, window: None, }; let (style, ex_style) = window_flags.to_window_styles(); let handle = unsafe { CreateWindowExW( ex_style, class_name.as_ptr(), title.as_ptr(), style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, parent.unwrap_or(0), pl_attribs.menu.unwrap_or(0), util::get_instance_handle(), &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) } if handle == 0 { return Err(os_error!(io::Error::last_os_error())); } // If the handle is non-null, then window creation must have succeeded, which means // that we *must* have populated the `InitData.window` field. let win = 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.0.is_some() { win.set_fullscreen(fullscreen.0.map(Into::into)); unsafe { force_window_active(win.window) }; } else if maximized { win.set_maximized(true); } Ok(win) } unsafe fn register_window_class(class_name: &[u16]) { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(super::event_loop::public_window_callback::), cbClsExtra: 0, cbWndExtra: 0, hInstance: util::get_instance_handle(), hIcon: 0, hCursor: 0, // must be null in order for cursor state to work properly hbrBackground: 0, lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), hIconSm: 0, }; // 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. unsafe { RegisterClassExW(&class) }; } struct ComInitialized(#[allow(dead_code)] *mut ()); impl Drop for ComInitialized { fn drop(&mut self) { unsafe { CoUninitialize() }; } } thread_local! { static COM_INITIALIZED: ComInitialized = { unsafe { CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED); ComInitialized(ptr::null_mut()) } }; static TASKBAR_LIST: Cell<*mut ITaskbarList> = Cell::new(ptr::null_mut()); static TASKBAR_LIST2: Cell<*mut ITaskbarList2> = Cell::new(ptr::null_mut()); } 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_LIST2.with(|task_bar_list2_ptr| { let mut task_bar_list2 = task_bar_list2_ptr.get(); if task_bar_list2.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList2, &mut task_bar_list2 as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list2).lpVtbl).parent.HrInit }; if unsafe { hr_init(task_bar_list2.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list2_ptr.set(task_bar_list2) } task_bar_list2 = task_bar_list2_ptr.get(); let mark_fullscreen_window = unsafe { (*(*task_bar_list2).lpVtbl).MarkFullscreenWindow }; unsafe { mark_fullscreen_window(task_bar_list2, handle, fullscreen.into()) }; }) } pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) { com_initialized(); TASKBAR_LIST.with(|task_bar_list_ptr| { let mut task_bar_list = task_bar_list_ptr.get(); if task_bar_list.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList, &mut task_bar_list as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list).lpVtbl).HrInit }; if unsafe { hr_init(task_bar_list.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list_ptr.set(task_bar_list) } task_bar_list = task_bar_list_ptr.get(); if skip { let delete_tab = unsafe { (*(*task_bar_list).lpVtbl).DeleteTab }; unsafe { delete_tab(task_bar_list, hwnd) }; } else { let add_tab = unsafe { (*(*task_bar_list).lpVtbl).AddTab }; unsafe { add_tab(task_bar_list, hwnd) }; } }); } unsafe fn force_window_active(handle: HWND) { // In some situation, 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 = unsafe { MapVirtualKeyW(VK_MENU as u32, MAPVK_VK_TO_VSC) }; let inputs = [ INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY, dwExtraInfo: 0, time: 0, }, }, }, INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, dwExtraInfo: 0, time: 0, }, }, }, ]; // Simulate a key press and release unsafe { SendInput( inputs.len() as u32, inputs.as_ptr(), mem::size_of::() as i32, ) }; unsafe { SetForegroundWindow(handle) }; } winit-0.29.15/src/platform_impl/windows/window_state.rs000064400000000000000000000424741046102023000213520ustar 00000000000000use crate::{ dpi::{PhysicalPosition, PhysicalSize, Size}, icon::Icon, keyboard::ModifiersState, platform_impl::platform::{event_loop, util, Fullscreen}, window::{CursorIcon, Theme, WindowAttributes}, }; use std::io; use std::sync::MutexGuard; use windows_sys::Win32::{ Foundation::{HWND, RECT}, Graphics::Gdi::InvalidateRgn, UI::WindowsAndMessaging::{ AdjustWindowRectEx, EnableMenuItem, GetMenu, GetSystemMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_BOTTOM, HWND_NOTOPMOST, HWND_TOPMOST, MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, SC_CLOSE, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWNOACTIVATE, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, }, }; /// Contains information about states and the window that the callback is going to use. pub(crate) struct WindowState { pub mouse: MouseProperties, /// Used by `WM_GETMINMAXINFO`. pub min_size: Option, pub max_size: Option, pub window_icon: Option, pub taskbar_icon: Option, pub saved_window: Option, pub scale_factor: f64, pub modifiers_state: ModifiersState, pub fullscreen: Option, pub current_theme: Theme, pub preferred_theme: Option, pub window_flags: WindowFlags, pub ime_state: ImeState, pub ime_allowed: bool, // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS pub is_active: bool, pub is_focused: bool, // Flag whether redraw was requested. pub redraw_requested: bool, pub dragging: bool, pub skip_taskbar: bool, } #[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(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CursorFlags: u8 { const GRABBED = 1 << 0; const HIDDEN = 1 << 1; const IN_WINDOW = 1 << 2; } } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WindowFlags: u32 { const RESIZABLE = 1 << 0; const MINIMIZABLE = 1 << 1; const MAXIMIZABLE = 1 << 2; const CLOSABLE = 1 << 3; const VISIBLE = 1 << 4; const ON_TASKBAR = 1 << 5; const ALWAYS_ON_TOP = 1 << 6; const ALWAYS_ON_BOTTOM = 1 << 7; const NO_BACK_BUFFER = 1 << 8; const TRANSPARENT = 1 << 9; const CHILD = 1 << 10; const MAXIMIZED = 1 << 11; const POPUP = 1 << 12; /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// included here to make masking easier. const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 13; const MARKER_BORDERLESS_FULLSCREEN = 1 << 14; /// 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 << 15; const MARKER_IN_SIZE_MOVE = 1 << 16; const MINIMIZED = 1 << 17; const IGNORE_CURSOR_EVENT = 1 << 18; /// Fully decorated window (incl. caption, border and drop shadow). const MARKER_DECORATIONS = 1 << 19; /// Drop shadow for undecorated windows. const MARKER_UNDECORATED_SHADOW = 1 << 20; const MARKER_ACTIVATE = 1 << 21; const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); } } #[derive(Eq, PartialEq)] pub enum ImeState { Disabled, Enabled, Preedit, } impl WindowState { pub(crate) fn new( attributes: &WindowAttributes, scale_factor: f64, current_theme: Theme, preferred_theme: Option, ) -> WindowState { WindowState { mouse: MouseProperties { cursor: CursorIcon::default(), capture_count: 0, cursor_flags: CursorFlags::empty(), last_position: None, }, min_size: attributes.min_inner_size, max_size: attributes.max_inner_size, window_icon: attributes.window_icon.clone(), taskbar_icon: None, saved_window: None, scale_factor, modifiers_state: ModifiersState::default(), fullscreen: None, current_theme, preferred_theme, window_flags: WindowFlags::empty(), ime_state: ImeState::Disabled, ime_allowed: false, is_active: false, is_focused: false, redraw_requested: false, dragging: false, skip_taskbar: false, } } 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) { // Required styles to properly support common window functionality like aero snap. let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU; let mut 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. } 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; } (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) { let flag = if !self.contains(WindowFlags::MARKER_ACTIVATE) { self.set(WindowFlags::MARKER_ACTIVATE, true); SW_SHOWNOACTIVATE } else { SW_SHOW }; unsafe { ShowWindow(window, flag); } } if diff.intersects(WindowFlags::ALWAYS_ON_TOP | WindowFlags::ALWAYS_ON_BOTTOM) { unsafe { SetWindowPos( window, match ( new.contains(WindowFlags::ALWAYS_ON_TOP), new.contains(WindowFlags::ALWAYS_ON_BOTTOM), ) { (true, false) => HWND_TOPMOST, (false, false) => HWND_NOTOPMOST, (false, true) => HWND_BOTTOM, (true, true) => unreachable!(), }, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, ); InvalidateRgn(window, 0, false.into()); } } if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { unsafe { 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 { 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) { let flags = MF_BYCOMMAND | if new.contains(WindowFlags::CLOSABLE) { MF_ENABLED } else { MF_DISABLED }; unsafe { EnableMenuItem(GetSystemMenu(window, 0), SC_CLOSE, flags); } } if !new.contains(WindowFlags::VISIBLE) { unsafe { 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.get(), 1, 0, ); // This condition is necessary to avoid having an unrestorable window if !new.contains(WindowFlags::MINIMIZED) { SetWindowLongW(window, GWL_STYLE, style as i32); SetWindowLongW(window, GWL_EXSTYLE, style_ex 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 SetWindowPos(window, 0, 0, 0, 0, 0, flags); SendMessageW( window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 0, 0, ); } } } pub fn adjust_rect(self, hwnd: HWND, mut rect: RECT) -> Result { unsafe { let mut style = GetWindowLongW(hwnd, GWL_STYLE) as u32; let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32; // Frameless style implemented by manually overriding the non-client area in `WM_NCCALCSIZE`. if !self.contains(WindowFlags::MARKER_DECORATIONS) { style &= !(WS_CAPTION | WS_SIZEBOX); } util::win_to_err({ let b_menu = GetMenu(hwnd) != 0; if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = ( *util::GET_DPI_FOR_WINDOW, *util::ADJUST_WINDOW_RECT_EX_FOR_DPI, ) { let dpi = get_dpi_for_window(hwnd); adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi) } else { AdjustWindowRectEx(&mut rect, style, b_menu.into(), style_ex) } })?; Ok(rect) } } pub fn adjust_size(self, hwnd: HWND, size: PhysicalSize) -> 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 = self.adjust_rect(hwnd, rect).unwrap_or(rect); let outer_x = (rect.right - rect.left).abs(); let outer_y = (rect.top - rect.bottom).abs(); PhysicalSize::new(outer_x as _, outer_y as _) } pub fn set_size(self, hwnd: HWND, size: PhysicalSize) { unsafe { let (width, height): (u32, u32) = self.adjust_size(hwnd, size).into(); SetWindowPos( hwnd, 0, 0, 0, width as _, height as _, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, ); InvalidateRgn(hwnd, 0, false.into()); } } } impl CursorFlags { fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { let client_rect = util::WindowArea::Inner.get_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(()) } } winit-0.29.15/src/window.rs000064400000000000000000001747611046102023000136200ustar 00000000000000//! The [`Window`] struct and associated types. use std::fmt; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError}, event_loop::EventLoopWindowTarget, monitor::{MonitorHandle, VideoMode}, platform_impl, SendSyncWrapper, }; pub use crate::icon::{BadIcon, Icon}; #[doc(inline)] pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; /// Represents a window. /// /// /// # Threading /// /// This is `Send + Sync`, meaning that it can be freely used from other /// threads. /// /// However, some platforms (macOS, Web and iOS) only allow user interface /// interactions on the main thread, so on those platforms, if you use the /// window from a thread other than the main, the code is scheduled to run on /// the main thread, and your thread may be blocked until that completes. /// /// /// # Example /// /// ```no_run /// use winit::{ /// event::{Event, WindowEvent}, /// event_loop::{ControlFlow, EventLoop}, /// window::Window, /// }; /// /// let mut event_loop = EventLoop::new().unwrap(); /// event_loop.set_control_flow(ControlFlow::Wait); /// let window = Window::new(&event_loop).unwrap(); /// /// event_loop.run(move |event, elwt| { /// match event { /// Event::WindowEvent { /// event: WindowEvent::CloseRequested, /// .. /// } => elwt.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) { self.window.maybe_wait_on_main(|w| { // 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(_)) = w.fullscreen().map(|f| f.into()) { w.set_fullscreen(None); } }) } } /// Identifier of a window. Unique for each window. /// /// Can be obtained with [`window.id()`](`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 { /// Returns a dummy id, useful for unit testing. /// /// # Safety /// /// 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 winit function will result in undefined behavior.** pub const unsafe fn dummy() -> Self { #[allow(unused_unsafe)] WindowId(unsafe { platform_impl::WindowId::dummy() }) } } impl From for u64 { fn from(window_id: WindowId) -> Self { window_id.0.into() } } impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id.into()) } } /// Object that allows building windows. #[derive(Clone, Default)] #[must_use] pub struct WindowBuilder { /// The attributes to use to create the window. pub(crate) 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 { pub inner_size: Option, pub min_inner_size: Option, pub max_inner_size: Option, pub position: Option, pub resizable: bool, pub enabled_buttons: WindowButtons, pub title: String, pub maximized: bool, pub visible: bool, pub transparent: bool, pub blur: bool, pub decorations: bool, pub window_icon: Option, pub preferred_theme: Option, pub resize_increments: Option, pub content_protected: bool, pub window_level: WindowLevel, pub active: bool, #[cfg(feature = "rwh_06")] pub(crate) parent_window: SendSyncWrapper>, pub(crate) fullscreen: SendSyncWrapper>, } impl Default for WindowAttributes { #[inline] fn default() -> WindowAttributes { WindowAttributes { inner_size: None, min_inner_size: None, max_inner_size: None, position: None, resizable: true, enabled_buttons: WindowButtons::all(), title: "winit window".to_owned(), maximized: false, fullscreen: SendSyncWrapper(None), visible: true, transparent: false, blur: false, decorations: true, window_level: Default::default(), window_icon: None, preferred_theme: None, resize_increments: None, content_protected: false, #[cfg(feature = "rwh_06")] parent_window: SendSyncWrapper(None), active: true, } } } impl WindowAttributes { /// Get the parent window stored on the attributes. #[cfg(feature = "rwh_06")] pub fn parent_window(&self) -> Option<&rwh_06::RawWindowHandle> { self.parent_window.0.as_ref() } /// Get `Fullscreen` option stored on the attributes. pub fn fullscreen(&self) -> Option<&Fullscreen> { self.fullscreen.0.as_ref() } } impl WindowBuilder { /// Initializes a new builder with default values. #[inline] pub fn new() -> Self { Default::default() } } impl WindowBuilder { /// Get the current window attributes. pub fn window_attributes(&self) -> &WindowAttributes { &self.window } /// Requests the window to be of specific dimensions. /// /// If this is not set, some platform-specific dimensions will be used. /// /// See [`Window::request_inner_size`] for details. #[inline] pub fn with_inner_size>(mut self, size: S) -> Self { self.window.inner_size = Some(size.into()); self } /// Sets the minimum dimensions a window can have. /// /// If this is not set, the window will have no minimum dimensions (aside /// from reserved). /// /// See [`Window::set_min_inner_size`] for details. #[inline] pub fn with_min_inner_size>(mut self, min_size: S) -> Self { self.window.min_inner_size = Some(min_size.into()); self } /// Sets the maximum dimensions a window can have. /// /// If this is not set, the window will have no maximum or will be set to /// the primary monitor's dimensions by the platform. /// /// See [`Window::set_max_inner_size`] for details. #[inline] pub fn with_max_inner_size>(mut self, max_size: S) -> Self { self.window.max_inner_size = Some(max_size.into()); self } /// Sets a desired initial position for the window. /// /// If this is not set, some platform-specific position will be chosen. /// /// See [`Window::set_outer_position`] for details. /// /// ## 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. /// - **X11:** The top left corner of the window, the window's "outer" /// position. /// - **Others:** Ignored. #[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. /// /// The default is `true`. /// /// See [`Window::set_resizable`] for details. #[inline] pub fn with_resizable(mut self, resizable: bool) -> Self { self.window.resizable = resizable; self } /// Sets the enabled window buttons. /// /// The default is [`WindowButtons::all`] /// /// See [`Window::set_enabled_buttons`] for details. #[inline] pub fn with_enabled_buttons(mut self, buttons: WindowButtons) -> Self { self.window.enabled_buttons = buttons; self } /// Sets the initial title of the window in the title bar. /// /// The default is `"winit window"`. /// /// See [`Window::set_title`] for details. #[inline] pub fn with_title>(mut self, title: T) -> Self { self.window.title = title.into(); self } /// Sets whether the window should be put into fullscreen upon creation. /// /// The default is `None`. /// /// See [`Window::set_fullscreen`] for details. #[inline] pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { self.window.fullscreen = SendSyncWrapper(fullscreen); self } /// Request that the window is maximized upon creation. /// /// The default is `false`. /// /// See [`Window::set_maximized`] for details. #[inline] pub fn with_maximized(mut self, maximized: bool) -> Self { self.window.maximized = maximized; self } /// Sets whether the window will be initially visible or hidden. /// /// The default is to show the window. /// /// See [`Window::set_visible`] for details. #[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. /// /// If this is `true`, writing colors with alpha values different than /// `1.0` will produce a transparent window. On some platforms this /// is more of a hint for the system and you'd still have the alpha /// buffer. To control it see [`Window::set_transparent`]. /// /// The default is `false`. #[inline] pub fn with_transparent(mut self, transparent: bool) -> Self { self.window.transparent = transparent; self } /// Sets whether the background of the window should be blurred by the system. /// /// The default is `false`. /// /// See [`Window::set_blur`] for details. #[inline] pub fn with_blur(mut self, blur: bool) -> Self { self.window.blur = blur; self } /// Get whether the window will support transparency. #[inline] pub fn transparent(&self) -> bool { self.window.transparent } /// Sets whether the window should have a border, a title bar, etc. /// /// The default is `true`. /// /// See [`Window::set_decorations`] for details. #[inline] pub fn with_decorations(mut self, decorations: bool) -> Self { self.window.decorations = decorations; self } /// Sets the window level. /// /// This is just a hint to the OS, and the system could ignore it. /// /// The default is [`WindowLevel::Normal`]. /// /// See [`WindowLevel`] for details. #[inline] pub fn with_window_level(mut self, level: WindowLevel) -> Self { self.window.window_level = level; self } /// Sets the window icon. /// /// The default is `None`. /// /// See [`Window::set_window_icon`] for details. #[inline] pub fn with_window_icon(mut self, window_icon: Option) -> Self { self.window.window_icon = window_icon; self } /// Sets a specific theme for the window. /// /// If `None` is provided, the window will use the system theme. /// /// The default is `None`. /// /// ## Platform-specific /// /// - **macOS:** This is an app-wide setting. /// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the /// system preference. When explicit theme is used, this will avoid dbus all together. /// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`. /// - **iOS / Android / Web / x11 / Orbital:** Ignored. #[inline] pub fn with_theme(mut self, theme: Option) -> Self { self.window.preferred_theme = theme; self } /// Build window with resize increments hint. /// /// The default is `None`. /// /// See [`Window::set_resize_increments`] for details. #[inline] pub fn with_resize_increments>(mut self, resize_increments: S) -> Self { self.window.resize_increments = Some(resize_increments.into()); self } /// Prevents the window contents from being captured by other apps. /// /// The default is `false`. /// /// ## Platform-specific /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely /// prevent all apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / Web / x11 / Orbital:** Ignored. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone #[inline] pub fn with_content_protected(mut self, protected: bool) -> Self { self.window.content_protected = protected; self } /// Whether the window will be initially focused or not. /// /// The window should be assumed as not focused by default /// following by the [`WindowEvent::Focused`]. /// /// ## Platform-specific: /// /// **Android / iOS / X11 / Wayland / Orbital:** Unsupported. /// /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused. #[inline] pub fn with_active(mut self, active: bool) -> Self { self.window.active = active; self } /// Build window with parent window. /// /// The default is `None`. /// /// ## Safety /// /// `parent_window` must be a valid window handle. /// /// ## Platform-specific /// /// - **Windows** : A child window has the WS_CHILD style and is confined /// to the client area of its parent window. For more information, see /// /// - **X11**: A child window is confined to the client area of its parent window. /// - **Android / iOS / Wayland / Web:** Unsupported. #[cfg(feature = "rwh_06")] #[inline] pub unsafe fn with_parent_window( mut self, parent_window: Option, ) -> Self { self.window.parent_window = SendSyncWrapper(parent_window); self } /// Builds the window. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. /// /// ## Platform-specific /// /// - **Web:** The window is created but not inserted into the web page automatically. Please /// see the web platform module for more information. #[inline] pub fn build( self, window_target: &EventLoopWindowTarget, ) -> Result { let window = platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)?; window.maybe_queue_on_main(|w| w.request_redraw()); Ok(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. /// /// ## Platform-specific /// /// - **Web:** The window is created but not inserted into the web page automatically. Please /// see the web platform module for more information. /// /// [`WindowBuilder::new().build(event_loop)`]: 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 { self.window.maybe_wait_on_main(|w| WindowId(w.id())) } /// Returns the scale factor that can be used to map logical pixels to physical pixels, and /// vice versa. /// /// 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. /// /// This value may differ from [`MonitorHandle::scale_factor`]. /// /// See the [`dpi`](crate::dpi) module for more information. /// /// ## Platform-specific /// /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **Wayland:** Uses the wp-fractional-scale protocol if available. Falls back to integer-scale factors otherwise. /// - **Android:** Always returns 1.0. /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s /// [`contentScaleFactor`]. /// /// [`WindowEvent::ScaleFactorChanged`]: crate::event::WindowEvent::ScaleFactorChanged /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] pub fn scale_factor(&self) -> f64 { self.window.maybe_wait_on_main(|w| w.scale_factor()) } /// Queues a [`WindowEvent::RedrawRequested`] event to be emitted that aligns with the windowing /// system drawing 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). To improve the event delivery /// consider using [`Window::pre_present_notify`] as described in docs. /// /// Applications should always aim to redraw whenever they receive a `RedrawRequested` event. /// /// There are no strong guarantees about when exactly a `RedrawRequest` event will be emitted /// with respect to other events, since the requirements can vary significantly between /// windowing systems. /// /// However as the event aligns with the windowing system drawing loop, it may not arrive in /// same or even next event loop iteration. /// /// ## Platform-specific /// /// - **Windows** This API uses `RedrawWindow` to request a `WM_PAINT` message and `RedrawRequested` /// is emitted in sync with any `WM_PAINT` messages. /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** The events are aligned with the frame callbacks when [`Window::pre_present_notify`] /// is used. /// - **Web:** [`WindowEvent::RedrawRequested`] will be aligned with the `requestAnimationFrame`. /// /// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested #[inline] pub fn request_redraw(&self) { self.window.maybe_queue_on_main(|w| w.request_redraw()) } /// Notify the windowing system before presenting to the window. /// /// You should call this event after your drawing operations, but before you submit /// the buffer to the display or commit your drawings. Doing so will help winit to properly /// schedule and make assumptions about its internal state. For example, it could properly /// throttle [`WindowEvent::RedrawRequested`]. /// /// ## Example /// /// This example illustrates how it looks with OpenGL, but it applies to other graphics /// APIs and software rendering. /// /// ```no_run /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// # fn swap_buffers() {} /// // Do the actual drawing with OpenGL. /// /// // Notify winit that we're about to submit buffer to the windowing system. /// window.pre_present_notify(); /// /// // Sumbit buffer to the windowing system. /// swap_buffers(); /// ``` /// /// ## Platform-specific /// /// **Wayland:** - schedules a frame callback to throttle [`WindowEvent::RedrawRequested`]. /// /// [`WindowEvent::RedrawRequested`]: crate::event::WindowEvent::RedrawRequested #[inline] pub fn pre_present_notify(&self) { self.window.maybe_queue_on_main(|w| w.pre_present_notify()); } /// 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. /// /// ## Platform-specific /// - **Web, macOS:** Does nothing // --------------------------- // Developers' Note: If this cannot be implemented on every desktop platform // at least, then this function should be provided through a platform specific // extension trait pub fn reset_dead_keys(&self) { self.window.maybe_queue_on_main(|w| w.reset_dead_keys()) } } /// 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 [`Window::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. /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the /// same value as [`Window::outer_position`]._ /// - **Android / Wayland:** 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.maybe_wait_on_main(|w| w.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. /// - **Web:** Returns the top-left coordinates relative to the viewport. /// - **Android / Wayland:** Always returns [`NotSupportedError`]. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.maybe_wait_on_main(|w| w.outer_position()) } /// Modifies the position of the window. /// /// See [`Window::outer_position`] for more information about the coordinates. /// This automatically un-maximizes the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the position in logical dimensions like this: /// window.set_outer_position(LogicalPosition::new(400.0, 200.0)); /// /// // Or specify the position in physical dimensions like this: /// window.set_outer_position(PhysicalPosition::new(400, 200)); /// ``` /// /// ## 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. /// - **Web:** Sets the top-left coordinates relative to the viewport. Doesn't account for CSS /// [`transform`]. /// - **Android / Wayland:** Unsupported. /// /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn set_outer_position>(&self, position: P) { let position = position.into(); self.window .maybe_queue_on_main(move |w| w.set_outer_position(position)) } /// 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. /// - **Web:** Returns the size of the canvas element. Doesn't account for CSS [`transform`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] pub fn inner_size(&self) -> PhysicalSize { self.window.maybe_wait_on_main(|w| w.inner_size()) } /// Request the new size for the window. /// /// On platforms where the size is entirely controlled by the user the /// applied size will be returned immediately, resize event in such case /// may not be generated. /// /// On platforms where resizing is disallowed by the windowing system, the current /// inner size is returned immidiatelly, and the user one is ignored. /// /// When `None` is returned, it means that the request went to the display system, /// and the actual size will be delivered later with the [`WindowEvent::Resized`]. /// /// See [`Window::inner_size`] for more information about the values. /// /// The request could automatically un-maximize the window if it's maximized. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the size in logical dimensions like this: /// let _ = window.request_inner_size(LogicalSize::new(400.0, 200.0)); /// /// // Or specify the size in physical dimensions like this: /// let _ = window.request_inner_size(PhysicalSize::new(400, 200)); /// ``` /// /// ## Platform-specific /// /// - **Web:** Sets the size of the canvas element. Doesn't account for CSS [`transform`]. /// /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized /// [`transform`]: https://developer.mozilla.org/en-US/docs/Web/CSS/transform #[inline] #[must_use] pub fn request_inner_size>(&self, size: S) -> Option> { let size = size.into(); self.window .maybe_wait_on_main(|w| w.request_inner_size(size)) } /// 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 [`Window::inner_size`] instead. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Returns the [`PhysicalSize`] of the window in /// screen space coordinates. /// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as /// [`Window::inner_size`]._ #[inline] pub fn outer_size(&self) -> PhysicalSize { self.window.maybe_wait_on_main(|w| w.outer_size()) } /// Sets a minimum dimension size for the window. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the size in logical dimensions like this: /// window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0))); /// /// // Or specify the size in physical dimensions like this: /// window.set_min_inner_size(Some(PhysicalSize::new(400, 200))); /// ``` /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { let min_size = min_size.map(|s| s.into()); self.window .maybe_queue_on_main(move |w| w.set_min_inner_size(min_size)) } /// Sets a maximum dimension size for the window. /// /// ```no_run /// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the size in logical dimensions like this: /// window.set_max_inner_size(Some(LogicalSize::new(400.0, 200.0))); /// /// // Or specify the size in physical dimensions like this: /// window.set_max_inner_size(Some(PhysicalSize::new(400, 200))); /// ``` /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { let max_size = max_size.map(|s| s.into()); self.window .maybe_queue_on_main(move |w| w.set_max_inner_size(max_size)) } /// Returns window resize increments if any were set. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { self.window.maybe_wait_on_main(|w| w.resize_increments()) } /// Sets window resize increments. /// /// This is a niche constraint hint usually employed by terminal emulators /// and other apps that need "blocky" resizes. /// /// ## Platform-specific /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers. /// - **Wayland / Windows:** Not implemented. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) { let increments = increments.map(Into::into); self.window .maybe_queue_on_main(move |w| w.set_resize_increments(increments)) } } /// 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.maybe_wait_on_main(|w| w.set_title(title)) } /// Change the window transparency state. /// /// This is just a hint that may not change anything about /// the window transparency, however doing a missmatch between /// the content of your window and this hint may result in /// visual artifacts. /// /// The default value follows the [`WindowBuilder::with_transparent`]. /// /// ## Platform-specific /// /// - **Web / iOS / Android:** Unsupported. /// - **X11:** Can only be set while building the window, with [`WindowBuilder::with_transparent`]. #[inline] pub fn set_transparent(&self, transparent: bool) { self.window .maybe_queue_on_main(move |w| w.set_transparent(transparent)) } /// Change the window blur state. /// /// If `true`, this will make the transparent window background blurry. /// /// ## Platform-specific /// /// - **Android / iOS / X11 / Web / Windows:** Unsupported. /// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol. #[inline] pub fn set_blur(&self, blur: bool) { self.window.maybe_queue_on_main(move |w| w.set_blur(blur)) } /// Modifies the window's visibility. /// /// If `false`, this will hide the window. If `true`, this will show the window. /// /// ## Platform-specific /// /// - **Android / Wayland / Web:** Unsupported. /// - **iOS:** Can only be called on the main thread. #[inline] pub fn set_visible(&self, visible: bool) { self.window .maybe_queue_on_main(move |w| w.set_visible(visible)) } /// Gets the window's current visibility state. /// /// `None` means it couldn't be determined, so it is not recommended to use this to drive your rendering backend. /// /// ## Platform-specific /// /// - **X11:** Not implemented. /// - **Wayland / iOS / Android / Web:** Unsupported. #[inline] pub fn is_visible(&self) -> Option { self.window.maybe_wait_on_main(|w| w.is_visible()) } /// Sets whether the window is resizable or not. /// /// Note that making the window unresizable doesn't exempt you from handling [`WindowEvent::Resized`], as that /// event can still be triggered by DPI scaling, entering fullscreen mode, etc. Also, the /// window could still be resized by calling [`Window::request_inner_size`]. /// /// ## Platform-specific /// /// This only has an effect on desktop platforms. /// /// - **X11:** Due to a bug in XFCE, this has no effect on Xfwm. /// - **iOS / Android / Web:** Unsupported. /// /// [`WindowEvent::Resized`]: crate::event::WindowEvent::Resized #[inline] pub fn set_resizable(&self, resizable: bool) { self.window .maybe_queue_on_main(move |w| w.set_resizable(resizable)) } /// Gets the window's current resizable state. /// /// ## Platform-specific /// /// - **X11:** Not implemented. /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_resizable(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_resizable()) } /// Sets the enabled window buttons. /// /// ## Platform-specific /// /// - **Wayland / X11 / Orbital:** Not implemented. /// - **Web / iOS / Android:** Unsupported. pub fn set_enabled_buttons(&self, buttons: WindowButtons) { self.window .maybe_queue_on_main(move |w| w.set_enabled_buttons(buttons)) } /// Gets the enabled window buttons. /// /// ## Platform-specific /// /// - **Wayland / X11 / Orbital:** Not implemented. Always returns [`WindowButtons::all`]. /// - **Web / iOS / Android:** Unsupported. Always returns [`WindowButtons::all`]. pub fn enabled_buttons(&self) -> WindowButtons { self.window.maybe_wait_on_main(|w| w.enabled_buttons()) } /// Sets the window to minimized or back /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Unsupported. /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { self.window .maybe_queue_on_main(move |w| w.set_minimized(minimized)) } /// Gets the window's current minimized state. /// /// `None` will be returned, if the minimized state couldn't be determined. /// /// ## Note /// /// - You shouldn't stop rendering for minimized windows, however you could lower the fps. /// /// ## Platform-specific /// /// - **Wayland**: always `None`. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn is_minimized(&self) -> Option { self.window.maybe_wait_on_main(|w| w.is_minimized()) } /// Sets the window to maximized or back. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window .maybe_queue_on_main(move |w| w.set_maximized(maximized)) } /// Gets the window's current maximized state. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn is_maximized(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_maximized()) } /// 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 disabled in exclusive fullscreen mode. /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Android / Orbital:** Unsupported. /// - **Web:** Does nothing without a [transient activation], but queues the request /// for the next activation. /// /// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window .maybe_queue_on_main(move |w| w.set_fullscreen(fullscreen.map(|f| f.into()))) } /// Gets the window's current fullscreen state. /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. /// - **Android / Orbital:** Will always return `None`. /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. /// - **Web:** Can only return `None` or `Borderless(None)`. #[inline] pub fn fullscreen(&self) -> Option { self.window .maybe_wait_on_main(|w| w.fullscreen().map(|f| f.into())) } /// Turn window decorations on or off. /// /// Enable/disable window decorations provided by the server or Winit. /// By default this is enabled. Note that fullscreen windows and windows on /// mobile and web platforms naturally do not have decorations. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** No effect. #[inline] pub fn set_decorations(&self, decorations: bool) { self.window .maybe_queue_on_main(move |w| w.set_decorations(decorations)) } /// Gets the window's current decorations state. /// /// Returns `true` when windows are decorated (server-side or by Winit). /// Also returns `true` when no decorations are required (mobile, web). /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Always returns `true`. #[inline] pub fn is_decorated(&self) -> bool { self.window.maybe_wait_on_main(|w| w.is_decorated()) } /// Change the window level. /// /// This is just a hint to the OS, and the system could ignore it. /// /// See [`WindowLevel`] for details. pub fn set_window_level(&self, level: WindowLevel) { self.window .maybe_queue_on_main(move |w| w.set_window_level(level)) } /// Sets the window icon. /// /// On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported. /// /// - **Windows:** 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. /// /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. #[inline] pub fn set_window_icon(&self, window_icon: Option) { self.window .maybe_queue_on_main(move |w| w.set_window_icon(window_icon)) } /// Set the IME cursor editing area, where the `position` is the top left corner of that area /// and `size` is the size of this area starting from the position. An example of such area /// could be a input field in the UI or line in the editor. /// /// The windowing system could place a candidate box close to that area, but try to not obscure /// the specified area, so the user input to it stays visible. /// /// The candidate box is the window / popup / overlay that allows you to select the desired /// characters. The look of this box may differ between input devices, even on the same /// platform. /// /// (Apple's official term is "candidate window", see their [chinese] and [japanese] guides). /// /// ## Example /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the position in logical dimensions like this: /// window.set_ime_cursor_area(LogicalPosition::new(400.0, 200.0), LogicalSize::new(100, 100)); /// /// // Or specify the position in physical dimensions like this: /// window.set_ime_cursor_area(PhysicalPosition::new(400, 200), PhysicalSize::new(100, 100)); /// ``` /// /// ## Platform-specific /// /// - **X11:** - area is not supported, only position. /// - **iOS / Android / Web / Orbital:** Unsupported. /// /// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0 /// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0 #[inline] pub fn set_ime_cursor_area, S: Into>(&self, position: P, size: S) { let position = position.into(); let size = size.into(); self.window .maybe_queue_on_main(move |w| w.set_ime_cursor_area(position, size)) } /// Sets whether the window should get IME events /// /// When IME is allowed, the window will receive [`Ime`] events, and during the /// preedit phase the window will NOT get [`KeyboardInput`] events. The window /// should allow IME while it is expecting text input. /// /// When IME is not allowed, the window won't receive [`Ime`] events, and will /// receive [`KeyboardInput`] events for every keypress instead. Not allowing /// IME is useful for games for example. /// /// IME is **not** allowed by default. /// /// ## Platform-specific /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined. /// - **iOS / Android / Web / Orbital:** Unsupported. /// - **X11**: Enabling IME will disable dead keys reporting during compose. /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput #[inline] pub fn set_ime_allowed(&self, allowed: bool) { self.window .maybe_queue_on_main(move |w| w.set_ime_allowed(allowed)) } /// Sets the IME purpose for the window using [`ImePurpose`]. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[inline] pub fn set_ime_purpose(&self, purpose: ImePurpose) { self.window .maybe_queue_on_main(move |w| w.set_ime_purpose(purpose)) } /// Brings the window to the front and sets input focus. Has no effect if the window is /// already in focus, minimized, or not visible. /// /// This method steals input focus from other applications. Do not use this method unless /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive /// user experience. /// /// ## Platform-specific /// /// - **iOS / Android / Wayland / Orbital:** Unsupported. #[inline] pub fn focus_window(&self) { self.window.maybe_queue_on_main(|w| w.focus_window()) } /// Gets whether the window has keyboard focus. /// /// This queries the same state information as [`WindowEvent::Focused`]. /// /// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused #[inline] pub fn has_focus(&self) -> bool { self.window.maybe_wait_on_main(|w| w.has_focus()) } /// 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 / Web / Orbital:** Unsupported. /// - **macOS:** `None` has no effect. /// - **X11:** Requests for user attention must be manually cleared. /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. #[inline] pub fn request_user_attention(&self, request_type: Option) { self.window .maybe_queue_on_main(move |w| w.request_user_attention(request_type)) } /// Sets the current window theme. Use `None` to fallback to system default. /// /// ## Platform-specific /// /// - **macOS:** This is an app-wide setting. /// - **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus /// to get the system preference. /// - **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it will default to [`Theme::Dark`]. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_theme(&self, theme: Option) { self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } /// Returns the current window theme. /// /// ## Platform-specific /// /// - **macOS:** This is an app-wide setting. /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. #[inline] pub fn theme(&self) -> Option { self.window.maybe_wait_on_main(|w| w.theme()) } /// Prevents the window contents from being captured by other apps. /// /// ## Platform-specific /// /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely /// prevent all apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone pub fn set_content_protected(&self, protected: bool) { self.window .maybe_queue_on_main(move |w| w.set_content_protected(protected)) } /// Gets the current title of the window. /// /// ## Platform-specific /// /// - **iOS / Android / x11 / Wayland / Web:** Unsupported. Always returns an empty string. #[inline] pub fn title(&self) -> String { self.window.maybe_wait_on_main(|w| w.title()) } } /// Cursor functions. impl Window { /// Modifies the cursor icon of the window. /// /// ## Platform-specific /// /// - **iOS / Android / Orbital:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window .maybe_queue_on_main(move |w| w.set_cursor_icon(cursor)) } /// Changes the position of the cursor in window coordinates. /// /// ```no_run /// # use winit::dpi::{LogicalPosition, PhysicalPosition}; /// # use winit::event_loop::EventLoop; /// # use winit::window::Window; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// // Specify the position in logical dimensions like this: /// window.set_cursor_position(LogicalPosition::new(400.0, 200.0)); /// /// // Or specify the position in physical dimensions like this: /// window.set_cursor_position(PhysicalPosition::new(400, 200)); /// ``` /// /// ## Platform-specific /// /// - **Wayland**: Cursor must be in [`CursorGrabMode::Locked`]. /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { let position = position.into(); self.window .maybe_wait_on_main(|w| w.set_cursor_position(position)) } /// Set grabbing [mode](CursorGrabMode) on the cursor preventing it from leaving the window. /// /// # Example /// /// First try confining the cursor, and if that fails, try locking it instead. /// /// ```no_run /// # use winit::event_loop::EventLoop; /// # use winit::window::{CursorGrabMode, Window}; /// # let mut event_loop = EventLoop::new().unwrap(); /// # let window = Window::new(&event_loop).unwrap(); /// window.set_cursor_grab(CursorGrabMode::Confined) /// .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)) /// .unwrap(); /// ``` #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { self.window.maybe_wait_on_main(|w| w.set_cursor_grab(mode)) } /// 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. /// - **X11:** The cursor is only hidden within the confines of the window. /// - **Wayland:** 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 .maybe_queue_on_main(move |w| w.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 /// /// - **X11:** Un-grabs the cursor. /// - **Wayland:** Requires the cursor to be inside the window to be dragged. /// - **macOS:** May prevent the button release event to be triggered. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { self.window.maybe_wait_on_main(|w| w.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:** Always returns an [`ExternalError::NotSupported`] /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { self.window .maybe_wait_on_main(|w| w.drag_resize_window(direction)) } /// Show [window menu] at a specified position . /// /// This is the context menu that is normally shown when interacting with /// the title bar. This is useful when implementing custom decorations. /// /// ## Platform-specific /// **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported. /// /// [window menu]: https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu pub fn show_window_menu(&self, position: impl Into) { let position = position.into(); self.window .maybe_queue_on_main(move |w| w.show_window_menu(position)) } /// Modifies whether the window catches cursor events. /// /// If `true`, the window will catch the cursor events. If `false`, events are passed through /// the window such that any other window behind it receives them. By default hittest is enabled. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Orbital:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.window .maybe_wait_on_main(|w| w.set_cursor_hittest(hittest)) } } /// Monitor info functions. impl Window { /// Returns the monitor on which the window currently resides. /// /// Returns `None` if current monitor can't be detected. #[inline] pub fn current_monitor(&self) -> Option { self.window .maybe_wait_on_main(|w| w.current_monitor().map(|inner| MonitorHandle { inner })) } /// Returns the list of all the monitors available on the system. /// /// This is the same as [`EventLoopWindowTarget::available_monitors`], and is provided for convenience. /// /// [`EventLoopWindowTarget::available_monitors`]: crate::event_loop::EventLoopWindowTarget::available_monitors #[inline] pub fn available_monitors(&self) -> impl Iterator { self.window.maybe_wait_on_main(|w| { w.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 /// /// **Wayland / Web:** Always returns `None`. /// /// [`EventLoopWindowTarget::primary_monitor`]: crate::event_loop::EventLoopWindowTarget::primary_monitor #[inline] pub fn primary_monitor(&self) -> Option { self.window .maybe_wait_on_main(|w| w.primary_monitor().map(|inner| MonitorHandle { inner })) } } #[cfg(feature = "rwh_06")] impl rwh_06::HasWindowHandle for Window { fn window_handle(&self) -> Result, rwh_06::HandleError> { let raw = self .window .maybe_wait_on_main(|w| w.raw_window_handle_rwh_06().map(SendSyncWrapper))? .0; // 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 .maybe_wait_on_main(|w| w.raw_display_handle_rwh_06().map(SendSyncWrapper))? .0; // SAFETY: The window handle will never be deallocated while the window is alive. Ok(unsafe { rwh_06::DisplayHandle::borrow_raw(raw) }) } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawWindowHandle for Window { fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { self.window .maybe_wait_on_main(|w| SendSyncWrapper(w.raw_window_handle_rwh_05())) .0 } } #[cfg(feature = "rwh_05")] unsafe impl rwh_05::HasRawDisplayHandle for Window { /// Returns a [`rwh_05::RawDisplayHandle`] used by the [`EventLoop`] that /// created a window. /// /// [`EventLoop`]: crate::event_loop::EventLoop fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle { self.window .maybe_wait_on_main(|w| SendSyncWrapper(w.raw_display_handle_rwh_05())) .0 } } #[cfg(feature = "rwh_04")] unsafe impl rwh_04::HasRawWindowHandle for Window { fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { self.window .maybe_wait_on_main(|w| SendSyncWrapper(w.raw_window_handle_rwh_04())) .0 } } /// The behavior of cursor grabbing. /// /// Use this enum with [`Window::set_cursor_grab`] to grab the cursor. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CursorGrabMode { /// No grabbing of the cursor is performed. None, /// The cursor is confined to the window area. /// /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you /// want to do so. /// /// ## Platform-specific /// /// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. Confined, /// The cursor is locked inside the window area to the certain position. /// /// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you /// want to do so. /// /// ## Platform-specific /// /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. Locked, } /// 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, } impl From for CursorIcon { fn from(direction: ResizeDirection) -> Self { use ResizeDirection::*; match direction { East => CursorIcon::EResize, North => CursorIcon::NResize, NorthEast => CursorIcon::NeResize, NorthWest => CursorIcon::NwResize, South => CursorIcon::SResize, SouthEast => CursorIcon::SeResize, SouthWest => CursorIcon::SwResize, West => CursorIcon::WResize, } } } /// Fullscreen modes. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Fullscreen { Exclusive(VideoMode), /// Providing `None` to `Borderless` will fullscreen on the current monitor. Borderless(Option), } /// The theme variant to use. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Theme { /// Use the light variant. Light, /// Use the dark variant. Dark, } /// ## Platform-specific /// /// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. /// /// [`Critical`]: Self::Critical /// [`Informational`]: Self::Informational #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] 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, } bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WindowButtons: u32 { const CLOSE = 1 << 0; const MINIMIZE = 1 << 1; const MAXIMIZE = 1 << 2; } } /// A window level groups windows with respect to their z-position. /// /// The relative ordering between windows in different window levels is fixed. /// The z-order of a window within the same window level may change dynamically on user interaction. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland:** Unsupported. #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] pub enum WindowLevel { /// The window will always be below normal windows. /// /// This is useful for a widget-based app. AlwaysOnBottom, /// The default. #[default] Normal, /// The window will always be on top of normal windows. AlwaysOnTop, } /// Generic IME purposes for use in [`Window::set_ime_purpose`]. /// /// The purpose may improve UX by optimizing the IME for the specific use case, /// if winit can express the purpose to the platform and the platform reacts accordingly. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub enum ImePurpose { /// No special hints for the IME (default). Normal, /// The IME is used for password input. Password, /// The IME is used to input into a terminal. /// /// For example, that could alter OSK on Wayland to show extra buttons. Terminal, } impl Default for ImePurpose { fn default() -> Self { Self::Normal } } /// An opaque token used to activate the [`Window`]. /// /// [`Window`]: crate::window::Window #[derive(Debug, PartialEq, Eq, Clone)] pub struct ActivationToken { pub(crate) _token: String, } impl ActivationToken { pub(crate) fn _new(_token: String) -> Self { Self { _token } } } winit-0.29.15/tests/send_objects.rs000064400000000000000000000013301046102023000153030ustar 00000000000000#[allow(dead_code)] fn needs_send() {} #[test] fn event_loop_proxy_send() { #[allow(dead_code)] fn is_send() { // ensures that `winit::EventLoopProxy` implements `Send` needs_send::>(); } } #[test] fn window_send() { // ensures that `winit::Window` implements `Send` needs_send::(); } #[test] fn window_builder_send() { needs_send::(); } #[test] fn ids_send() { // ensures that the various `..Id` types implement `Send` needs_send::(); needs_send::(); needs_send::(); } winit-0.29.15/tests/serde_objects.rs000064400000000000000000000020621046102023000154570ustar 00000000000000#![cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey}, window::CursorIcon, }; #[allow(dead_code)] fn needs_serde>() {} #[test] fn window_serde() { needs_serde::(); } #[test] fn events_serde() { needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); } #[test] fn dpi_serde() { needs_serde::>(); needs_serde::>(); needs_serde::>(); needs_serde::>(); needs_serde::>(); } winit-0.29.15/tests/sync_object.rs000064400000000000000000000004071046102023000151470ustar 00000000000000#[allow(dead_code)] fn needs_sync() {} #[test] fn window_sync() { // ensures that `winit::Window` implements `Sync` needs_sync::(); } #[test] fn window_builder_sync() { needs_sync::(); }