smithay-client-toolkit-0.18.0/.cirrus.yml000064400000000000000000000011131046102023000164330ustar 00000000000000task: only_if: $CIRRUS_BRANCH == 'master' || $CIRRUS_PR != '' matrix: - name: FreeBSD 12.1 freebsd_instance: image_family: freebsd-12-1-snap - name: FreeBSD 13.0 freebsd_instance: image_family: freebsd-13-0-snap # Install Rust setup_script: - fetch https://sh.rustup.rs -o rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain stable - pkg install -y cmake fontconfig pkgconf libxkbcommon test_script: - . $HOME/.cargo/env - mkdir -p $HOME/sockets - export XDG_RUNTIME_DIR="$HOME/sockets" - cargo test smithay-client-toolkit-0.18.0/.github/workflows/ci.yml000064400000000000000000000031461046102023000210460ustar 00000000000000name: Continuous Integration on: push: branches: - master pull_request: jobs: ci: strategy: fail-fast: false matrix: rust: ['1.65.0', 'stable', 'beta'] runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Cargo cache uses: actions/cache@v1 with: path: ~/.cargo key: cargo-${{ matrix.rust }} - name: Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Install system dependencies run: sudo apt-get install libxkbcommon-dev libwayland-dev - name: Test lib no features uses: actions-rs/cargo@v1 with: command: test args: --no-default-features --lib - name: Test doc no features uses: actions-rs/cargo@v1 with: command: test args: --no-default-features --doc - name: Test full features uses: actions-rs/cargo@v1 with: command: test args: --all-features lint: runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable override: true components: rustfmt, clippy - name: Cargo fmt run: cargo fmt --all -- --check - name: Install system dependencies run: sudo apt-get install libxkbcommon-dev - name: Clippy run: cargo clippy -- -D warnings smithay-client-toolkit-0.18.0/.github/workflows/docs.yml000064400000000000000000000017311046102023000214010ustar 00000000000000name: Deploy Docs to GitHub Pages on: push: branches: - master jobs: doc: name: Documentation on Github Pages runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v2 - name: Cargo cache uses: actions/cache@v1 with: path: ~/.cargo key: cargo-stable - name: Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: Install system dependencies run: sudo apt-get install libxkbcommon-dev libwayland-dev - name: Build Documentation uses: actions-rs/cargo@v1 with: command: doc args: --no-deps - name: Setup index run: cp ./doc_index.html ./target/doc/index.html - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc smithay-client-toolkit-0.18.0/.gitignore000064400000000000000000000000431046102023000163140ustar 00000000000000/rls /target **/*.rs.bk Cargo.lock smithay-client-toolkit-0.18.0/.idea/.gitignore000064400000000000000000000002601046102023000172750ustar 00000000000000# Default ignored files /shelf/ /workspace.xml # Editor-based HTTP Client requests /httpRequests/ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml smithay-client-toolkit-0.18.0/.idea/client-toolkit.iml000064400000000000000000000007701046102023000207570ustar 00000000000000 smithay-client-toolkit-0.18.0/.idea/modules.xml000064400000000000000000000004301046102023000174760ustar 00000000000000 smithay-client-toolkit-0.18.0/.idea/vcs.xml000064400000000000000000000002641046102023000166260ustar 00000000000000 smithay-client-toolkit-0.18.0/.idea/workspace.xml000064400000000000000000000072451046102023000200370ustar 00000000000000 { "keyToString": { "RunOnceActivity.OpenProjectViewOnStart": "true", "RunOnceActivity.ShowReadmeOnStart": "true", "RunOnceActivity.cidr.known.project.marker": "true", "WebServerToolWindowFactoryState": "false", "cf.first.check.clang-format": "false", "cidr.known.project.marker": "true", "last_opened_file_path": "/home/me/dev/client-toolkit", "node.js.detected.package.eslint": "true", "node.js.detected.package.tslint": "true", "node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)", "nodejs_package_manager_path": "npm", "org.rust.cargo.project.model.PROJECT_DISCOVERY": "true" } } 1657647871710 smithay-client-toolkit-0.18.0/CHANGELOG.md000064400000000000000000000337711046102023000161530ustar 00000000000000# Change Log ## Unreleased #### Breaking Changes #### Fixed #### Additions ## 0.18.0 - 2023-09-23 #### Breaking Changes - `ThemedPointer::set_cursor` now takes only `Connection` and `&str`. - `SeatState:get_pointer_with_them*` now takes `Shm` and `WlSurface` for the themed cursor. - `ThemedPointer` now automatically releases the associated `WlPointer`. - `CursorIcon` from `cursor-icon` crate is now used for `set_cursor` and `Frame`. - `wayland-csd-frame` is now used for CSD types like `WindowState`, `DecorationsFrame`, etc. - Added `CompositorHandle::transform_changed` to listen for transform changes. - `xkeysym::Keysym` is used as a keyboard key representation instead of `u32` - `wayland-rs` dependencies are updated to 0.31 - `calloop` dependency updated to 0.12.1 - Take `OwnedFd` instead of `RawFd` as argument to `receive_to_fd` functions. #### Fixed - Crash when `wl_output` is below version 3. #### Additions - Make `DataDeviceManagerState`'s `create_{copy_paste,drag_and_drop}_source` accept `IntoIterator`. - Add support for `zwp_primary_selection_v1`. - `CursorShapeManager` providing handling for `cursor-shape-v1` protocol. - `SeatState::get_pointer_with_theme` will now automatically use `wp_cursor_shape_v1` when available. - Add support for `xdg_shell` version 6. - Add support for `wl_surafce::preferred_buffer_scale` and `wl_surface::preferred_buffer_transform`. ## 0.17.0 - 2023-03-28 #### Breaking Changes - `wayland-rs` dependencies are updated to 0.30 and all APIs have changed significantly as a result #### Additions - `xkbcommon` is a new optional dependecy for keyboard handling gated by the `xkbcommon` feature - pointer-constraints-unstable-v1 protocol support - relative-pointer-unstable-v1 protocol support - wlr-layer-shell-unstable-v1 protocol support - OutputInfo includes logical size and logical position - New SHM pool types ## 0.16.0 - 2022-06-18 #### Breaking changes - `calloop` is updated to version 0.10, and the keyboard handling API is slightly changed as a result. #### Additions - `DataDevice::with_dnd` and `DataOffer::receive_to_fd` allow more flexible interaction with the data device abstraction - the output integration now supports version `4` of `wl_output` ## 0.15.4 - 2022-04-10 #### Bugfixes - `Window`'s `wl_pointer` not being relased on `Drop`. ## 0.15.3 - 2021-12-27 #### Bugfixes - SCTK now correctly interacts with the wayland socket being conccurently polled from other threads. ## 0.15.2 - 2021-10-27 - Most types are now `Debug` ## 0.15.1 - 2021-08-23 #### Bugfixes - when not using `dlopen` feature, `xkbdcommon` library is linked using `pkg-config` ## 0.15.0 - 2021-08-10 #### Breaking Changes - Update `wayland-client` to 0.29 #### Additions - `AutoMemPool` now guarantees a minimum alignment of returned buffers ## 0.14.0 - 2021-05-07 #### Breaking Changes - `ConceptFrame` is removed, as well as the `frames` cargo feature, and replaced by a more minimalistic `FallbackFrame`. Dependency on `andrew` and `fontconfig` is dropped in the process. If fancier decorations are needed, they should be implemented using the `Frame` trait. - Update to calloop 0.7: `calloop::Source` is replaced by `calloop::RegistrationToken` #### Additions - `AutoMemPool` added as an alternative to the existing SHM pools ## 0.13.0 - 2021-03-04 #### Breaking Changes - Mark OutputInfo as `#[non_exhaustive]` to allow future expansion without breaking API. - Batch output information updates instead of potentially making multiple callbacks for one logical change - Add name and description fields to OutputInfo. #### Additions - `Window::start_interactive_move` to enable dragging the window with a user action #### Bugfixes - `ConceptFrame` now correctly loads fonts using fontconfig ## 0.12.2 -- 2020-12-30 #### Changes - Dependency on `byteorder` was replaced with `u32::from_ne_bytes()` #### Bugfixes - Don't crash when the font cannot be loaded to draw decorations ## 0.12.1 -- 2020-12-08 #### Changes - Unmaintained `memmap` dependency is replaced with `memmap2` ## 0.12.0 -- 2020-09-30 #### Breaking Changes - Update `wayland-client` to version 0.28 - `Environment::init` was renamed to `Environment::new_pending` - `init_default_environment!` macro was renamed to `new_default_environment!` #### Additions - `Environment::new` method to fully bootstrap environment ## 0.11.0 -- 2020-08-30 #### Breaking Changes - `window.set_decorate` is now taking mutable reference - Added `show_window_menu` on a `Frame` trait to request a window menu for a window. - `ShowMenu` enum variant to `FrameRequest` - `create_window` now also takes `Option` - `Frame::init` now also takes `Option` to reuse users' `ThemeManager` #### Additions - `WaylandSource::queue` to access the `EventQueue` underlying a `WaylandSource` - A window menu could be shown on right click on decorations for `ConceptFrame` - `ConceptFrame` will no longer change cursor over base surface if `ThemeManager` was provided #### Changes - `Window::set_title` now truncates the provided string to 1024 bytes, to avoid blowing up the Wayland connection - `ConceptFrame` is now hiding decorations for `State::Fullscreen` - Restore original size of fullscreened window on unfullscreen - Explicitly setting `ClientSide` decorations will result in `ServerSide` ones being destroyed - Requesting `ServerSide` decorations in `set_decorate` will now fallback to `ClientSide` if the former are not available - Requesting `None` decorations if `ServerSide` are presented will result in setting `ClientSide` decorations with hidden frame - `ConceptFrame` will use `Disabled` style for maximized button for non-resizeable frame - `ConceptFrame` will create subsurfaces for client side decorations only if a frame is visible - `Window` will restore original size after being tiled #### Bugfixes - Toggling between `ServerSide` and `None` decorations raising protocol error - Precision in a rate of key repeat events - `ThemeManager` not being clone-able even if it was stated in docs - Repeat rate not being disabled when receiving zero for `rate` in `wl_keyboard.repeat_info` ## 0.10.0 -- 2020-07-10 #### Breaking Changes - `create_surface` and `create_surface_with_scale_callback` now return `Attached` - Update `wayland-client` to `0.27` #### Changes - `andrew` is updated to `0.3`. #### Bugfixes - seat: Seats with an empty name are no longer filtered out ## 0.9.1 -- 2020-05-03 #### Additions - keyboard: Update the keysyms list with new symbols - Add primary selection helpers, which are included as part of default `Environment`. #### Changes - surfaces: dpi-aware surface will no longer believe their DPI factor reverts to 1 when they become hidden. #### BugFixes - keyboard: Remove the unnecessary type parameter of `map_keyboard` ## 0.9.0 -- 2020-04-22 #### Breaking Changes - `AutoThemer` is removed as it is no longer necessary with `wayland-cursor` 0.26 - `calloop` is updated to 0.6, and the adapters are modified in consequence #### Additions - Add `clone_seat_data()` method as a shorthand to get `SeatData` #### Bugfixes - Surface lock held across scale factor callback deadlocks scale factor API. ## 0.8.1 -- 2020-04-09 #### Additions - Add `listen_for_outputs()` which calls a provided callback on creation/removal of outputs. - Add an `OutputHandling` trait making `listen_for_outputs()` available on `Environment`. - Introduce the `calloop` cargo feature, enabled by default, controlling the support for the calloop event loop - Introduce the `frames` cargo feature, enabled by default, controlling the existence of provided `Frame` implementations (currently `ConceptFrame`) and the dependency on `andrew` ## 0.8.0 -- 2020-02-27 #### Breaking Changes - `Frame` configuration is now done through a `Frame::Config` associated type and the `Theme` trait is removed. - Merge `Frame::set_active` and `Frame::set_maximized` into `Frame::set_states` #### Additions - HiDPI scaling for decorations #### Bugfixes - HiDPI cursor icon position - Fix graphical glitches in `ConceptFrame` decoration drawing - Black pixel on left-bottom corner on CSD - Remove a deadlock when trying to access the seat data from within the seat callback ## 0.7.0 -- 2020-02-07 #### Breaking changes - Upgrade to `wayland-client` 0.25. This changes the prototype of most callbacks by adding the `DispatchData` mechanism for state sharing - Re-structure the lib API around the new `Environment` type as an entry point (breaks a lot of things). This makes the crate follow a monolithic-modular structure centered on this type. - `keyboard` is now a submodule of `seat` - `keyboard` key repetition is now handled as a calloop event source - `pointer` is now a submodule of `seat` - The initialization of `pointer` theming utilities now require a `ThemeSpec` argument instead of just a theme name, allowing control over the size of the cursors as well - Pointer theming utilities can no longer be shared across threads, as it was racy. - `Window` now tracks new seats automatically (the `new_seat` method is removed) - `Window` can no longer be shared across threads, as it was racy. - Decorations management is now handled with the `Decorations` enum, for full control to clients. #### Additions - The `pointer` theming will now read the `XCURSOR_THEME` and `XCURSOR_SIZE` environment variables to figure the default theme - `pointer` theming utilities now handle HiDPI monitors - SCTK now uses the `log` crate to log its warning and error messages - Data offers `ReadPipe`scan be inserted in a calloop event loop as an event source - The `WaylandSource` wrapper allows a `wayland-client` `EventQueue` to be inserted into a calloop event source. ## 0.6.4 -- 2019-08-27 #### Bugfixes - Keyboard input breaking when `LC_ALL`, `LC_CTYPE` or `LANG` are set to an empty string - UTF8 interpretation no longer stops working if loading the compose table failed ## 0.6.3 -- 2019-06-29 - Keyboard: fix extra key repeat when using also releasing a modifier ## 0.6.2 -- 2019-06-13 - Update `Nix` to 0.14 ## 0.6.1 -- 2019-04-07 - Additional theming capability on `ConceptFrame` via the `Theme` trait: optional methods `get__button_icon_color` allows the stroke color on the buttons to be customized beyond what the secondary color allows. Button color methods now affect the `ConceptFrame`'s fill behind the buttons. - Fix the firing of `Configure` events in window abstraction. ## 0.6.0 -- 2019-02-18 #### Breaking changes - Upgrade to `wayland-client` version 0.23 ## 0.5.0 -- 2019-02-05 #### Breaking changes - Update the crate to `wayland-client` version 0.22 - Window: `set_title()` now requires a manual `refresh()` for the change to take effect #### Bugfixes - Keyboard: fix system repeat rate as repeats per second rather then millisecond delay between repeats - Surface: fix panic in `compute_dpi_factor()` by only computing the dpi factor on surfaces known to the OutputMgr ## 0.4.4 -- 2018-12-27 - Shell: expose shell interface and add `create_shell_surface` to `Environment`. - Fix build failure on big endian targets ## 0.4.3 -- 2018-12-03 - Update dependencies: rand, memmap, nix and image - Surface: `create_surface` and `get_dpi_factor` utilities for creating dpi aware surfaces. ## 0.4.2 -- 2018-11-14 - Fix compilation on BSD systems ## 0.4.1 -- 2018-11-06 - Window: always request server-side decorations if available, otherwise ther compositor never configures us - keyboard: only compute utf8 value on keypress, not key release. Otherwise it confuses `xkb_compose`. ## 0.4.0 -- 2018-10-09 - BasicFrame: Display the title of the window in the window header - Pass `set_selection()` `Option` and `AutoThemer::init()` `Proxy` by reference - Window: add `set_theme()` function which takes an object implementing the trait `Theme` to adjust the look of window decorations - Window: add new `ConceptFrame` which provides an alternative to the `BasicFrame` window decorations - MemPool: add `mmap` method - **[Breaking]** Keyboard: remove `modifiers` field from `keyboard::Event::Enter`, `keyboard::Event::Key` and `keyboard::KeyRepeatEvent` - **[Breaking]** Keyboard: add `keyboard::Event::Modifiers` - **[Breaking]** Upgrade to wayland-rs 0.21 - Keyboard: end key repetition when the keyboard loses focus ## 0.3.0 -- 2018-08-17 - Window: the minimum window width is set to 2 pixels to circumvent a bug in mutter - https://gitlab.gnome.org/GNOME/mutter/issues/259 - **[Breaking]** MemPool: MemPool now requires an implementation to be called when the pool becomes free - **[Breaking]** DoubleMemPool: DoubleMemPool now requires an implementation to be called when one of its pools becomes free - **[Breaking]** DoubleMemPool: `swap()` is removed as `pool()` will now automatically track and return any free pools avaliable or return None - Keyboard: add key repetition with 'map_keyboard_auto_with_repeat' and 'map_keyboard_rmlvo_with_repeat' - Window: add `init_with_decorations` to allow the use of server-side decorations ## 0.2.6 -- 2018-07-14 Big thanks to @trimental for improving the visual look of the window decorations: - BasicFrame: remove side and bottom border decorations - BasicFrame: round window corners ## 0.2.5 -- 2018-07-10 - Keyboard: try to load `libxkbcommon.so.0` as well to improve compatibility ## 0.2.4 -- 2018-06-26 - Window: notify the compositor of our dimensions to avoid placement glitches ## 0.2.3 -- 2018-06-08 - Update `nix` dependency to be fix build on FreeBSD (even if we can't run) ## 0.2.2 -- 2018-06-08 - BasicFrame: don't desync the subsurface from the main one. This avoids graphical glitches where the borders are not drawn exactly the same size as the contents. - Window: add `set_resizable`, (minor **breaking change** of the `Frame` trait by adding a new method) ## 0.2.1 -- 2018-05-03 - Add `DoubleMemPool` for double buffering, and use it to improve the drawing performance of `BasicFrame`. ## 0.2.0 -- 2018-04-29 - *Breaking* OutputMgr: expose wl_output global id ## 0.1.0 -- 2018-04-26 Initial version, including: - basic environment manager - keyboard keymap handling - basic window decoration smithay-client-toolkit-0.18.0/CONTRIBUTING.md000064400000000000000000000044021046102023000165600ustar 00000000000000# Contributing Smithay's Client ToolKit (SCTK) is open to contributions from anyone. ## Coordination Most discussion about features and their implementations takes place on github. If you have questions, suggestions, ideas, you can open an issue to discuss it, or add your message in an already existing issue if it fits its scope. If you want a more realtime discussion there is a a Matrix room dedicated to the Smithay project: [#smithay:matrix.org](https://matrix.to/#/#smithay:matrix.org). If you don't want to use matrix, this room is also bridged to gitter: https://gitter.im/smithay/Lobby. ## Scope & Structure SCTK aims to provide generic building blocks to write wayland clients, abstracting away the boilerplate of the wayland protocol while allowing direct control when wanted. As such, it is composed of several loosely-coupled modules, which can be used independenly of each other. This given, if you want to contribute a new feature to SCTK, please consider these design points: - The feature should be designed it is most general form, allowing it to be used by other projects, probaby different from the exact use-case you have in mind. - This new feature should not heavily depend on the other parts of SCTK if it can avoid it. As much as possible, SCTK users should be able to use your feature alone. ## Pull requests & commits organisation The development branch is the `master` branch, and it should be the target of your pull requests. In general, single-purpose pull requests are prefered. If you have two independent contributions to make, please open two different pull requests. On the other hand, if you have changes that could technically be separated, but really belong together (for example a new feature, that first require some refactoring before being introduced), it is okay to ship them in the same pull request. However, to simplify the review work (and future reference to the commit history), these changes should be separated in different commits. This will allow the reviewers to review each commit independently, reducing the cognitive load. At merge time, pull requests consisting of a single commit or of a few well-scoped commits will be rebased on master. Pull requests which have accumulated several review-addressing commits will be squashed. smithay-client-toolkit-0.18.0/Cargo.lock0000644000001524640000000000100135260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ash" version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ "libloading 0.7.4", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "calloop" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ebbdc267c07e09f7884745db06cc9e1f14dbc87baadc33138b8d0ebf664590" dependencies = [ "bitflags 2.4.0", "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.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cmake" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] [[package]] name = "codespan-reporting" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "com-rs" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" [[package]] name = "concurrent-queue" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] [[package]] name = "const-cstr" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types 0.3.2", "libc", ] [[package]] name = "core-graphics-types" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ "bitflags 1.3.2", "core-foundation", "libc", ] [[package]] name = "core-text" version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ "core-foundation", "core-graphics", "foreign-types 0.3.2", "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-channel" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset 0.9.0", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "cursor-icon" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740bb192a8e2d1350119916954f4409ee7f62f149b536911eeb78ba5a20526bf" [[package]] name = "d3d12" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16e44ab292b1dddfdaf7be62cfd8877df52f2f3fde5858d95bab606be259f20" dependencies = [ "bitflags 2.4.0", "libloading 0.8.0", "winapi", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading 0.8.0", ] [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drm-fourcc" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" [[package]] name = "dwrote" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ "lazy_static", "libc", "winapi", "wio", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "errno" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", "windows-sys", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "euclid" version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" dependencies = [ "num-traits", ] [[package]] name = "exr" version = "1.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" dependencies = [ "bit_field", "flume", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fdeflate" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "float-ord" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" [[package]] name = "flume" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "spin", ] [[package]] name = "font-kit" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" dependencies = [ "bitflags 1.3.2", "byteorder", "core-foundation", "core-graphics", "core-text", "dirs-next", "dwrote", "float-ord", "freetype", "lazy_static", "libc", "log", "pathfinder_geometry", "pathfinder_simd", "walkdir", "winapi", "yeslogic-fontconfig-sys", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared 0.1.1", ] [[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 0.3.1", ] [[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.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "foreign-types-shared" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "freetype" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" dependencies = [ "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 = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gif" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" dependencies = [ "color_quant", "weezl", ] [[package]] name = "gimli" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "glow" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" dependencies = [ "js-sys", "slotmap", "wasm-bindgen", "web-sys", ] [[package]] name = "gpu-alloc" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ "bitflags 2.4.0", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ "bitflags 2.4.0", ] [[package]] name = "gpu-allocator" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" dependencies = [ "backtrace", "log", "thiserror", "winapi", "windows", ] [[package]] name = "gpu-descriptor" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags 1.3.2", "gpu-descriptor-types", "hashbrown", ] [[package]] name = "gpu-descriptor-types" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "half" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" dependencies = [ "crunchy", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hassle-rs" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" dependencies = [ "bitflags 1.3.2", "com-rs", "libc", "libloading 0.7.4", "thiserror", "widestring", "winapi", ] [[package]] name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hexf-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "image" version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", "jpeg-decoder", "num-rational", "num-traits", "png", "qoi", "tiff", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys", ] [[package]] name = "jpeg-decoder" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "khronos-egl" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", "libloading 0.7.4", "pkg-config", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ "cfg-if", "windows-sys", ] [[package]] name = "linux-raw-sys" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "lyon_geom" version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d89ccbdafd83d259403e22061be27bccc3254bba65cdc5303250c4227c8c8e" dependencies = [ "arrayvec 0.5.2", "euclid", "num-traits", ] [[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.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" dependencies = [ "libc", ] [[package]] name = "memmap2" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" 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 = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "metal" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" dependencies = [ "bitflags 2.4.0", "block", "core-graphics-types", "foreign-types 0.5.0", "log", "objc", "paste", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", "simd-adler32", ] [[package]] name = "naga" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ceaaa4eedaece7e4ec08c55c640ba03dbb73fb812a6570a59bcf1930d0f70e" dependencies = [ "bit-set", "bitflags 2.4.0", "codespan-reporting", "hexf-parse", "indexmap", "log", "num-traits", "rustc-hash", "spirv", "termcolor", "thiserror", "unicode-xid", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", "static_assertions", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", "objc_exception", ] [[package]] name = "objc_exception" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" dependencies = [ "cc", ] [[package]] name = "object" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.1", ] [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pathfinder_geometry" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" dependencies = [ "log", "pathfinder_simd", ] [[package]] name = "pathfinder_simd" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" dependencies = [ "rustc_version", ] [[package]] name = "pest" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", ] [[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.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "png" version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "polling" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51348b98db9d4a18ada4fdf7ff5274666e7e6c5a50c42a7d77c5e5c0cb6b036b" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", "rustix", "tracing", "windows-sys", ] [[package]] name = "pollster" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46b2164ebdb1dfeec5e337be164292351e11daf63a05174c6776b2f47460f0c9" [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-xml" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] [[package]] name = "range-alloc" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] name = "raqote" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5b6cb89f8be6a645e5834f3ad44a7960d12343d97b5b7fb07cb0365ae36aa2d" dependencies = [ "euclid", "font-kit", "lyon_geom", "pathfinder_geometry", "png", "sw-composite", "typed-arena", ] [[package]] name = "raw-window-handle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] [[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_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "renderdoc-sys" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[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 = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ "pest", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "slotmap" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" dependencies = [ "version_check", ] [[package]] name = "smallvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smithay-client-toolkit" version = "0.18.0" dependencies = [ "bitflags 2.4.0", "bytemuck", "calloop", "calloop-wayland-source", "cursor-icon", "drm-fourcc", "env_logger", "font-kit", "image", "libc", "log", "memmap2 0.9.0", "pkg-config", "pollster", "raqote", "raw-window-handle", "rustix", "thiserror", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", "wgpu", "xkbcommon", "xkeysym", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spirv" version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ "bitflags 1.3.2", "num-traits", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "sw-composite" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac8fb7895b4afa060ad731a32860db8755da3449a47e796d5ecf758db2671d4" [[package]] name = "syn" version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiff" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "ucd-trie" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[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.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" 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.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-backend" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abfc134185f589b9cc8f3d6a562e5764a8daa219238e75c3e4d36ff5c919164d" dependencies = [ "cc", "downcast-rs", "nix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ "bitflags 2.4.0", "nix", "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.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b" dependencies = [ "nix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ "bitflags 2.4.0", "wayland-backend", "wayland-client", "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.0", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" 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", "pkg-config", ] [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" [[package]] name = "wgpu" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed547920565c56c7a29afb4538ac5ae5048865a5d2f05bff3ad4fbeb921a9a2c" dependencies = [ "arrayvec 0.7.4", "cfg-if", "js-sys", "log", "naga", "parking_lot", "profiling", "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "wgpu-core", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-core" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f8a44dd301a30ceeed3c27d8c0090433d3da04d7b2a4042738095a424d12ae7" dependencies = [ "arrayvec 0.7.4", "bit-vec", "bitflags 2.4.0", "codespan-reporting", "log", "naga", "parking_lot", "profiling", "raw-window-handle", "rustc-hash", "smallvec", "thiserror", "web-sys", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a80bf0e3c77399bb52850cb0830af9bad073d5cfcb9dd8253bef8125c42db17" dependencies = [ "android_system_properties", "arrayvec 0.7.4", "ash", "bit-set", "bitflags 2.4.0", "block", "core-graphics-types", "d3d12", "glow", "gpu-alloc", "gpu-allocator", "gpu-descriptor", "hassle-rs", "js-sys", "khronos-egl", "libc", "libloading 0.8.0", "log", "metal", "naga", "objc", "parking_lot", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", "rustc-hash", "smallvec", "thiserror", "wasm-bindgen", "web-sys", "wgpu-types", "winapi", ] [[package]] name = "wgpu-types" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee64d7398d0c2f9ca48922c902ef69c42d000c759f3db41e355f4a570b052b67" dependencies = [ "bitflags 2.4.0", "js-sys", "web-sys", ] [[package]] name = "widestring" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 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" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" 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.1", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "wio" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi", ] [[package]] name = "xcursor" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" dependencies = [ "nom", ] [[package]] name = "xkbcommon" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" dependencies = [ "libc", "memmap2 0.8.0", "xkeysym", ] [[package]] name = "xkeysym" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" dependencies = [ "bytemuck", ] [[package]] name = "yeslogic-fontconfig-sys" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" dependencies = [ "const-cstr", "dlib", "once_cell", "pkg-config", ] [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] smithay-client-toolkit-0.18.0/Cargo.toml0000644000000056340000000000100135450ustar # 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" name = "smithay-client-toolkit" version = "0.18.0" authors = [ "Elinor Berger ", "i509VCB ", "Ashley Wulber ", ] description = "Toolkit for making client wayland applications." documentation = "https://smithay.github.io/client-toolkit" readme = "README.md" keywords = [ "wayland", "client", ] categories = ["gui"] license = "MIT" repository = "https://github.com/smithay/client-toolkit" [package.metadata.docs.rs] features = [ "calloop", "xkbcommon", ] rustdoc-args = [ "--cfg", "docsrs", ] [[example]] name = "wgpu" required-features = ["wayland-backend/client_system"] [dependencies.bitflags] version = "2.4" [dependencies.bytemuck] version = "1.13.0" optional = true [dependencies.calloop] version = "0.12.1" optional = true [dependencies.calloop-wayland-source] version = "0.2.0" optional = true [dependencies.cursor-icon] version = "1.0.0" [dependencies.libc] version = "0.2.148" [dependencies.log] version = "0.4" [dependencies.memmap2] version = "0.9.0" [dependencies.rustix] version = "0.38.15" features = [ "fs", "pipe", "shm", ] [dependencies.thiserror] version = "1.0.30" [dependencies.wayland-backend] version = "0.3.0" [dependencies.wayland-client] version = "0.31.1" [dependencies.wayland-csd-frame] version = "0.3.0" [dependencies.wayland-cursor] version = "0.31.0" [dependencies.wayland-protocols] version = "0.31.0" features = [ "client", "staging", "unstable", ] [dependencies.wayland-protocols-wlr] version = "0.2.0" features = ["client"] [dependencies.wayland-scanner] version = "0.31.0" [dependencies.xkbcommon] version = "0.7.0" features = ["wayland"] optional = true [dependencies.xkeysym] version = "0.2.0" [dev-dependencies.bytemuck] version = "1.13.0" [dev-dependencies.drm-fourcc] version = "2.2.0" [dev-dependencies.env_logger] version = "0.10" [dev-dependencies.font-kit] version = "0.11.0" [dev-dependencies.image] version = "0.24" [dev-dependencies.pollster] version = "0.3.0" [dev-dependencies.raqote] version = "0.8.2" [dev-dependencies.raw-window-handle] version = "0.5.2" [dev-dependencies.wgpu] version = "0.17.1" [build-dependencies.pkg-config] version = "0.3" optional = true [features] calloop = [ "dep:calloop", "calloop-wayland-source", ] default = [ "calloop", "xkbcommon", ] xkbcommon = [ "dep:xkbcommon", "bytemuck", "pkg-config", "xkeysym/bytemuck", ] smithay-client-toolkit-0.18.0/Cargo.toml.orig000064400000000000000000000034451046102023000172240ustar 00000000000000[package] name = "smithay-client-toolkit" version = "0.18.0" authors = ["Elinor Berger ", "i509VCB ", "Ashley Wulber "] documentation = "https://smithay.github.io/client-toolkit" repository = "https://github.com/smithay/client-toolkit" license = "MIT" edition = "2021" categories = ["gui"] keywords = ["wayland", "client"] description = "Toolkit for making client wayland applications." readme = "README.md" [package.metadata.docs.rs] features = ["calloop", "xkbcommon"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] bitflags = "2.4" bytemuck = { version = "1.13.0", optional = true } cursor-icon = "1.0.0" libc = "0.2.148" log = "0.4" memmap2 = "0.9.0" rustix = { version = "0.38.15", features = ["fs", "pipe", "shm"] } thiserror = "1.0.30" wayland-backend = "0.3.0" wayland-client = "0.31.1" wayland-cursor = "0.31.0" wayland-protocols = { version = "0.31.0", features = ["client", "staging", "unstable"] } wayland-protocols-wlr = { version = "0.2.0", features = ["client"] } wayland-scanner = "0.31.0" wayland-csd-frame = "0.3.0" xkbcommon = { version = "0.7.0", optional = true, features = ["wayland"] } xkeysym = "0.2.0" calloop = { version = "0.12.1", optional = true } calloop-wayland-source = { version = "0.2.0", optional = true } [features] default = ["calloop", "xkbcommon"] calloop = ["dep:calloop", "calloop-wayland-source"] xkbcommon = ["dep:xkbcommon", "bytemuck", "pkg-config", "xkeysym/bytemuck"] [build-dependencies] pkg-config = { version = "0.3", optional = true } [dev-dependencies] bytemuck = "1.13.0" drm-fourcc = "2.2.0" font-kit = "0.11.0" image = "0.24" env_logger = "0.10" wgpu = "0.17.1" raqote = "0.8.2" raw-window-handle = "0.5.2" pollster = "0.3.0" [[example]] name = "wgpu" required-features = ["wayland-backend/client_system"] smithay-client-toolkit-0.18.0/LICENSE.txt000064400000000000000000000020401046102023000161460ustar 00000000000000Copyright (c) 2018 Victor Berger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.smithay-client-toolkit-0.18.0/README.md000064400000000000000000000025771046102023000156210ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/smithay-client-toolkit.svg)](https://crates.io/crates/smithay-client-toolkit) [![docs.rs](https://docs.rs/smithay-client-toolkit/badge.svg)](https://docs.rs/smithay-client-toolkit) [![Build Status](https://github.com/Smithay/client-toolkit/workflows/Continuous%20Integration/badge.svg)](https://github.com/Smithay/client-toolkit/actions?query=workflow%3A%22Continuous+Integration%22) # Smithay's Client Toolkit This crate is a toolkit for writing wayland clients in rust, on top of [wayland-client](https://crates.io/crates/wayland-client). Currently a work in progress, it currently provides the following utilities: - Automatic binding of general wayland globals (`wl_compositor`, `wl_shm`, etc..) - Abstraction to create windows (aka toplevel surfaces), abstracting the interaction with the shell (`xdg_shell` or `wl_shell`) and the drawing of decorations - Wrapper for `wl_keyboard` for automatic keymap interpretation using `libxkbcommon.so`. - Utilites for creating dpi aware surfaces. ## Documentation The documentation for the master branch is [available online](https://smithay.github.io/client-toolkit/). The documentation for the releases can be found on [docs.rs](https://docs.rs/smithay-client-toolkit). ## Requirements Requires at least rust 1.65 to be used and version 1.15 of the wayland system libraries if using the system backend. smithay-client-toolkit-0.18.0/build.rs000064400000000000000000000001521046102023000157720ustar 00000000000000fn main() { #[cfg(feature = "xkbcommon")] pkg_config::Config::new().find("xkbcommon").unwrap(); } smithay-client-toolkit-0.18.0/doc_index.html000064400000000000000000000002121046102023000171440ustar 00000000000000 smithay-client-toolkit-0.18.0/examples/data_device.rs000064400000000000000000001070261046102023000207510ustar 00000000000000use std::{ convert::TryInto, fs::{self, File}, io::{BufRead, BufReader, Write}, os::unix::io::OwnedFd, time::Duration, }; use smithay_client_toolkit::reexports::calloop::{ EventLoop, LoopHandle, PostAction, RegistrationToken, }; use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, data_device_manager::{ data_device::{DataDevice, DataDeviceHandler}, data_offer::{DataOfferHandler, DragOffer, SelectionOffer}, data_source::{CopyPasteSource, DataSourceHandler, DragSource}, DataDeviceManagerState, WritePipe, }, delegate_compositor, delegate_data_device, delegate_keyboard, delegate_output, delegate_pointer, delegate_primary_selection, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, primary_selection::{ device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler}, offer::PrimarySelectionOffer, selection::{PrimarySelectionSource, PrimarySelectionSourceHandler}, PrimarySelectionManagerState, }, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers}, pointer::{PointerEvent, PointerEventKind, PointerHandler, BTN_LEFT}, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, }; use wayland_client::{ globals::registry_queue_init, protocol::{ wl_data_device::WlDataDevice, wl_data_device_manager::DndAction, wl_keyboard::{self, WlKeyboard}, wl_output, wl_pointer::{self, WlPointer}, wl_seat::{self, WlSeat}, wl_shm, wl_surface, }, Connection, QueueHandle, }; use wayland_protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }; fn main() { println!( "Press c to set the selection, p to set primary selection, or click and drag on\ the window to drag and drop. Selection contents are printed automatically." ); env_logger::init(); // All Wayland apps start by connecting the compositor (server). let conn = Connection::connect_to_env().unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); let loop_handle = event_loop.handle(); WaylandSource::new(conn.clone(), event_queue).insert(loop_handle).unwrap(); // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); // For desktop platforms, the XDG shell is the standard protocol for creating desktop windows. let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available"); // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer // we share with the compositor process. let shm = Shm::bind(&globals, &qh).expect("wl shm is not available."); // A window is created from a surface. let surface = compositor.create_surface(&qh); // And then we can create the window. let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh); // Configure the window, this may include hints to the compositor about the desired minimum size of the // window, app id for WM identification, the window title, etc. window.set_title("A wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.SimpleWindow"); window.set_min_size(Some((256, 256))); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); let pool = SlotPool::new(256 * 256 * 4, &shm).expect("Failed to create pool"); // Create primary selection manager state and log if it's not present. let primary_selection_manager_state = PrimarySelectionManagerState::bind(&globals, &qh).ok(); if primary_selection_manager_state.is_none() { eprintln!("zwp_primary_selection_v1 is not available."); } let mut simple_window = DataDeviceWindow { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), shm_state: shm, data_device_manager_state: DataDeviceManagerState::bind(&globals, &qh) .expect("data device manager is not available"), primary_selection_manager_state, exit: false, first_configure: true, pool, shift: None, buffer: None, window, height: 256, width: 256, keyboard: None, keyboard_focus: false, pointer: None, seat_objects: Vec::new(), copy_paste_sources: Vec::new(), selection_sources: Vec::new(), drag_sources: Vec::new(), loop_handle: event_loop.handle(), accept_counter: 0, dnd_offers: Vec::new(), selection_offers: Vec::new(), primary_selection_offers: Vec::new(), }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_loop.dispatch(Duration::from_millis(30), &mut simple_window).unwrap(); if simple_window.exit { println!("exiting example"); break; } } } struct DataDeviceWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm_state: Shm, data_device_manager_state: DataDeviceManagerState, primary_selection_manager_state: Option, exit: bool, first_configure: bool, pool: SlotPool, width: u32, height: u32, shift: Option, buffer: Option, window: Window, keyboard: Option, keyboard_focus: bool, pointer: Option, dnd_offers: Vec<(DragOffer, Vec, Option)>, selection_offers: Vec<(SelectionOffer, Vec, Option)>, primary_selection_offers: Vec<(PrimarySelectionOffer, Vec, Option)>, seat_objects: Vec, copy_paste_sources: Vec, selection_sources: Vec, drag_sources: Vec<(DragSource, bool)>, loop_handle: LoopHandle<'static, DataDeviceWindow>, accept_counter: u32, } impl CompositorHandler for DataDeviceWindow { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for DataDeviceWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for DataDeviceWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { self.width = configure.new_size.0.map(|w| w.get()).unwrap_or(self.width); self.height = configure.new_size.1.map(|h| h.get()).unwrap_or(self.height); self.buffer = None; // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(conn, qh); } } } impl SeatHandler for DataDeviceWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _qh: &QueueHandle, _seat: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { let seat_object = if let Some(seat_object) = self.seat_objects.iter_mut().find(|s| s.seat == seat) { seat_object } else { // create the data device here for this seat let data_device_manager = &self.data_device_manager_state; let data_device = data_device_manager.get_data_device(qh, &seat); let primary_device = self .primary_selection_manager_state .as_ref() .map(|manager| manager.get_selection_device(qh, &seat)); self.seat_objects.push(SeatObject { seat: seat.clone(), keyboard: None, pointer: None, data_device, primary_device, }); self.seat_objects.last_mut().unwrap() }; if capability == Capability::Keyboard && self.keyboard.is_none() { let keyboard = self.seat_state.get_keyboard(qh, &seat, None).expect("Failed to create keyboard"); self.keyboard = Some(keyboard.clone()); seat_object.keyboard.replace(keyboard); } if capability == Capability::Pointer && self.pointer.is_none() { let pointer = self.seat_state.get_pointer(qh, &seat).expect("Failed to create pointer"); self.pointer = Some(pointer.clone()); seat_object.pointer.replace(pointer); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } if capability == Capability::Pointer && self.pointer.is_some() { println!("Unset pointer capability"); self.pointer.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for DataDeviceWindow { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], _keysyms: &[Keysym], ) { if self.window.wl_surface() == surface { self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.window.wl_surface() == surface { self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, qh: &QueueHandle, kbd: &wl_keyboard::WlKeyboard, serial: u32, event: KeyEvent, ) { match event.utf8 { Some(s) if s.to_lowercase() == "c" => { println!("Creating copy paste source and setting selection..."); if let Some(data_device) = self.seat_objects.iter().find_map(|seat| { if seat.keyboard.as_ref() == Some(kbd) { Some(&seat.data_device) } else { None } }) { let source = self .data_device_manager_state .create_copy_paste_source(qh, SUPPORTED_MIME_TYPES.to_vec()); source.set_selection(data_device, serial); self.copy_paste_sources.push(source); } } Some(s) if s.to_lowercase() == "p" => { println!("Creating primary selection source and setting selection..."); if let Some(primary_selection_device) = self.seat_objects.iter().find_map(|seat| { if seat.keyboard.as_ref() == Some(kbd) { seat.primary_device.as_ref() } else { None } }) { let source = self .primary_selection_manager_state .as_ref() .unwrap() .create_selection_source(qh, SUPPORTED_MIME_TYPES.to_vec()); source.set_selection(primary_selection_device, serial); self.selection_sources.push(source); } } _ => {} }; } fn release_key( &mut self, _: &Connection, _qh: &QueueHandle, _kbd: &wl_keyboard::WlKeyboard, _serial: u32, _event: KeyEvent, ) { } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _serial: u32, _modifiers: Modifiers, ) { } } impl PointerHandler for DataDeviceWindow { fn pointer_frame( &mut self, _conn: &Connection, qh: &QueueHandle, pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { // Ignore events for other surfaces if self.window.wl_surface() != &event.surface { continue; } let surface = event.surface.clone(); match event.kind { Press { button, serial, .. } if button == BTN_LEFT => { if let Some(seat) = self.seat_objects.iter().find(|seat| seat.pointer.as_ref() == Some(pointer)) { println!("Creating drag and drop source and starting drag..."); self.shift = self.shift.xor(Some(0)); let source = self.data_device_manager_state.create_drag_and_drop_source( qh, SUPPORTED_MIME_TYPES.to_vec(), DndAction::Copy, ); source.start_drag(&seat.data_device, &surface, None, serial); self.drag_sources.push((source, false)); } } Motion { .. } => { // dbg!(event.position); } _ => {} } } } } impl ShmHandler for DataDeviceWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } } impl DataDeviceWindow { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let buffer = self.buffer.get_or_insert_with(|| { self.pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match self.pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = self .pool .create_buffer( self.width as i32, self.height as i32, stride, wl_shm::Format::Argb8888, ) .expect("create buffer"); *buffer = second_buffer; canvas } }; // Draw to the window: { let shift = self.shift.unwrap_or(0); canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { let x = ((index + shift as usize) % width as usize) as u32; let y = (index / width as usize) as u32; let a = 0xFF; let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); let color = (a << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); if let Some(shift) = &mut self.shift { *shift = (*shift + 1) % width; } } // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); // Request our next frame self.window.wl_surface().frame(qh, self.window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.window.wl_surface()).expect("buffer attach"); self.window.wl_surface().commit(); } } impl DataDeviceHandler for DataDeviceWindow { fn enter( &mut self, _conn: &Connection, _qh: &QueueHandle, wl_data_device: &WlDataDevice, ) { let data_device = &self .seat_objects .iter() .find(|seat| seat.data_device.inner() == wl_data_device) .unwrap() .data_device; let drag_offer = data_device.data().drag_offer().unwrap(); println!("data offer entered x: {:.2} y: {:.2}", drag_offer.x, drag_offer.y); // Accept the first mime type we support. if let Some(mime) = drag_offer.with_mime_types(|mime_types| { for mime in mime_types { if SUPPORTED_MIME_TYPES.contains(&mime.as_str()) { return Some(mime.clone()); } } None }) { drag_offer.accept_mime_type(0, Some(mime)); } // Accept the action now just in case drag_offer.set_actions(DndAction::Copy, DndAction::Copy); } fn leave(&mut self, _conn: &Connection, _qh: &QueueHandle, _data_device: &WlDataDevice) { println!("Data offer left"); } fn motion( &mut self, _conn: &Connection, _qh: &QueueHandle, wl_data_device: &WlDataDevice, ) { let data_device = &self .seat_objects .iter() .find(|seat| seat.data_device.inner() == wl_data_device) .unwrap() .data_device; let DragOffer { x, y, time, .. } = data_device.data().drag_offer().unwrap(); dbg!((time, x, y)); } fn selection( &mut self, _conn: &Connection, _qh: &QueueHandle, wl_data_device: &WlDataDevice, ) { let data_device = &self .seat_objects .iter() .find(|seat| seat.data_device.inner() == wl_data_device) .unwrap() .data_device; if let Some(offer) = data_device.data().selection_offer() { offer.with_mime_types(|mimes| { println!("Received selection offer with mime types:"); for mime in mimes { println!("\t{mime}"); } }); self.selection_offers.push((offer.clone(), Vec::new(), None)); let cur_offer = self.selection_offers.last_mut().unwrap(); let mime_type = match offer.with_mime_types(pick_mime) { Some(mime_type) => mime_type, None => return, }; let read_pipe = match offer.receive(mime_type) { Ok(p) => p, Err(e) => { eprintln!("Failed to receive the offer: {:?}", e); return; } }; let cur_offer_ = cur_offer.0.clone(); if let Ok(token) = self.loop_handle.insert_source(read_pipe, move |_, f, state| { let offer = match state.selection_offers.iter().position(|o| o.0 == cur_offer_) { Some(s) => state.selection_offers.remove(s), None => return PostAction::Continue, }; let (offer, mut data, token) = match offer { (o, d, Some(t)) => (o, d, t), _ => return PostAction::Continue, }; // SAFETY: it's safe as long as we don't close the underlying file. let f: &mut fs::File = unsafe { f.get_mut() }; let mut reader = BufReader::new(f); let consumed = match reader.fill_buf() { Ok(buf) => { if buf.is_empty() { println!("selection data: {:?}", String::from_utf8(data.clone())); state.selection_offers.push((offer, Vec::new(), None)); return PostAction::Remove; } else { data.extend_from_slice(buf); state.selection_offers.push((offer, data, Some(token))); } buf.len() } Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { state.selection_offers.push((offer, data, Some(token))); return PostAction::Continue; } Err(e) => { eprintln!("Error reading selection data: {}", e); state.selection_offers.push((offer, Vec::new(), None)); return PostAction::Remove; } }; reader.consume(consumed); PostAction::Continue }) { cur_offer.2 = Some(token); } } } fn drop_performed( &mut self, _conn: &Connection, _qh: &QueueHandle, wl_data_device: &WlDataDevice, ) { let data_device = &self .seat_objects .iter() .find(|seat| seat.data_device.inner() == wl_data_device) .unwrap() .data_device; if let Some(offer) = data_device.data().drag_offer() { println!("Dropped: {offer:?}"); self.dnd_offers.push((offer.clone(), Vec::new(), None)); let cur_offer = self.dnd_offers.last_mut().unwrap(); let mime_type = match offer.with_mime_types(pick_mime) { Some(mime) => mime, None => return, }; let read_pipe = match cur_offer.0.receive(mime_type.clone()) { Ok(p) => p, Err(e) => { eprintln!("Failed to receive the offer: {:?}", e); return; } }; self.accept_counter += 1; cur_offer.0.accept_mime_type(self.accept_counter, Some(mime_type)); cur_offer.0.set_actions(DndAction::Copy, DndAction::Copy); let cur_offer_ = cur_offer.0.clone(); match self.loop_handle.insert_source(read_pipe, move |_, f, state| { let offer = match state.dnd_offers.iter().position(|o| o.0 == cur_offer_) { Some(s) => state.dnd_offers.remove(s), None => return PostAction::Continue, }; let (offer, mut data, token) = match offer { (o, d, Some(t)) => (o, d, t), _ => return PostAction::Continue, }; // SAFETY: it's safe as long as we don't close the underlying file. let f: &mut fs::File = unsafe { f.get_mut() }; let mut reader = BufReader::new(f); let consumed = match reader.fill_buf() { Ok(buf) => { if buf.is_empty() { println!("Dropped data: {:?}", String::from_utf8(data.clone())); offer.finish(); offer.destroy(); state.dnd_offers.push((offer, Vec::new(), None)); return PostAction::Remove; } else { data.extend_from_slice(buf); state.dnd_offers.push((offer, data, Some(token))); } buf.len() } Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { state.dnd_offers.push((offer, data, Some(token))); return PostAction::Continue; } Err(e) => { eprintln!("Error reading dropped data: {}", e); offer.finish(); offer.destroy(); return PostAction::Remove; } }; reader.consume(consumed); PostAction::Continue }) { Ok(token) => { cur_offer.2 = Some(token); } Err(err) => { eprintln!("{err}"); cur_offer.0.finish(); } } } } } impl DataOfferHandler for DataDeviceWindow { fn source_actions( &mut self, _conn: &Connection, _qh: &QueueHandle, offer: &mut DragOffer, actions: wayland_client::protocol::wl_data_device_manager::DndAction, ) { println!("Source actions: {actions:?}"); offer.set_actions(DndAction::Copy, DndAction::Copy); } fn selected_action( &mut self, _conn: &Connection, _qh: &QueueHandle, _offer: &mut DragOffer, actions: wayland_client::protocol::wl_data_device_manager::DndAction, ) { // In this small example there isn't much to do here. // Normal applications might track the action and then handling each differently. println!("Selected action: {actions:?}"); } } impl DataSourceHandler for DataDeviceWindow { fn accept_mime( &mut self, _conn: &Connection, _qh: &QueueHandle, _source: &wayland_client::protocol::wl_data_source::WlDataSource, mime: Option, ) { println!("Source mime type: {mime:?} was accepted"); } fn send_request( &mut self, _conn: &Connection, _qh: &QueueHandle, source: &wayland_client::protocol::wl_data_source::WlDataSource, mime: String, write_pipe: WritePipe, ) { let fd = OwnedFd::from(write_pipe); if self .copy_paste_sources .iter_mut() .any(|s| s.inner() == source && SUPPORTED_MIME_TYPES.contains(&mime.as_str())) { let mut f = File::from(fd); writeln!(f, "Copied from selection via sctk").unwrap(); } else if self .drag_sources .iter_mut() .any(|s| s.0.inner() == source && SUPPORTED_MIME_TYPES.contains(&mime.as_str()) && s.1) { let mut f = File::from(fd); writeln!(f, "Dropped via sctk").unwrap(); } } fn cancelled( &mut self, _conn: &Connection, _qh: &QueueHandle, source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { self.drag_sources.retain(|s| s.0.inner() != source); source.destroy(); } fn dnd_dropped( &mut self, _conn: &Connection, _qh: &QueueHandle, _source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { println!("Drop performed"); } fn dnd_finished( &mut self, _conn: &Connection, _qh: &QueueHandle, source: &wayland_client::protocol::wl_data_source::WlDataSource, ) { println!("Finished"); self.drag_sources.retain(|s| s.0.inner() != source); source.destroy(); } fn action( &mut self, _conn: &Connection, _qh: &QueueHandle, source: &wayland_client::protocol::wl_data_source::WlDataSource, action: wayland_client::protocol::wl_data_device_manager::DndAction, ) { if let Some(source) = self.drag_sources.iter_mut().find(|s| s.0.inner() == source) { source.1 = action.contains(DndAction::Copy); } } } impl PrimarySelectionDeviceHandler for DataDeviceWindow { fn selection( &mut self, _: &Connection, _: &QueueHandle, primary_device: &ZwpPrimarySelectionDeviceV1, ) { let primary_device = self .seat_objects .iter() .find(|seat| seat.primary_device.as_ref().map(|p| p.inner()) == Some(primary_device)) .unwrap() .primary_device .as_ref() .unwrap(); if let Some(offer) = primary_device.data().selection_offer() { offer.with_mime_types(|mimes| { println!("Received primary selection offer with mime types:"); for mime in mimes { println!("\t{mime}"); } }); // Add a new offer. self.primary_selection_offers.push((offer.clone(), Vec::new(), None)); let mime_type = match offer.with_mime_types(pick_mime) { Some(mime) => mime, None => return, }; let read_pipe = match offer.receive(mime_type) { Ok(p) => p, Err(e) => { eprintln!("Failed to receive the offer: {:?}", e); return; } }; if let Ok(token) = self.loop_handle.insert_source(read_pipe, move |_, f, state| { let offer = match state.primary_selection_offers.iter().position(|of| of.0 == offer) { Some(s) => state.primary_selection_offers.remove(s), None => return PostAction::Continue, }; let (offer, mut data, token) = match offer { (o, d, Some(t)) => (o, d, t), _ => return PostAction::Continue, }; // SAFETY: it's safe as long as we don't close the underlying file. let f: &mut fs::File = unsafe { f.get_mut() }; let mut reader = BufReader::new(f); let consumed = match reader.fill_buf() { Ok(buf) => { if buf.is_empty() { println!( "primary selection data: {:?}", String::from_utf8(data.clone()) ); state.primary_selection_offers.push((offer, Vec::new(), None)); return PostAction::Remove; } else { data.extend_from_slice(buf); state.primary_selection_offers.push((offer, data, Some(token))); } buf.len() } Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { state.primary_selection_offers.push((offer, data, Some(token))); return PostAction::Continue; } Err(e) => { eprintln!("Error reading selection data: {}", e); state.primary_selection_offers.push((offer, Vec::new(), None)); return PostAction::Remove; } }; reader.consume(consumed); PostAction::Continue }) { self.primary_selection_offers.last_mut().unwrap().2 = Some(token); } } } } impl PrimarySelectionSourceHandler for DataDeviceWindow { fn send_request( &mut self, _: &Connection, _: &QueueHandle, source: &ZwpPrimarySelectionSourceV1, mime: String, write_pipe: WritePipe, ) { let fd = OwnedFd::from(write_pipe); if self .selection_sources .iter_mut() .any(|s| s.inner() == source && SUPPORTED_MIME_TYPES.contains(&mime.as_str())) { let mut f = File::from(fd); writeln!(f, "Copied from primary selection via sctk").unwrap(); } } fn cancelled( &mut self, _: &Connection, _: &QueueHandle, source: &ZwpPrimarySelectionSourceV1, ) { self.selection_sources.retain(|s| s.inner() == source); } } impl ProvidesRegistryState for DataDeviceWindow { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState]; } struct SeatObject { seat: WlSeat, keyboard: Option, pointer: Option, data_device: DataDevice, primary_device: Option, } delegate_compositor!(DataDeviceWindow); delegate_output!(DataDeviceWindow); delegate_shm!(DataDeviceWindow); delegate_seat!(DataDeviceWindow); delegate_keyboard!(DataDeviceWindow); delegate_pointer!(DataDeviceWindow); delegate_xdg_shell!(DataDeviceWindow); delegate_xdg_window!(DataDeviceWindow); delegate_data_device!(DataDeviceWindow); delegate_primary_selection!(DataDeviceWindow); delegate_registry!(DataDeviceWindow); const SUPPORTED_MIME_TYPES: &[&str; 6] = &[ "text/plain;charset=utf-8", "text/plain;charset=UTF-8", "UTF8_STRING", "STRING", "text/plain", "TEXT", ]; fn pick_mime(mime_types: &[String]) -> Option { for mime in mime_types { if SUPPORTED_MIME_TYPES.contains(&mime.as_str()) { return Some(mime.clone()); } } None } smithay-client-toolkit-0.18.0/examples/dmabuf_formats.rs000064400000000000000000000076521046102023000215160ustar 00000000000000use drm_fourcc::{DrmFourcc, DrmModifier}; use smithay_client_toolkit::{ dmabuf::{DmabufFeedback, DmabufFormat, DmabufHandler, DmabufState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, }; use wayland_client::{globals::registry_queue_init, protocol::wl_buffer, Connection, QueueHandle}; use wayland_protocols::wp::linux_dmabuf::zv1::client::{ zwp_linux_buffer_params_v1, zwp_linux_dmabuf_feedback_v1, }; struct AppData { registry_state: RegistryState, dmabuf_state: DmabufState, feedback: Option, } impl DmabufHandler for AppData { fn dmabuf_state(&mut self) -> &mut DmabufState { &mut self.dmabuf_state } fn dmabuf_feedback( &mut self, _conn: &Connection, _qh: &QueueHandle, _proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, feedback: DmabufFeedback, ) { self.feedback = Some(feedback); } fn created( &mut self, _conn: &Connection, _qh: &QueueHandle, _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, _buffer: wl_buffer::WlBuffer, ) { } fn failed( &mut self, _conn: &Connection, _qh: &QueueHandle, _params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, ) { } fn released( &mut self, _conn: &Connection, _qh: &QueueHandle, _buffer: &wl_buffer::WlBuffer, ) { } } impl ProvidesRegistryState for AppData { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![,]; } fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut app_data = AppData { registry_state: RegistryState::new(&globals), dmabuf_state: DmabufState::new(&globals, &qh), feedback: None, }; match app_data.dmabuf_state.version() { None => println!("`zwp_linux_dmabuf_v1` version `>3` not supported by compositor."), Some(0..=2) => unreachable!(), Some(3) => { println!("Version `3` of `zwp_linux_dmabuf_v1` supported. Showing modifiers.\n"); // Roundtrip after binding global to receive modifier events. event_queue.roundtrip(&mut app_data).unwrap(); for entry in app_data.dmabuf_state.modifiers() { print_format(entry); } return; } Some(4..) => { println!("Version `4` of `zwp_linux_dmabuf_v1` supported. Showing default dmabuf feedback.\n"); app_data.dmabuf_state.get_default_feedback(&qh).unwrap(); let feedback = loop { event_queue.blocking_dispatch(&mut app_data).unwrap(); if let Some(feedback) = app_data.feedback.as_ref() { break feedback; } }; println!("Main device: 0x{:x}", feedback.main_device()); println!("Tranches:"); let format_table = feedback.format_table(); for tranche in feedback.tranches() { println!(" Device: 0x{:x}", tranche.device); println!(" Flags: {:?}", tranche.flags); println!(" Formats"); for idx in &tranche.formats { print!(" "); print_format(&format_table[*idx as usize]); } } } } } fn print_format(format: &DmabufFormat) { print!("Format: "); match DrmFourcc::try_from(format.format) { Ok(format) => print!("{:?}", format), Err(err) => print!("{:?}", err), } println!(", Modifier: {:?}", DrmModifier::from(format.modifier)); } smithay_client_toolkit::delegate_dmabuf!(AppData); smithay_client_toolkit::delegate_registry!(AppData); smithay-client-toolkit-0.18.0/examples/generic_list_seats.rs000064400000000000000000000042621046102023000223650ustar 00000000000000use smithay_client_toolkit::{ delegate_registry, delegate_seat, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{Capability, SeatHandler, SeatState}, }; use wayland_client::{globals::registry_queue_init, protocol::wl_seat, Connection, QueueHandle}; fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut list_seats = ListSeats { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), _dummy: MyTest {}, }; event_queue.roundtrip(&mut list_seats).unwrap(); println!("Available seats:"); for seat in list_seats.seat_state.seats() { if let Some(info) = list_seats.seat_state.info(&seat) { println!("{info}"); } } } pub trait Test { fn test() { println!("Test"); } } pub struct MyTest {} impl Test for MyTest {} struct ListSeats { seat_state: SeatState, registry_state: RegistryState, _dummy: T, } impl SeatHandler for ListSeats { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) { // Not applicable } fn new_capability( &mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, _: Capability, ) { // Not applicable } fn remove_capability( &mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, _: Capability, ) { // Not applicable } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) { // Not applicable } } delegate_seat!(@ ListSeats); delegate_registry!(@ ListSeats); impl ProvidesRegistryState for ListSeats { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers!(SeatState); } smithay-client-toolkit-0.18.0/examples/generic_simple_window.rs000064400000000000000000000342231046102023000230730ustar 00000000000000use std::convert::TryInto; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Modifiers}, pointer::{PointerEvent, PointerEventKind, PointerHandler}, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, Connection, QueueHandle, }; use xkeysym::Keysym; fn main() { env_logger::init(); // All Wayland apps start by connecting the compositor (server). let conn = Connection::connect_to_env().unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); // For desktop platforms, the XDG shell is the standard protocol for creating desktop windows. let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available"); // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer // we share with the compositor process. let shm = Shm::bind(&globals, &qh).expect("wl shm is not available."); // A window is created from a surface. let surface = compositor.create_surface(&qh); // And then we can create the window. let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh); // Configure the window, this may include hints to the compositor about the desired minimum size of the // window, app id for WM identification, the window title, etc. window.set_title("A wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.SimpleWindow"); window.set_min_size(Some((256, 256))); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); // We don't know how large the window will be yet, so lets assume the minimum size we suggested for the // initial memory allocation. let pool = SlotPool::new(256 * 256 * 4, &shm).expect("Failed to create pool"); let mut simple_window = SimpleWindow { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), shm, exit: false, first_configure: true, pool, width: 256, height: 256, shift: None, buffer: None, window, keyboard: None, keyboard_focus: false, pointer: None, _dummy: MyTest {}, }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut simple_window).unwrap(); if simple_window.exit { println!("exiting example"); break; } } } pub trait Test { fn test() { println!("Test"); } } pub struct MyTest {} impl Test for MyTest {} struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm: Shm, exit: bool, first_configure: bool, pool: SlotPool, width: u32, height: u32, shift: Option, buffer: Option, window: Window, keyboard: Option, keyboard_focus: bool, pointer: Option, _dummy: T, } impl CompositorHandler for SimpleWindow { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for SimpleWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for SimpleWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { self.buffer = None; self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256); self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256); // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(conn, qh); } } } impl SeatHandler for SimpleWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { println!("Set keyboard capability"); let keyboard = self.seat_state.get_keyboard(qh, &seat, None).expect("Failed to create keyboard"); self.keyboard = Some(keyboard); } if capability == Capability::Pointer && self.pointer.is_none() { println!("Set pointer capability"); let pointer = self.seat_state.get_pointer(qh, &seat).expect("Failed to create pointer"); self.pointer = Some(pointer); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } if capability == Capability::Pointer && self.pointer.is_some() { println!("Unset pointer capability"); self.pointer.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for SimpleWindow { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], keysyms: &[Keysym], ) { if self.window.wl_surface() == surface { println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.window.wl_surface() == surface { println!("Release keyboard focus on window"); self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key press: {event:?}"); } fn release_key( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key release: {event:?}"); } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _serial: u32, modifiers: Modifiers, ) { println!("Update modifiers: {modifiers:?}"); } } impl PointerHandler for SimpleWindow { fn pointer_frame( &mut self, _conn: &Connection, _qh: &QueueHandle, _pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { // Ignore events for other surfaces if &event.surface != self.window.wl_surface() { continue; } match event.kind { Enter { .. } => { println!("Pointer entered @{:?}", event.position); } Leave { .. } => { println!("Pointer left"); } Motion { .. } => {} Press { button, .. } => { println!("Press {:x} @ {:?}", button, event.position); self.shift = self.shift.xor(Some(0)); } Release { button, .. } => { println!("Release {:x} @ {:?}", button, event.position); } Axis { horizontal, vertical, .. } => { println!("Scroll H:{horizontal:?}, V:{vertical:?}"); } } } } } impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl SimpleWindow { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let buffer = self.buffer.get_or_insert_with(|| { self.pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match self.pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = self .pool .create_buffer( self.width as i32, self.height as i32, stride, wl_shm::Format::Argb8888, ) .expect("create buffer"); *buffer = second_buffer; canvas } }; // Draw to the window: { let shift = self.shift.unwrap_or(0); canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { let x = ((index + shift as usize) % width as usize) as u32; let y = (index / width as usize) as u32; let a = 0xFF; let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); let color = (a << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); if let Some(shift) = &mut self.shift { *shift = (*shift + 1) % width; } } // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); // Request our next frame self.window.wl_surface().frame(qh, self.window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.window.wl_surface()).expect("buffer attach"); self.window.commit(); } } delegate_compositor!(@ SimpleWindow); delegate_output!(@ SimpleWindow); delegate_shm!(@ SimpleWindow); delegate_seat!(@ SimpleWindow); delegate_keyboard!(@ SimpleWindow); delegate_pointer!(@ SimpleWindow); delegate_xdg_shell!(@ SimpleWindow); delegate_xdg_window!(@ SimpleWindow); delegate_registry!(@ SimpleWindow); impl ProvidesRegistryState for SimpleWindow { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState,]; } smithay-client-toolkit-0.18.0/examples/image_viewer.rs000064400000000000000000000221071046102023000211600ustar 00000000000000use std::env; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_output, wl_shm, wl_surface}, Connection, QueueHandle, }; fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut state = State { registry_state: RegistryState::new(&globals), output_state: OutputState::new(&globals, &qh), compositor_state: CompositorState::bind(&globals, &qh) .expect("wl_compositor not available"), shm_state: Shm::bind(&globals, &qh).expect("wl_shm not available"), xdg_shell_state: XdgShell::bind(&globals, &qh).expect("xdg shell not available"), pool: None, windows: Vec::new(), }; let mut pool_size = 0; for path in env::args_os().skip(1) { let image = match image::open(&path) { Ok(i) => i, Err(e) => { println!("Failed to open image {}.", path.to_string_lossy()); println!("Error was: {e:?}"); return; } }; // We'll need the image in RGBA for drawing it let image = image.to_rgba8(); let surface = state.compositor_state.create_surface(&qh); pool_size += image.width() * image.height() * 4; let window = state.xdg_shell_state.create_window(surface, WindowDecorations::ServerDefault, &qh); window.set_title("A wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.ImageViewer"); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); state.windows.push(ImageViewer { width: image.width(), height: image.height(), window, image, first_configure: true, damaged: true, buffer: None, }); } let pool = SlotPool::new(pool_size as usize, &state.shm_state).expect("Failed to create pool"); state.pool = Some(pool); if state.windows.is_empty() { println!("USAGE: ./image_viewer []..."); return; } // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut state).unwrap(); if state.windows.is_empty() { println!("exiting example"); break; } } } struct State { registry_state: RegistryState, output_state: OutputState, compositor_state: CompositorState, shm_state: Shm, xdg_shell_state: XdgShell, pool: Option, windows: Vec, } struct ImageViewer { window: Window, image: image::RgbaImage, width: u32, height: u32, buffer: Option, first_configure: bool, damaged: bool, } impl CompositorHandler for State { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for State { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for State { fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { self.windows.retain(|v| v.window != *window); } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, window: &Window, configure: WindowConfigure, _serial: u32, ) { for viewer in &mut self.windows { if viewer.window != *window { continue; } viewer.buffer = None; viewer.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256); viewer.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256); viewer.damaged = true; // Initiate the first draw. viewer.first_configure = false; } self.draw(conn, qh); } } impl ShmHandler for State { fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } } impl State { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { for viewer in &mut self.windows { if viewer.first_configure || !viewer.damaged { continue; } let window = &viewer.window; let width = viewer.width; let height = viewer.height; let stride = viewer.width as i32 * 4; let pool = self.pool.as_mut().unwrap(); let buffer = viewer.buffer.get_or_insert_with(|| { pool.create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = pool .create_buffer( viewer.width as i32, viewer.height as i32, stride, wl_shm::Format::Argb8888, ) .expect("create buffer"); *buffer = second_buffer; canvas } }; // Draw to the window: { let image = image::imageops::resize( &viewer.image, viewer.width, viewer.height, image::imageops::FilterType::Nearest, ); for (pixel, argb) in image.pixels().zip(canvas.chunks_exact_mut(4)) { // We do this in an horribly inefficient manner, for the sake of simplicity. // We'll send pixels to the server in ARGB8888 format (this is one of the only // formats that are guaranteed to be supported), but image provides it in // big-endian RGBA8888, so we need to do the conversion. argb[3] = pixel.0[3]; argb[2] = pixel.0[0]; argb[1] = pixel.0[1]; argb[0] = pixel.0[2]; } } // Damage the entire window window.wl_surface().damage_buffer(0, 0, viewer.width as i32, viewer.height as i32); viewer.damaged = false; // Request our next frame window.wl_surface().frame(qh, window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(window.wl_surface()).expect("buffer attach"); window.wl_surface().commit(); } } } delegate_compositor!(State); delegate_output!(State); delegate_shm!(State); delegate_xdg_shell!(State); delegate_xdg_window!(State); delegate_registry!(State); impl ProvidesRegistryState for State { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers!(OutputState); } smithay-client-toolkit-0.18.0/examples/image_viewporter.rs000064400000000000000000000245531046102023000220740ustar 00000000000000use std::{env, path::Path}; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_simple, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState, SimpleGlobal}, registry_handlers, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_output, wl_shm, wl_surface}, Connection, Dispatch, QueueHandle, }; use wayland_protocols::wp::viewporter::client::{ wp_viewport::{self, WpViewport}, wp_viewporter::{self, WpViewporter}, }; fn main() { env_logger::init(); // All Wayland apps start by connecting the compositor (server). let conn = Connection::connect_to_env().unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); // For desktop platforms, the XDG shell is the standard protocol for creating desktop windows. let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available"); // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer // we share with the compositor process. let shm = Shm::bind(&globals, &qh).expect("wl shm is not available."); // In this example, we use the viewporter to allow the compositor to scale and crop presented images. // // Since the wp_viewporter protocol has no events, we can use SimpleGlobal. let wp_viewporter = SimpleGlobal::::bind(&globals, &qh) .expect("wp_viewporter not available"); let mut windows = Vec::new(); let mut pool_size = 0; for path in env::args_os().skip(1) { let image = match image::open(&path) { Ok(i) => i, Err(e) => { println!("Failed to open image {}.", path.to_string_lossy()); println!("Error was: {e:?}"); return; } }; // We'll need the image in RGBA for drawing it let image = image.to_rgba8(); pool_size += image.width() * image.height() * 4; // A window is created from a surface. let surface = compositor.create_surface(&qh); // And then we can create the window. let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh); // Configure the window, this may include hints to the compositor about the desired minimum size of the // window, app id for WM identification, the window title, etc. // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.ImageViewer"); window.set_min_size(Some((256, 256))); let path: &Path = path.as_os_str().as_ref(); window.set_title(path.components().last().unwrap().as_os_str().to_string_lossy()); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); // For scaling, create a viewport for the window. let viewport = wp_viewporter.get().expect("Requires wp_viewporter").get_viewport( window.wl_surface(), &qh, (), ); windows.push(ImageViewer { width: image.width(), height: image.height(), window, viewport, image, first_configure: true, damaged: true, }); } if windows.is_empty() { println!("USAGE: ./image_viewer []..."); return; } let pool = SlotPool::new(pool_size as usize, &shm).expect("Failed to create pool"); let mut state = State { registry_state: RegistryState::new(&globals), output_state: OutputState::new(&globals, &qh), shm, wp_viewporter, pool, windows, }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut state).unwrap(); if state.windows.is_empty() { println!("exiting example"); break; } } } struct State { registry_state: RegistryState, output_state: OutputState, shm: Shm, wp_viewporter: SimpleGlobal, pool: SlotPool, windows: Vec, } struct ImageViewer { window: Window, image: image::RgbaImage, viewport: WpViewport, width: u32, height: u32, first_configure: bool, damaged: bool, } impl CompositorHandler for State { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for State { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for State { fn request_close(&mut self, _: &Connection, _: &QueueHandle, window: &Window) { self.windows.retain(|v| v.window != *window); } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, window: &Window, configure: WindowConfigure, _serial: u32, ) { for viewer in &mut self.windows { if viewer.window != *window { continue; } if let (Some(width), Some(height)) = configure.new_size { viewer.width = width.get(); viewer.height = height.get(); viewer.viewport.set_destination(width.get() as _, height.get() as _); if !viewer.first_configure { viewer.window.commit(); } } // Initiate the first draw. viewer.first_configure = false; } self.draw(conn, qh); } } impl ShmHandler for State { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl State { pub fn draw(&mut self, _conn: &Connection, _qh: &QueueHandle) { for viewer in &mut self.windows { if viewer.first_configure || !viewer.damaged { continue; } let window = &viewer.window; let width = viewer.image.width(); let height = viewer.image.height(); let stride = width as i32 * 4; let (buffer, canvas) = self .pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer"); // Draw to the window for (pixel, argb) in viewer.image.pixels().zip(canvas.chunks_exact_mut(4)) { // We do this in an horribly inefficient manner, for the sake of simplicity. // We'll send pixels to the server in ARGB8888 format (this is one of the only // formats that are guaranteed to be supported), but image provides it in // big-endian RGBA8888, so we need to do the conversion. argb[3] = pixel.0[3]; argb[2] = pixel.0[0]; argb[1] = pixel.0[1]; argb[0] = pixel.0[2]; } // Damage the entire window (using the real dimensions) window.wl_surface().damage_buffer(0, 0, viewer.width as i32, viewer.height as i32); viewer.damaged = false; // Set the entire buffer as the source area for the viewport. // Destination was set during configure. viewer.viewport.set_source(0.0, 0.0, viewer.width as f64, viewer.height as f64); // Attach and commit to present. buffer.attach_to(window.wl_surface()).expect("buffer attach"); window.wl_surface().commit(); } } } delegate_compositor!(State); delegate_output!(State); delegate_shm!(State); delegate_xdg_shell!(State); delegate_xdg_window!(State); delegate_simple!(State, WpViewporter, 1); delegate_registry!(State); impl ProvidesRegistryState for State { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers!(OutputState); } impl AsMut> for State { fn as_mut(&mut self) -> &mut SimpleGlobal { &mut self.wp_viewporter } } impl Dispatch for State { fn event( _: &mut State, _: &WpViewport, _: wp_viewport::Event, _: &(), _: &Connection, _: &QueueHandle, ) { unreachable!("wp_viewport::Event is empty in version 1") } } impl Drop for ImageViewer { fn drop(&mut self) { self.viewport.destroy() } } smithay-client-toolkit-0.18.0/examples/list_outputs.rs000064400000000000000000000125501046102023000212740ustar 00000000000000//! Test application to list all available outputs. use std::error::Error; use smithay_client_toolkit::{ delegate_output, delegate_registry, output::{OutputHandler, OutputInfo, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, }; use wayland_client::{globals::registry_queue_init, protocol::wl_output, Connection, QueueHandle}; fn main() -> Result<(), Box> { // We initialize the logger for the purpose of debugging. // Set `RUST_LOG=debug` to see extra debug information. env_logger::init(); // Try to connect to the Wayland server. let conn = Connection::connect_to_env()?; // Now create an event queue and a handle to the queue so we can create objects. let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // Initialize the registry handling so other parts of Smithay's client toolkit may bind // globals. let registry_state = RegistryState::new(&globals); // Initialize the delegate we will use for outputs. let output_delegate = OutputState::new(&globals, &qh); // Set up application state. // // This is where you will store your delegates and any data you wish to access/mutate while the // application is running. let mut list_outputs = ListOutputs { registry_state, output_state: output_delegate }; // `OutputState::new()` binds the output globals found in `registry_queue_init()`. // // After the globals are bound, we need to dispatch again so that events may be sent to the newly // created objects. event_queue.roundtrip(&mut list_outputs)?; // Now our outputs have been initialized with data, we may access what outputs exist and information about // said outputs using the output delegate. for output in list_outputs.output_state.outputs() { print_output( &list_outputs .output_state .info(&output) .ok_or_else(|| "output has no info".to_owned())?, ); } Ok(()) } /// Application data. /// /// This type is where the delegates for some parts of the protocol and any application specific data will /// live. struct ListOutputs { registry_state: RegistryState, output_state: OutputState, } // In order to use OutputDelegate, we must implement this trait to indicate when something has happened to an // output and to provide an instance of the output state to the delegate when dispatching events. impl OutputHandler for ListOutputs { // First we need to provide a way to access the delegate. // // This is needed because delegate implementations for handling events use the application data type in // their function signatures. This allows the implementation to access an instance of the type. fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } // Then there exist these functions that indicate the lifecycle of an output. // These will be called as appropriate by the delegate implementation. fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } // Now we need to say we are delegating the responsibility of output related events for our application data // type to the requisite delegate. delegate_output!(ListOutputs); // In order for our delegate to know of the existence of globals, we need to implement registry // handling for the program. This trait will forward events to the RegistryHandler trait // implementations. delegate_registry!(ListOutputs); // In order for delegate_registry to work, our application data type needs to provide a way for the // implementation to access the registry state. // // We also need to indicate which delegates will get told about globals being created. We specify // the types of the delegates inside the array. impl ProvidesRegistryState for ListOutputs { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers! { // Here we specify that OutputState needs to receive events regarding the creation and destruction of // globals. OutputState, } } /// Prints some [`OutputInfo`]. fn print_output(info: &OutputInfo) { println!("{}", info.model); if let Some(name) = info.name.as_ref() { println!("\tname: {name}"); } if let Some(description) = info.description.as_ref() { println!("\tdescription: {description}"); } println!("\tmake: {}", info.make); println!("\tx: {}, y: {}", info.location.0, info.location.1); println!("\tsubpixel: {:?}", info.subpixel); println!("\tphysical_size: {}×{}mm", info.physical_size.0, info.physical_size.1); if let Some((x, y)) = info.logical_position.as_ref() { println!("\tlogical x: {x}, y: {y}"); } if let Some((width, height)) = info.logical_size.as_ref() { println!("\tlogical width: {width}, height: {height}"); } println!("\tmodes:"); for mode in &info.modes { println!("\t\t{mode}"); } } smithay-client-toolkit-0.18.0/examples/list_seats.rs000064400000000000000000000036561046102023000206770ustar 00000000000000use smithay_client_toolkit::{ delegate_registry, delegate_seat, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{Capability, SeatHandler, SeatState}, }; use wayland_client::{globals::registry_queue_init, protocol::wl_seat, Connection, QueueHandle}; fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut list_seats = ListSeats { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), }; event_queue.blocking_dispatch(&mut list_seats).unwrap(); println!("Available seats:"); for seat in list_seats.seat_state.seats() { if let Some(info) = list_seats.seat_state.info(&seat) { println!("{info}"); } } } struct ListSeats { seat_state: SeatState, registry_state: RegistryState, } impl SeatHandler for ListSeats { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) { // Not applicable } fn new_capability( &mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, _: Capability, ) { // Not applicable } fn remove_capability( &mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, _: Capability, ) { // Not applicable } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) { // Not applicable } } delegate_seat!(ListSeats); delegate_registry!(ListSeats); impl ProvidesRegistryState for ListSeats { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers!(SeatState); } smithay-client-toolkit-0.18.0/examples/list_shm_formats.rs000064400000000000000000000033331046102023000220720ustar 00000000000000/// Example app showing how to use delegate types from Smithay's client toolkit and initializing state. use smithay_client_toolkit::{ delegate_shm, shm::{Shm, ShmHandler}, }; use wayland_client::{ globals::{registry_queue_init, GlobalListContents}, protocol::wl_registry, Connection, Dispatch, QueueHandle, }; struct ListShmFormats { shm_state: Shm, } fn main() { // Initialize logging for Rust backend. env_logger::init(); // Connect to the compositor. let conn = Connection::connect_to_env().unwrap(); // Create an event queue and get the initial global list. let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // Create the state to dispatch. let mut list_formats = ListShmFormats { // Bind ShmState to implement wl_shm handling. shm_state: Shm::bind(&globals, &qh).expect("wl_shm is not available"), }; // Roundtrip to get the supported wl_shm formats. event_queue.roundtrip(&mut list_formats).unwrap(); println!("Supported formats:"); for format in list_formats.shm_state.formats() { println!("{format:?}"); } } impl ShmHandler for ListShmFormats { fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } } // Delegate handling of the wl_shm protocol to our state object. delegate_shm!(ListShmFormats); impl Dispatch for ListShmFormats { fn event( _state: &mut Self, _registry: &wl_registry::WlRegistry, _event: wl_registry::Event, _data: &GlobalListContents, _conn: &Connection, _qh: &QueueHandle, ) { // We don't need any other globals. } } smithay-client-toolkit-0.18.0/examples/relative_pointer.rs000064400000000000000000000422701046102023000220730ustar 00000000000000//! An example demonstrating relative pointer and (if supported) pointer constraints use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_output, delegate_pointer, delegate_pointer_constraints, delegate_registry, delegate_relative_pointer, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, globals::ProvidesBoundGlobal, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ pointer::{PointerEvent, PointerEventKind, PointerHandler}, pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState}, relative_pointer::{RelativeMotionEvent, RelativePointerHandler, RelativePointerState}, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_output, wl_pointer, wl_region, wl_seat, wl_shm, wl_surface}, Connection, Dispatch, QueueHandle, }; use wayland_protocols::wp::{ pointer_constraints::zv1::client::{ zwp_confined_pointer_v1, zwp_locked_pointer_v1, zwp_pointer_constraints_v1, }, relative_pointer::zv1::client::zwp_relative_pointer_v1, }; const WHITE: raqote::SolidSource = raqote::SolidSource { r: 255, g: 255, b: 255, a: 255 }; const BLACK: raqote::SolidSource = raqote::SolidSource { r: 0, g: 0, b: 0, a: 255 }; const GREY: raqote::SolidSource = raqote::SolidSource { r: 192, g: 192, b: 192, a: 255 }; const SOLID_WHITE: raqote::Source = raqote::Source::Solid(WHITE); const SOLID_BLACK: raqote::Source = raqote::Source::Solid(BLACK); const SPEED: f32 = 0.001; enum Constraint { Confine(zwp_confined_pointer_v1::ZwpConfinedPointerV1), ConfineRegion(zwp_confined_pointer_v1::ZwpConfinedPointerV1), Lock(zwp_locked_pointer_v1::ZwpLockedPointerV1), } fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let font = font_kit::source::SystemSource::new() .select_best_match( &[font_kit::family_name::FamilyName::SansSerif], &font_kit::properties::Properties::new(), ) .unwrap() .load() .unwrap(); let mut simple_window = SimpleWindow { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), compositor_state: CompositorState::bind(&globals, &qh) .expect("wl_compositor not available"), shm_state: Shm::bind(&globals, &qh).expect("wl_shm not available"), xdg_shell_state: XdgShell::bind(&globals, &qh).expect("xdg shell not available"), relative_pointer_state: RelativePointerState::bind(&globals, &qh), pointer_constraint_state: PointerConstraintsState::bind(&globals, &qh), exit: false, width: 256, height: 256, window: None, pointer: None, relative_pointer: None, constraint: None, constraint_active: false, pos: (0.5, 0.5), font, }; let surface = simple_window.compositor_state.create_surface(&qh); let window = simple_window.xdg_shell_state.create_window(surface, WindowDecorations::ServerDefault, &qh); window.set_title("A wayland window"); window.set_app_id("io.github.smithay.client-toolkit.RelativePointer"); window.set_min_size(Some((256, 256))); window.commit(); simple_window.window = Some(window); while !simple_window.exit { event_queue.blocking_dispatch(&mut simple_window).unwrap(); } } struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, compositor_state: CompositorState, shm_state: Shm, xdg_shell_state: XdgShell, relative_pointer_state: RelativePointerState, pointer_constraint_state: PointerConstraintsState, exit: bool, width: u32, height: u32, window: Option, pointer: Option, relative_pointer: Option, constraint: Option, constraint_active: bool, pos: (f32, f32), font: font_kit::loaders::freetype::Font, } impl CompositorHandler for SimpleWindow { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for SimpleWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for SimpleWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256); self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256); if let Some(Constraint::ConfineRegion(confine)) = &self.constraint { let region = self.compositor_state.wl_compositor().create_region(qh, ()); region.add( self.width as i32 / 4, self.height as i32 / 4, self.width as i32 / 2, self.height as i32 / 2, ); confine.set_region(Some(®ion)); } self.draw(conn, qh); } } impl SeatHandler for SimpleWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Pointer && self.pointer.is_none() { println!("Set pointer capability"); let pointer = self.seat_state.get_pointer(qh, &seat).expect("Failed to create pointer"); let relative_pointer = self.relative_pointer_state.get_relative_pointer(&pointer, qh).ok(); if relative_pointer.is_some() { println!("Created relative pointer"); } else { println!("Compositor does not support relative pointer events"); } self.pointer = Some(pointer); self.relative_pointer = relative_pointer; } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Pointer && self.pointer.is_some() { println!("Unset pointer capability"); self.pointer.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl PointerHandler for SimpleWindow { fn pointer_frame( &mut self, conn: &Connection, qh: &QueueHandle, _pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { for event in events { if let PointerEventKind::Release { .. } = event.kind { self.change_constraint(conn, qh); } } } } impl PointerConstraintsHandler for SimpleWindow { fn confined( &mut self, _conn: &Connection, _qh: &QueueHandle, _confined_pointer: &zwp_confined_pointer_v1::ZwpConfinedPointerV1, _surface: &wl_surface::WlSurface, _pointer: &wl_pointer::WlPointer, ) { println!("Confined"); self.constraint_active = true; } fn unconfined( &mut self, _conn: &Connection, _qh: &QueueHandle, _confined_pointer: &zwp_confined_pointer_v1::ZwpConfinedPointerV1, _surface: &wl_surface::WlSurface, _pointer: &wl_pointer::WlPointer, ) { println!("Unconfined"); self.constraint_active = false; } fn locked( &mut self, _conn: &Connection, _qh: &QueueHandle, _locked_pointer: &zwp_locked_pointer_v1::ZwpLockedPointerV1, _surface: &wl_surface::WlSurface, _pointer: &wl_pointer::WlPointer, ) { println!("Locked"); self.constraint_active = false; self.constraint_active = true; } fn unlocked( &mut self, _conn: &Connection, _qh: &QueueHandle, _locked_pointer: &zwp_locked_pointer_v1::ZwpLockedPointerV1, _surface: &wl_surface::WlSurface, _pointer: &wl_pointer::WlPointer, ) { println!("Unlocked"); self.constraint_active = false; } } impl RelativePointerHandler for SimpleWindow { fn relative_pointer_motion( &mut self, _conn: &Connection, _qh: &QueueHandle, _relative_pointer: &zwp_relative_pointer_v1::ZwpRelativePointerV1, _pointer: &wl_pointer::WlPointer, event: RelativeMotionEvent, ) { println!("{event:?}"); self.pos.0 = (self.pos.0 + event.delta.0 as f32 * SPEED).rem_euclid(1.); self.pos.1 = (self.pos.1 + event.delta.1 as f32 * SPEED).rem_euclid(1.); } } impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } } impl SimpleWindow { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { if let Some(window) = self.window.as_ref() { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let mut pool = SlotPool::new(width as usize * height as usize * 4, &self.shm_state) .expect("Failed to create pool"); let buffer = pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Xrgb8888) .expect("create buffer") .0; let mut dt = raqote::DrawTarget::from_backing( width as i32, height as i32, bytemuck::cast_slice_mut(pool.canvas(&buffer).unwrap()), ); if let Some(Constraint::ConfineRegion(_)) = &self.constraint { dt.clear(GREY); dt.fill_rect( (width / 4) as f32, (height / 4) as f32, (width / 2) as f32, (height / 2) as f32, &SOLID_WHITE, &raqote::DrawOptions::new(), ); } else { dt.clear(WHITE); } let mut pb = raqote::PathBuilder::new(); pb.arc( self.pos.0 * self.width as f32, self.pos.1 * self.height as f32, 5., 0., 2. * std::f32::consts::PI, ); pb.close(); dt.stroke( &pb.finish(), &SOLID_BLACK, &raqote::StrokeStyle::default(), &raqote::DrawOptions::new(), ); dt.draw_text( &self.font, 14., self.constraint_label(), raqote::Point::new(2., 16.), &SOLID_BLACK, &raqote::DrawOptions::new(), ); dt.draw_text( &self.font, 14., "Click to change mode", raqote::Point::new(2., 32.), &SOLID_BLACK, &raqote::DrawOptions::new(), ); // Damage the entire window window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); // Request our next frame window.wl_surface().frame(qh, window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(window.wl_surface()).expect("buffer attach"); window.wl_surface().commit(); } } // Text for label describing current pointer constraint mode fn constraint_label(&self) -> &str { if self.pointer_constraint_state.bound_global().is_err() { return "Pointer constraints not supported by compositor"; } match &self.constraint { None => "Pointer unconstrained", Some(Constraint::Confine(_)) => { if self.constraint_active { "Pointer confined to window" } else { "Pointer confined to window (inactive)" } } Some(Constraint::ConfineRegion(_)) => { if self.constraint_active { "Pointer confined to region" } else { "Pointer confined to region (inactive)" } } Some(Constraint::Lock(_)) => { if self.constraint_active { "Pointer locked in place" } else { "Pointer locked in place (inactive)" } } } } // Swap between constraint modes fn change_constraint(&mut self, conn: &Connection, qh: &QueueHandle) { if self.pointer_constraint_state.bound_global().is_err() { return; } let pointer = if let Some(pointer) = self.pointer.as_ref() { pointer } else { return; }; let surface = if let Some(window) = &self.window { window.wl_surface() } else { return; }; self.constraint = match self.constraint.take() { None => Some(Constraint::Confine( self.pointer_constraint_state .confine_pointer( surface, pointer, None, zwp_pointer_constraints_v1::Lifetime::Persistent, qh, ) .unwrap(), )), Some(Constraint::Confine(confine)) => { let region = self.compositor_state.wl_compositor().create_region(qh, ()); region.add( self.width as i32 / 4, self.height as i32 / 4, self.width as i32 / 2, self.height as i32 / 2, ); confine.set_region(Some(®ion)); Some(Constraint::ConfineRegion(confine)) } Some(Constraint::ConfineRegion(confine)) => { confine.destroy(); Some(Constraint::Lock( self.pointer_constraint_state .lock_pointer( surface, pointer, None, zwp_pointer_constraints_v1::Lifetime::Persistent, qh, ) .unwrap(), )) } Some(Constraint::Lock(lock)) => { lock.destroy(); None } }; self.draw(conn, qh); } } delegate_compositor!(SimpleWindow); delegate_output!(SimpleWindow); delegate_shm!(SimpleWindow); delegate_seat!(SimpleWindow); delegate_pointer!(SimpleWindow); delegate_pointer_constraints!(SimpleWindow); delegate_relative_pointer!(SimpleWindow); delegate_xdg_shell!(SimpleWindow); delegate_xdg_window!(SimpleWindow); delegate_registry!(SimpleWindow); impl ProvidesRegistryState for SimpleWindow { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState,]; } impl Dispatch for SimpleWindow { fn event( _: &mut Self, _: &wl_region::WlRegion, _: wl_region::Event, _: &(), _: &Connection, _: &QueueHandle, ) { } } smithay-client-toolkit-0.18.0/examples/simple_layer.rs000064400000000000000000000322171046102023000212050ustar 00000000000000//! This example is horrible. Please make a better one soon. use std::convert::TryInto; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers}, pointer::{PointerEvent, PointerEventKind, PointerHandler}, Capability, SeatHandler, SeatState, }, shell::{ wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, }, WaylandSurface, }, shm::{slot::SlotPool, Shm, ShmHandler}, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, Connection, QueueHandle, }; fn main() { env_logger::init(); // All Wayland apps start by connecting the compositor (server). let conn = Connection::connect_to_env().unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor is not available"); // This app uses the wlr layer shell, which may not be available with every compositor. let layer_shell = LayerShell::bind(&globals, &qh).expect("layer shell is not available"); // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer // we share with the compositor process. let shm = Shm::bind(&globals, &qh).expect("wl_shm is not available"); // A layer surface is created from a surface. let surface = compositor.create_surface(&qh); // And then we create the layer shell. let layer = layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None); // Configure the layer surface, providing things like the anchor on screen, desired size and the keyboard // interactivity layer.set_anchor(Anchor::BOTTOM); layer.set_keyboard_interactivity(KeyboardInteractivity::OnDemand); layer.set_size(256, 256); // In order for the layer surface to be mapped, we need to perform an initial commit with no attached\ // buffer. For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the layer // surface with the correct options. layer.commit(); // We don't know how large the window will be yet, so lets assume the minimum size we suggested for the // initial memory allocation. let pool = SlotPool::new(256 * 256 * 4, &shm).expect("Failed to create pool"); let mut simple_layer = SimpleLayer { // Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to // listen for seats and outputs. registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), shm, exit: false, first_configure: true, pool, width: 256, height: 256, shift: None, layer, keyboard: None, keyboard_focus: false, pointer: None, }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut simple_layer).unwrap(); if simple_layer.exit { println!("exiting example"); break; } } } struct SimpleLayer { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm: Shm, exit: bool, first_configure: bool, pool: SlotPool, width: u32, height: u32, shift: Option, layer: LayerSurface, keyboard: Option, keyboard_focus: bool, pointer: Option, } impl CompositorHandler for SimpleLayer { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, _conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(qh); } } impl OutputHandler for SimpleLayer { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl LayerShellHandler for SimpleLayer { fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle, _layer: &LayerSurface) { self.exit = true; } fn configure( &mut self, _conn: &Connection, qh: &QueueHandle, _layer: &LayerSurface, configure: LayerSurfaceConfigure, _serial: u32, ) { if configure.new_size.0 == 0 || configure.new_size.1 == 0 { self.width = 256; self.height = 256; } else { self.width = configure.new_size.0; self.height = configure.new_size.1; } // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(qh); } } } impl SeatHandler for SimpleLayer { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { println!("Set keyboard capability"); let keyboard = self.seat_state.get_keyboard(qh, &seat, None).expect("Failed to create keyboard"); self.keyboard = Some(keyboard); } if capability == Capability::Pointer && self.pointer.is_none() { println!("Set pointer capability"); let pointer = self.seat_state.get_pointer(qh, &seat).expect("Failed to create pointer"); self.pointer = Some(pointer); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } if capability == Capability::Pointer && self.pointer.is_some() { println!("Unset pointer capability"); self.pointer.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for SimpleLayer { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], keysyms: &[Keysym], ) { if self.layer.wl_surface() == surface { println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.layer.wl_surface() == surface { println!("Release keyboard focus on window"); self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key press: {event:?}"); // press 'esc' to exit if event.keysym == Keysym::Escape { self.exit = true; } } fn release_key( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key release: {event:?}"); } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _serial: u32, modifiers: Modifiers, ) { println!("Update modifiers: {modifiers:?}"); } } impl PointerHandler for SimpleLayer { fn pointer_frame( &mut self, _conn: &Connection, _qh: &QueueHandle, _pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { // Ignore events for other surfaces if &event.surface != self.layer.wl_surface() { continue; } match event.kind { Enter { .. } => { println!("Pointer entered @{:?}", event.position); } Leave { .. } => { println!("Pointer left"); } Motion { .. } => {} Press { button, .. } => { println!("Press {:x} @ {:?}", button, event.position); self.shift = self.shift.xor(Some(0)); } Release { button, .. } => { println!("Release {:x} @ {:?}", button, event.position); } Axis { horizontal, vertical, .. } => { println!("Scroll H:{horizontal:?}, V:{vertical:?}"); } } } } } impl ShmHandler for SimpleLayer { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl SimpleLayer { pub fn draw(&mut self, qh: &QueueHandle) { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let (buffer, canvas) = self .pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer"); // Draw to the window: { let shift = self.shift.unwrap_or(0); canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { let x = ((index + shift as usize) % width as usize) as u32; let y = (index / width as usize) as u32; let a = 0xFF; let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); let color = (a << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); if let Some(shift) = &mut self.shift { *shift = (*shift + 1) % width; } } // Damage the entire window self.layer.wl_surface().damage_buffer(0, 0, width as i32, height as i32); // Request our next frame self.layer.wl_surface().frame(qh, self.layer.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.layer.wl_surface()).expect("buffer attach"); self.layer.commit(); // TODO save and reuse buffer when the window size is unchanged. This is especially // useful if you do damage tracking, since you don't need to redraw the undamaged parts // of the canvas. } } delegate_compositor!(SimpleLayer); delegate_output!(SimpleLayer); delegate_shm!(SimpleLayer); delegate_seat!(SimpleLayer); delegate_keyboard!(SimpleLayer); delegate_pointer!(SimpleLayer); delegate_layer!(SimpleLayer); delegate_registry!(SimpleLayer); impl ProvidesRegistryState for SimpleLayer { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState]; } smithay-client-toolkit-0.18.0/examples/simple_window.rs000064400000000000000000000351121046102023000213750ustar 00000000000000use std::{convert::TryInto, time::Duration}; use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle}; use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers}, pointer::{PointerEvent, PointerEventKind, PointerHandler}, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, shm::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, Connection, QueueHandle, }; fn main() { env_logger::init(); // All Wayland apps start by connecting the compositor (server). let conn = Connection::connect_to_env().unwrap(); // Enumerate the list of globals to get the protocols the server implements. let (globals, event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); let loop_handle = event_loop.handle(); WaylandSource::new(conn.clone(), event_queue).insert(loop_handle).unwrap(); // The compositor (not to be confused with the server which is commonly called the compositor) allows // configuring surfaces to be presented. let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); // For desktop platforms, the XDG shell is the standard protocol for creating desktop windows. let xdg_shell = XdgShell::bind(&globals, &qh).expect("xdg shell is not available"); // Since we are not using the GPU in this example, we use wl_shm to allow software rendering to a buffer // we share with the compositor process. let shm = Shm::bind(&globals, &qh).expect("wl shm is not available."); // A window is created from a surface. let surface = compositor.create_surface(&qh); // And then we can create the window. let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh); // Configure the window, this may include hints to the compositor about the desired minimum size of the // window, app id for WM identification, the window title, etc. window.set_title("A wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.SimpleWindow"); window.set_min_size(Some((256, 256))); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); // We don't know how large the window will be yet, so lets assume the minimum size we suggested for the // initial memory allocation. let pool = SlotPool::new(256 * 256 * 4, &shm).expect("Failed to create pool"); let mut simple_window = SimpleWindow { // Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to // listen for seats and outputs. registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), shm, exit: false, first_configure: true, pool, width: 256, height: 256, shift: None, buffer: None, window, keyboard: None, keyboard_focus: false, pointer: None, loop_handle: event_loop.handle(), }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_loop.dispatch(Duration::from_millis(16), &mut simple_window).unwrap(); if simple_window.exit { println!("exiting example"); break; } } } struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm: Shm, exit: bool, first_configure: bool, pool: SlotPool, width: u32, height: u32, shift: Option, buffer: Option, window: Window, keyboard: Option, keyboard_focus: bool, pointer: Option, loop_handle: LoopHandle<'static, SimpleWindow>, } impl CompositorHandler for SimpleWindow { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for SimpleWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for SimpleWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { println!("Window configured to: {:?}", configure); self.buffer = None; self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256); self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256); // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(conn, qh); } } } impl SeatHandler for SimpleWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { println!("Set keyboard capability"); let keyboard = self .seat_state .get_keyboard_with_repeat( qh, &seat, None, self.loop_handle.clone(), Box::new(|_state, _wl_kbd, event| { println!("Repeat: {:?} ", event); }), ) .expect("Failed to create keyboard"); self.keyboard = Some(keyboard); } if capability == Capability::Pointer && self.pointer.is_none() { println!("Set pointer capability"); let pointer = self.seat_state.get_pointer(qh, &seat).expect("Failed to create pointer"); self.pointer = Some(pointer); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } if capability == Capability::Pointer && self.pointer.is_some() { println!("Unset pointer capability"); self.pointer.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for SimpleWindow { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], keysyms: &[Keysym], ) { if self.window.wl_surface() == surface { println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.window.wl_surface() == surface { println!("Release keyboard focus on window"); self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key press: {event:?}"); } fn release_key( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { println!("Key release: {event:?}"); } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _serial: u32, modifiers: Modifiers, ) { println!("Update modifiers: {modifiers:?}"); } } impl PointerHandler for SimpleWindow { fn pointer_frame( &mut self, _conn: &Connection, _qh: &QueueHandle, _pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { // Ignore events for other surfaces if &event.surface != self.window.wl_surface() { continue; } match event.kind { Enter { .. } => { println!("Pointer entered @{:?}", event.position); } Leave { .. } => { println!("Pointer left"); } Motion { .. } => {} Press { button, .. } => { println!("Press {:x} @ {:?}", button, event.position); self.shift = self.shift.xor(Some(0)); } Release { button, .. } => { println!("Release {:x} @ {:?}", button, event.position); } Axis { horizontal, vertical, .. } => { println!("Scroll H:{horizontal:?}, V:{vertical:?}"); } } } } } impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl SimpleWindow { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let buffer = self.buffer.get_or_insert_with(|| { self.pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match self.pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = self .pool .create_buffer( self.width as i32, self.height as i32, stride, wl_shm::Format::Argb8888, ) .expect("create buffer"); *buffer = second_buffer; canvas } }; // Draw to the window: { let shift = self.shift.unwrap_or(0); canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { let x = ((index + shift as usize) % width as usize) as u32; let y = (index / width as usize) as u32; let a = 0xFF; let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); let color = (a << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); if let Some(shift) = &mut self.shift { *shift = (*shift + 1) % width; } } // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); // Request our next frame self.window.wl_surface().frame(qh, self.window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.window.wl_surface()).expect("buffer attach"); self.window.commit(); } } delegate_compositor!(SimpleWindow); delegate_output!(SimpleWindow); delegate_shm!(SimpleWindow); delegate_seat!(SimpleWindow); delegate_keyboard!(SimpleWindow); delegate_pointer!(SimpleWindow); delegate_xdg_shell!(SimpleWindow); delegate_xdg_window!(SimpleWindow); delegate_registry!(SimpleWindow); impl ProvidesRegistryState for SimpleWindow { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState,]; } smithay-client-toolkit-0.18.0/examples/themed_window.rs000064400000000000000000000521421046102023000213540ustar 00000000000000use std::sync::Arc; use std::time::Duration; use std::{convert::TryInto, num::NonZeroU32}; use smithay_client_toolkit::reexports::client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, Connection, Proxy, QueueHandle, }; use smithay_client_toolkit::reexports::csd_frame::{ DecorationsFrame, FrameAction, FrameClick, ResizeEdge, }; use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, delegate_subcompositor, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers}, pointer::{ CursorIcon, PointerData, PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer, }, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ fallback_frame::FallbackFrame, window::{DecorationMode, Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, XdgSurface, }, WaylandSurface, }, shm::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, subcompositor::SubcompositorState, }; // Cursor shapes. 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, ]; fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); let registry_state = RegistryState::new(&globals); let seat_state = SeatState::new(&globals, &qh); let output_state = OutputState::new(&globals, &qh); let compositor_state = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); let subcompositor_state = SubcompositorState::bind(compositor_state.wl_compositor().clone(), &globals, &qh) .expect("wl_subcompositor not available"); let shm_state = Shm::bind(&globals, &qh).expect("wl_shm not available"); let xdg_shell_state = XdgShell::bind(&globals, &qh).expect("xdg shell not available"); let width = NonZeroU32::new(256).unwrap(); let height = NonZeroU32::new(256).unwrap(); let pool = SlotPool::new(width.get() as usize * height.get() as usize * 4, &shm_state) .expect("Failed to create pool"); let window_surface = compositor_state.create_surface(&qh); let window = xdg_shell_state.create_window(window_surface, WindowDecorations::ServerDefault, &qh); window.set_title("A wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.SimpleWindow"); window.set_min_size(Some((width.get(), height.get()))); // In order for the window to be mapped, we need to perform an initial commit with no attached buffer. // For more info, see WaylandSurface::commit // // The compositor will respond with an initial configure that we can then use to present to the window with // the correct options. window.commit(); println!("Press `n` to cycle through cursor icons."); let mut simple_window = SimpleWindow { registry_state, seat_state, output_state, compositor_state, subcompositor_state: Arc::new(subcompositor_state), shm_state, _xdg_shell_state: xdg_shell_state, exit: false, first_configure: true, pool, width, height, shift: None, buffer: None, window, window_frame: None, keyboard: None, keyboard_focus: false, themed_pointer: None, set_cursor: false, window_cursor_icon_idx: 0, decorations_cursor: None, }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut simple_window).unwrap(); if simple_window.exit { println!("Exiting example."); break; } } } struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, compositor_state: CompositorState, subcompositor_state: Arc, shm_state: Shm, _xdg_shell_state: XdgShell, exit: bool, first_configure: bool, pool: SlotPool, width: NonZeroU32, height: NonZeroU32, shift: Option, buffer: Option, window: Window, window_frame: Option>, keyboard: Option, keyboard_focus: bool, themed_pointer: Option, set_cursor: bool, window_cursor_icon_idx: usize, decorations_cursor: Option, } impl CompositorHandler for SimpleWindow { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for SimpleWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for SimpleWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, window: &Window, configure: WindowConfigure, _serial: u32, ) { self.buffer = None; println!( "Configure size {:?}, decorations: {:?}", configure.new_size, configure.decoration_mode ); let (width, height) = if configure.decoration_mode == DecorationMode::Client { let window_frame = self.window_frame.get_or_insert_with(|| { FallbackFrame::new( &self.window, &self.shm_state, self.subcompositor_state.clone(), qh.clone(), ) .expect("failed to create client side decorations frame.") }); // Un-hide the frame. window_frame.set_hidden(false); // Configure state before touching any resizing. window_frame.update_state(configure.state); // Update the capabilities. window_frame.update_wm_capabilities(configure.capabilities); let (width, height) = match configure.new_size { (Some(width), Some(height)) => { // The size could be 0. window_frame.subtract_borders(width, height) } _ => { // You might want to consider checking for configure bounds. (Some(self.width), Some(self.height)) } }; // Clamp the size to at least one pixel. let width = width.unwrap_or(NonZeroU32::new(1).unwrap()); let height = height.unwrap_or(NonZeroU32::new(1).unwrap()); println!("New dimentions: {width}, {height}"); window_frame.resize(width, height); let (x, y) = window_frame.location(); let outer_size = window_frame.add_borders(width.get(), height.get()); window.xdg_surface().set_window_geometry( x, y, outer_size.0 as i32, outer_size.1 as i32, ); (width, height) } else { // Hide the frame, if any. if let Some(frame) = self.window_frame.as_mut() { frame.set_hidden(true) } let width = configure.new_size.0.unwrap_or(self.width); let height = configure.new_size.1.unwrap_or(self.height); self.window.xdg_surface().set_window_geometry( 0, 0, width.get() as i32, height.get() as i32, ); (width, height) }; // Update new width and height; self.width = width; self.height = height; // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(conn, qh); } } } impl SeatHandler for SimpleWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { println!("Set keyboard capability"); let keyboard = self.seat_state.get_keyboard(qh, &seat, None).expect("Failed to create keyboard"); self.keyboard = Some(keyboard); } if capability == Capability::Pointer && self.themed_pointer.is_none() { println!("Set pointer capability"); let surface = self.compositor_state.create_surface(qh); let themed_pointer = self .seat_state .get_pointer_with_theme( qh, &seat, self.shm_state.wl_shm(), surface, ThemeSpec::default(), ) .expect("Failed to create pointer"); self.themed_pointer.replace(themed_pointer); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } if capability == Capability::Pointer && self.themed_pointer.is_some() { println!("Unset pointer capability"); self.themed_pointer.take().unwrap().pointer().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for SimpleWindow { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], keysyms: &[Keysym], ) { if self.window.wl_surface() == surface { println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.window.wl_surface() == surface { self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { if event.keysym == Keysym::N { // Cycle through cursor icons. self.window_cursor_icon_idx = (self.window_cursor_icon_idx + 1) % CURSORS.len(); println!("Setting cursor icon to: {}", CURSORS[self.window_cursor_icon_idx].name()); self.set_cursor = true; } } fn release_key( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, _: KeyEvent, ) { } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _serial: u32, _: Modifiers, ) { } } impl PointerHandler for SimpleWindow { fn pointer_frame( &mut self, _conn: &Connection, _qh: &QueueHandle, pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { let (x, y) = event.position; match event.kind { Enter { .. } => { self.set_cursor = true; self.decorations_cursor = self.window_frame.as_mut().and_then(|frame| { frame.click_point_moved(Duration::ZERO, &event.surface.id(), x, y) }); } Leave { .. } => { if &event.surface != self.window.wl_surface() { if let Some(window_frame) = self.window_frame.as_mut() { window_frame.click_point_left(); } } } Motion { time } => { if let Some(new_cursor) = self.window_frame.as_mut().and_then(|frame| { frame.click_point_moved( Duration::from_millis(time as u64), &event.surface.id(), x, y, ) }) { self.set_cursor = true; self.decorations_cursor = Some(new_cursor); } } Press { button, serial, time } | Release { button, serial, time } => { let pressed = matches!(event.kind, Press { .. }); if &event.surface != self.window.wl_surface() { let click = match button { 0x110 => FrameClick::Normal, 0x111 => FrameClick::Alternate, _ => continue, }; if let Some(action) = self.window_frame.as_mut().and_then(|frame| { frame.on_click(Duration::from_millis(time as u64), click, pressed) }) { self.frame_action(pointer, serial, action); } } else if pressed { self.shift = self.shift.xor(Some(0)); } } Axis { .. } => {} } } } } impl SimpleWindow { fn frame_action(&mut self, pointer: &wl_pointer::WlPointer, serial: u32, action: FrameAction) { let pointer_data = pointer.data::().unwrap(); let seat = pointer_data.seat(); match action { FrameAction::Close => self.exit = true, FrameAction::Minimize => self.window.set_minimized(), FrameAction::Maximize => self.window.set_maximized(), FrameAction::UnMaximize => self.window.unset_maximized(), FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), 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, }; self.window.resize(seat, serial, edge); } FrameAction::Move => self.window.move_(seat, serial), _ => (), } } } impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm_state } } impl SimpleWindow { pub fn draw(&mut self, conn: &Connection, qh: &QueueHandle) { if self.set_cursor { let cursor_icon = self.decorations_cursor.unwrap_or(CURSORS[self.window_cursor_icon_idx]); let _ = self.themed_pointer.as_mut().unwrap().set_cursor(conn, cursor_icon); self.set_cursor = false; } let width = self.width.get(); let height = self.height.get(); let stride = self.width.get() as i32 * 4; let buffer = self.buffer.get_or_insert_with(|| { self.pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match self.pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = self .pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer"); *buffer = second_buffer; canvas } }; // Draw to the window: { let shift = self.shift.unwrap_or(0); canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| { let x = ((index + shift as usize) % width as usize) as u32; let y = (index / width as usize) as u32; let a = 0xFF; let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height); let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height); let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height); let color = (a << 24) + (r << 16) + (g << 8) + b; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); if let Some(shift) = &mut self.shift { *shift = (*shift + 1) % width; } } // Draw the decorations frame. if let Some(frame) = self.window_frame.as_mut() { if frame.is_dirty() && !frame.is_hidden() { frame.draw(); } } // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, width as i32, height as i32); // Request our next frame self.window.wl_surface().frame(qh, self.window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.window.wl_surface()).expect("buffer attach"); self.window.wl_surface().commit(); } } delegate_compositor!(SimpleWindow); delegate_subcompositor!(SimpleWindow); delegate_output!(SimpleWindow); delegate_shm!(SimpleWindow); delegate_seat!(SimpleWindow); delegate_keyboard!(SimpleWindow); delegate_pointer!(SimpleWindow); delegate_xdg_shell!(SimpleWindow); delegate_xdg_window!(SimpleWindow); delegate_registry!(SimpleWindow); impl ProvidesRegistryState for SimpleWindow { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState, SeatState,]; } smithay-client-toolkit-0.18.0/examples/wgpu.rs000064400000000000000000000220011046102023000174700ustar 00000000000000use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, }; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{Capability, SeatHandler, SeatState}, shell::{ xdg::{ window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, XdgShell, }, WaylandSurface, }, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_output, wl_seat, wl_surface}, Connection, Proxy, QueueHandle, }; fn main() { env_logger::init(); let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let qh = event_queue.handle(); // Initialize xdg_shell handlers so we can select the correct adapter let compositor_state = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); let xdg_shell_state = XdgShell::bind(&globals, &qh).expect("xdg shell not available"); let surface = compositor_state.create_surface(&qh); // Create the window for adapter selection let window = xdg_shell_state.create_window(surface, WindowDecorations::ServerDefault, &qh); window.set_title("wgpu wayland window"); // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine. window.set_app_id("io.github.smithay.client-toolkit.WgpuExample"); window.set_min_size(Some((256, 256))); window.commit(); // Initialize wgpu let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::all(), ..Default::default() }); // Create the raw window handle for the surface. let handle = { let mut handle = WaylandDisplayHandle::empty(); handle.display = conn.backend().display_ptr() as *mut _; let display_handle = RawDisplayHandle::Wayland(handle); let mut handle = WaylandWindowHandle::empty(); handle.surface = window.wl_surface().id().as_ptr() as *mut _; let window_handle = RawWindowHandle::Wayland(handle); /// https://github.com/rust-windowing/raw-window-handle/issues/49 struct YesRawWindowHandleImplementingHasRawWindowHandleIsUnsound( RawDisplayHandle, RawWindowHandle, ); unsafe impl HasRawDisplayHandle for YesRawWindowHandleImplementingHasRawWindowHandleIsUnsound { fn raw_display_handle(&self) -> RawDisplayHandle { self.0 } } unsafe impl HasRawWindowHandle for YesRawWindowHandleImplementingHasRawWindowHandleIsUnsound { fn raw_window_handle(&self) -> RawWindowHandle { self.1 } } YesRawWindowHandleImplementingHasRawWindowHandleIsUnsound(display_handle, window_handle) }; let surface = unsafe { instance.create_surface(&handle).unwrap() }; // Pick a supported adapter let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { compatible_surface: Some(&surface), ..Default::default() })) .expect("Failed to find suitable adapter"); let (device, queue) = pollster::block_on(adapter.request_device(&Default::default(), None)) .expect("Failed to request device"); let mut wgpu = Wgpu { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh), exit: false, width: 256, height: 256, window, device, surface, adapter, queue, }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_queue.blocking_dispatch(&mut wgpu).unwrap(); if wgpu.exit { println!("exiting example"); break; } } // On exit we must destroy the surface before the window is destroyed. drop(wgpu.surface); drop(wgpu.window); } struct Wgpu { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, exit: bool, width: u32, height: u32, window: Window, adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, surface: wgpu::Surface, } impl CompositorHandler for Wgpu { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_factor: i32, ) { // Not needed for this example. } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { } } impl OutputHandler for Wgpu { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for Wgpu { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, _conn: &Connection, _qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { let (new_width, new_height) = configure.new_size; self.width = new_width.map_or(256, |v| v.get()); self.height = new_height.map_or(256, |v| v.get()); let adapter = &self.adapter; let surface = &self.surface; let device = &self.device; let queue = &self.queue; let cap = surface.get_capabilities(&adapter); let surface_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: cap.formats[0], view_formats: vec![cap.formats[0]], alpha_mode: wgpu::CompositeAlphaMode::Auto, width: self.width, height: self.height, // Wayland is inherently a mailbox system. present_mode: wgpu::PresentMode::Mailbox, }; surface.configure(&self.device, &surface_config); // We don't plan to render much in this example, just clear the surface. let surface_texture = surface.get_current_texture().expect("failed to acquire next swapchain texture"); let texture_view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = device.create_command_encoder(&Default::default()); { let _renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &texture_view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLUE), store: true, }, })], depth_stencil_attachment: None, }); } // Submit the command in the queue to execute queue.submit(Some(encoder.finish())); surface_texture.present(); } } impl SeatHandler for Wgpu { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, _qh: &QueueHandle, _seat: wl_seat::WlSeat, _capability: Capability, ) { } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, _capability: Capability, ) { } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } delegate_compositor!(Wgpu); delegate_output!(Wgpu); delegate_seat!(Wgpu); delegate_xdg_shell!(Wgpu); delegate_xdg_window!(Wgpu); delegate_registry!(Wgpu); impl ProvidesRegistryState for Wgpu { fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } registry_handlers![OutputState]; } smithay-client-toolkit-0.18.0/rustfmt.toml000064400000000000000000000001451046102023000167300ustar 00000000000000use_small_heuristics = "Max" use_field_init_shorthand = true newline_style = "Unix" edition = "2018" smithay-client-toolkit-0.18.0/src/compositor.rs000064400000000000000000000354731046102023000176760ustar 00000000000000use std::mem; use std::os::unix::io::OwnedFd; use std::sync::MutexGuard; use std::sync::{ atomic::{AtomicI32, Ordering}, Arc, Mutex, }; use wayland_client::{ globals::{BindError, GlobalList}, protocol::{ wl_callback, wl_compositor, wl_output, wl_region, wl_surface::{self, WlSurface}, }, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use crate::{ error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, output::{OutputData, OutputHandler, OutputState, ScaleWatcherHandle}, }; pub trait CompositorHandler: Sized { /// The surface has either been moved into or out of an output and the output has a different scale factor. fn scale_factor_changed( &mut self, conn: &Connection, qh: &QueueHandle, surface: &wl_surface::WlSurface, new_factor: i32, ); /// The surface has either been moved into or out of an output and the output has different transform. fn transform_changed( &mut self, conn: &Connection, qh: &QueueHandle, surface: &wl_surface::WlSurface, new_transform: wl_output::Transform, ); /// A frame callback has been completed. /// /// This function will be called after sending a [`WlSurface::frame`](wl_surface::WlSurface::frame) request /// and committing the surface. fn frame( &mut self, conn: &Connection, qh: &QueueHandle, surface: &wl_surface::WlSurface, time: u32, ); } pub trait SurfaceDataExt: Send + Sync { fn surface_data(&self) -> &SurfaceData; } impl SurfaceDataExt for SurfaceData { fn surface_data(&self) -> &SurfaceData { self } } #[derive(Debug)] pub struct CompositorState { wl_compositor: wl_compositor::WlCompositor, } impl CompositorState { pub fn bind( globals: &GlobalList, qh: &QueueHandle, ) -> Result where State: Dispatch + 'static, { let wl_compositor = globals.bind(qh, 1..=6, GlobalData)?; Ok(CompositorState { wl_compositor }) } pub fn wl_compositor(&self) -> &wl_compositor::WlCompositor { &self.wl_compositor } pub fn create_surface(&self, qh: &QueueHandle) -> wl_surface::WlSurface where D: Dispatch + 'static, { self.create_surface_with_data(qh, Default::default()) } pub fn create_surface_with_data( &self, qh: &QueueHandle, data: U, ) -> wl_surface::WlSurface where D: Dispatch + 'static, U: SurfaceDataExt + 'static, { self.wl_compositor.create_surface(qh, data) } } /// Data associated with a [`WlSurface`](wl_surface::WlSurface). #[derive(Debug)] pub struct SurfaceData { /// The scale factor of the output with the highest scale factor. pub(crate) scale_factor: AtomicI32, /// Parent surface used when creating subsurfaces. /// /// For top-level surfaces this is always `None`. pub(crate) parent_surface: Option, /// The inner mutable storage. inner: Mutex, } impl SurfaceData { /// Create a new surface that initially reports the given scale factor and parent. pub fn new(parent_surface: Option, scale_factor: i32) -> Self { Self { scale_factor: AtomicI32::new(scale_factor), parent_surface, inner: Default::default(), } } /// The scale factor of the output with the highest scale factor. pub fn scale_factor(&self) -> i32 { self.scale_factor.load(Ordering::Relaxed) } /// The suggest transform for the surface. pub fn transform(&self) -> wl_output::Transform { self.inner.lock().unwrap().transform } /// The parent surface used for this surface. /// /// The surface is `Some` for primarily for subsurfaces, /// since they must have a parent surface. pub fn parent_surface(&self) -> Option<&WlSurface> { self.parent_surface.as_ref() } /// The outputs the surface is currently inside. pub fn outputs(&self) -> impl Iterator { self.inner.lock().unwrap().outputs.clone().into_iter() } } impl Default for SurfaceData { fn default() -> Self { Self::new(None, 1) } } #[derive(Debug)] struct SurfaceDataInner { /// The transform of the given surface. transform: wl_output::Transform, /// The outputs the surface is currently inside. outputs: Vec, /// A handle to the OutputInfo callback that dispatches scale updates. watcher: Option, } impl Default for SurfaceDataInner { fn default() -> Self { Self { transform: wl_output::Transform::Normal, outputs: Vec::new(), watcher: None } } } /// An owned [`WlSurface`](wl_surface::WlSurface). /// /// This destroys the surface on drop. #[derive(Debug)] pub struct Surface(wl_surface::WlSurface); impl Surface { pub fn new( compositor: &impl ProvidesBoundGlobal, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { Self::with_data(compositor, qh, Default::default()) } pub fn with_data( compositor: &impl ProvidesBoundGlobal, qh: &QueueHandle, data: U, ) -> Result where D: Dispatch + 'static, U: Send + Sync + 'static, { Ok(Surface(compositor.bound_global()?.create_surface(qh, data))) } pub fn wl_surface(&self) -> &wl_surface::WlSurface { &self.0 } } impl From for Surface { fn from(surface: wl_surface::WlSurface) -> Self { Surface(surface) } } impl Drop for Surface { fn drop(&mut self) { self.0.destroy(); } } #[macro_export] macro_rules! delegate_compositor { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_compositor::WlCompositor: $crate::globals::GlobalData ] => $crate::compositor::CompositorState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_surface::WlSurface: $crate::compositor::SurfaceData ] => $crate::compositor::CompositorState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_callback::WlCallback: $crate::reexports::client::protocol::wl_surface::WlSurface ] => $crate::compositor::CompositorState ); }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, surface: [$($surface: ty),*$(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_compositor::WlCompositor: $crate::globals::GlobalData ] => $crate::compositor::CompositorState ); $( $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_surface::WlSurface: $surface ] => $crate::compositor::CompositorState ); )* $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_callback::WlCallback: $crate::reexports::client::protocol::wl_surface::WlSurface ] => $crate::compositor::CompositorState ); }; } impl Dispatch for CompositorState where D: Dispatch + CompositorHandler + OutputHandler + 'static, U: SurfaceDataExt + 'static, { fn event( state: &mut D, surface: &wl_surface::WlSurface, event: wl_surface::Event, data: &U, conn: &Connection, qh: &QueueHandle, ) { let data = data.surface_data(); let mut inner = data.inner.lock().unwrap(); match event { wl_surface::Event::Enter { output } => { inner.outputs.push(output); } wl_surface::Event::Leave { output } => { inner.outputs.retain(|o| o != &output); } wl_surface::Event::PreferredBufferScale { factor } => { let current_scale = data.scale_factor.load(Ordering::Relaxed); drop(inner); data.scale_factor.store(factor, Ordering::Relaxed); if current_scale != factor { state.scale_factor_changed(conn, qh, surface, factor); } return; } wl_surface::Event::PreferredBufferTransform { transform } => { // Only handle known values. if let WEnum::Value(transform) = transform { let old_transform = std::mem::replace(&mut inner.transform, transform); drop(inner); if old_transform != transform { state.transform_changed(conn, qh, surface, transform); } } return; } _ => unreachable!(), } // NOTE: with v6 we don't need any special handling of the scale factor, everything // was handled from the above, so return. if surface.version() >= 6 { return; } inner.watcher.get_or_insert_with(|| { // Avoid storing the WlSurface inside the closure as that would create a reference // cycle. Instead, store the ID and re-create the proxy. let id = surface.id(); OutputState::add_scale_watcher(state, move |state, conn, qh, _| { let id = id.clone(); if let Ok(surface) = wl_surface::WlSurface::from_id(conn, id) { if let Some(data) = surface.data::() { let data = data.surface_data(); let inner = data.inner.lock().unwrap(); dispatch_surface_state_updates(state, conn, qh, &surface, data, inner); } } }) }); dispatch_surface_state_updates(state, conn, qh, surface, data, inner); } } fn dispatch_surface_state_updates( state: &mut D, conn: &Connection, qh: &QueueHandle, surface: &WlSurface, data: &SurfaceData, mut inner: MutexGuard, ) where D: Dispatch + CompositorHandler + OutputHandler + 'static, U: SurfaceDataExt + 'static, { let current_scale = data.scale_factor.load(Ordering::Relaxed); let (factor, transform) = match inner .outputs .iter() .filter_map(|output| { output .data::() .map(|data| data.with_output_info(|info| (info.scale_factor, info.transform))) }) // NOTE: reduce will only work for more than 1 element, thus we map transform to normal // since we can't guess which one to use. With the exactly one output, the corrent // transform will be passed instead. .reduce(|acc, props| (acc.0.max(props.0), wl_output::Transform::Normal)) { None => return, Some(props) => props, }; data.scale_factor.store(factor, Ordering::Relaxed); let old_transform = mem::replace(&mut inner.transform, transform); // Drop the mutex before we send of any events. drop(inner); if factor != current_scale { state.scale_factor_changed(conn, qh, surface, factor); } if transform != old_transform { state.transform_changed(conn, qh, surface, transform); } } /// A trivial wrapper around a [`WlRegion`][wl_region::WlRegion]. /// /// This destroys the region on drop. #[derive(Debug)] pub struct Region(wl_region::WlRegion); impl Region { pub fn new( compositor: &impl ProvidesBoundGlobal, ) -> Result { compositor .bound_global() .map(|c| { c.send_constructor(wl_compositor::Request::CreateRegion {}, Arc::new(RegionData)) .unwrap_or_else(|_| Proxy::inert(c.backend().clone())) }) .map(Region) } pub fn add(&self, x: i32, y: i32, width: i32, height: i32) { self.0.add(x, y, width, height) } pub fn subtract(&self, x: i32, y: i32, width: i32, height: i32) { self.0.subtract(x, y, width, height) } pub fn wl_region(&self) -> &wl_region::WlRegion { &self.0 } } impl Drop for Region { fn drop(&mut self) { self.0.destroy() } } struct RegionData; impl wayland_client::backend::ObjectData for RegionData { fn event( self: Arc, _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message, ) -> Option> { unreachable!("wl_region has no events"); } fn destroyed(&self, _: wayland_client::backend::ObjectId) {} } impl Dispatch for CompositorState where D: Dispatch + CompositorHandler, { fn event( _: &mut D, _: &wl_compositor::WlCompositor, _: wl_compositor::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wl_compositor has no events") } } impl ProvidesBoundGlobal for CompositorState { fn bound_global(&self) -> Result { Ok(self.wl_compositor.clone()) } } impl Dispatch for CompositorState where D: Dispatch + CompositorHandler, { fn event( state: &mut D, _: &wl_callback::WlCallback, event: wl_callback::Event, surface: &wl_surface::WlSurface, conn: &Connection, qh: &QueueHandle, ) { match event { wl_callback::Event::Done { callback_data } => { state.frame(conn, qh, surface, callback_data); } _ => unreachable!(), } } } smithay-client-toolkit-0.18.0/src/data_device_manager/data_device.rs000064400000000000000000000207331046102023000236430ustar 00000000000000use std::{ ops::DerefMut, sync::{Arc, Mutex}, }; use crate::{ data_device_manager::data_offer::DataDeviceOffer, reexports::client::{ event_created_child, protocol::{ wl_data_device::{self, WlDataDevice}, wl_data_offer::{self, WlDataOffer}, wl_seat::WlSeat, }, Connection, Dispatch, Proxy, QueueHandle, }, }; use super::{ data_offer::{DataOfferData, DataOfferHandler, DragOffer, SelectionOffer}, DataDeviceManagerState, }; /// Handler trait for DataDevice events. /// /// The functions defined in this trait are called as DataDevice events are received from the compositor. pub trait DataDeviceHandler: Sized { // Introduces a new data offer // ASHLEY left out because the data offer will be introduced to the user once the type is known // either through the enter method or the selection method. // fn data_offer( // &mut self, // conn: &Connection, // qh: &QueueHandle, // data_device: DataDevice, // offer: WlDataOffer, // ); /// The data device pointer has entered a surface at the provided location fn enter(&mut self, conn: &Connection, qh: &QueueHandle, data_device: &WlDataDevice); /// The drag and drop pointer has left the surface and the session ends. /// The offer will be destroyed unless it was previously dropped. /// In the case of a dropped offer, the client must destroy it manually after it is finished. fn leave(&mut self, conn: &Connection, qh: &QueueHandle, data_device: &WlDataDevice); /// Drag and Drop motion. fn motion(&mut self, conn: &Connection, qh: &QueueHandle, data_device: &WlDataDevice); /// Advertises a new selection. fn selection(&mut self, conn: &Connection, qh: &QueueHandle, data_device: &WlDataDevice); /// Drop performed. /// After the next data offer action event, data may be able to be received, unless the action is "ask". fn drop_performed( &mut self, conn: &Connection, qh: &QueueHandle, data_device: &WlDataDevice, ); } #[derive(Debug, Eq, PartialEq)] pub struct DataDevice { pub(crate) device: WlDataDevice, } impl DataDevice { pub fn data(&self) -> &DataDeviceData { self.device.data().unwrap() } /// Unset the selection of the provided data device as a response to the event with with provided serial. pub fn unset_selection(&self, serial: u32) { self.device.set_selection(None, serial); } pub fn inner(&self) -> &WlDataDevice { &self.device } } impl Drop for DataDevice { fn drop(&mut self) { if self.device.version() >= 2 { self.device.release() } } } impl Dispatch for DataDeviceManagerState where D: Dispatch + Dispatch + DataDeviceHandler + DataOfferHandler + 'static, { event_created_child!(D, WlDataDevice, [ 0 => (WlDataOffer, Default::default()) ]); fn event( state: &mut D, data_device: &wl_data_device::WlDataDevice, event: wl_data_device::Event, data: &DataDeviceData, conn: &Connection, qh: &QueueHandle, ) { use wayland_client::protocol::wl_data_device::Event; let mut inner = data.inner.lock().unwrap(); match event { Event::DataOffer { id } => { inner.undetermined_offers.push(id.clone()); let data = id.data::().unwrap(); data.init_undetermined_offer(&id); } Event::Enter { serial, surface, x, y, id } => { // XXX the spec isn't clear here. if let Some(offer) = inner.drag_offer.take() { offer.destroy(); } if let Some(offer) = id { if let Some(i) = inner.undetermined_offers.iter().position(|o| o == &offer) { inner.undetermined_offers.remove(i); } let data = offer.data::().unwrap(); data.to_dnd_offer(serial, surface, x, y, None); inner.drag_offer = Some(offer.clone()); // XXX Drop done here to prevent Mutex deadlocks. drop(inner); state.enter(conn, qh, data_device); } } Event::Leave => { // We must destroy the offer we've got on enter. if let Some(offer) = inner.drag_offer.take() { let data = offer.data::().unwrap(); if !data.leave() { inner.drag_offer = Some(offer); } } // XXX Drop done here to prevent Mutex deadlocks. drop(inner); state.leave(conn, qh, data_device); } Event::Motion { time, x, y } => { if let Some(offer) = inner.drag_offer.take() { let data = offer.data::().unwrap(); // Update the data offer location. data.motion(x, y, time); inner.drag_offer = Some(offer); } // XXX Drop done here to prevent Mutex deadlocks. drop(inner); state.motion(conn, qh, data_device); } Event::Drop => { if let Some(offer) = inner.drag_offer.take() { let data = offer.data::().unwrap(); let mut drag_inner = data.inner.lock().unwrap(); if let DataDeviceOffer::Drag(ref mut o) = drag_inner.deref_mut().offer { o.dropped = true; } drop(drag_inner); inner.drag_offer = Some(offer); } // XXX Drop done here to prevent Mutex deadlocks. drop(inner); // Pass the info about the drop to the user. state.drop_performed(conn, qh, data_device); } Event::Selection { id } => { // We must drop the current offer regardless. if let Some(offer) = inner.selection_offer.take() { offer.destroy(); } if let Some(offer) = id { if let Some(i) = inner.undetermined_offers.iter().position(|o| o == &offer) { inner.undetermined_offers.remove(i); } let data = offer.data::().unwrap(); data.to_selection_offer(); inner.selection_offer = Some(offer.clone()); // XXX Drop done here to prevent Mutex deadlocks. drop(inner); state.selection(conn, qh, data_device); } } _ => unreachable!(), } } } #[derive(Debug)] pub struct DataDeviceData { /// The seat associated with this device. pub(crate) seat: WlSeat, /// The inner mutable storage. pub(crate) inner: Arc>, } impl DataDeviceData { pub(crate) fn new(seat: WlSeat) -> Self { Self { seat, inner: Default::default() } } /// Get the seat associated with this data device. pub fn seat(&self) -> &WlSeat { &self.seat } /// Get the active dnd offer if it exists. pub fn drag_offer(&self) -> Option { self.inner.lock().unwrap().drag_offer.as_ref().and_then(|offer| { let data = offer.data::().unwrap(); data.as_drag_offer() }) } /// Get the active selection offer if it exists. pub fn selection_offer(&self) -> Option { self.inner.lock().unwrap().selection_offer.as_ref().and_then(|offer| { let data = offer.data::().unwrap(); data.as_selection_offer() }) } } #[derive(Debug, Default)] pub(crate) struct DataDeviceInner { /// the active dnd offer and its data pub drag_offer: Option, /// the active selection offer and its data pub selection_offer: Option, /// the active undetermined offers and their data pub undetermined_offers: Vec, } smithay-client-toolkit-0.18.0/src/data_device_manager/data_offer.rs000064400000000000000000000367731046102023000235200ustar 00000000000000use std::{ ops::{Deref, DerefMut}, os::unix::prelude::{AsFd, OwnedFd}, sync::{Arc, Mutex}, }; use log::warn; use crate::reexports::client::{ protocol::{ wl_data_device_manager::DndAction, wl_data_offer::{self, WlDataOffer}, wl_surface::WlSurface, }, Connection, Dispatch, Proxy, QueueHandle, }; use super::{DataDeviceManagerState, ReadPipe}; /// Handler trait for DataOffer events. /// /// The functions defined in this trait are called as DataOffer events are received from the compositor. pub trait DataOfferHandler: Sized { /// Called to advertise the available DnD Actions as set by the source. fn source_actions( &mut self, conn: &Connection, qh: &QueueHandle, offer: &mut DragOffer, actions: DndAction, ); /// Called to advertise the action selected by the compositor after matching /// the source/destination side actions. Only one action or none will be /// selected in the actions sent by the compositor. This may be called /// multiple times during a DnD operation. The most recent DndAction is the /// only valid one. /// /// At the time of a `drop` event on the data device, this action must be /// used except in the case of an ask action. In the case that the last /// action received is `ask`, the destination asks the user for their /// preference, then calls set_actions & accept each one last time. Finally, /// the destination may then request data to be sent and finishing the data /// offer fn selected_action( &mut self, conn: &Connection, qh: &QueueHandle, offer: &mut DragOffer, actions: DndAction, ); } /// An error that may occur when working with data offers. #[derive(Debug, thiserror::Error)] pub enum DataOfferError { #[error("offer is not valid to receive from yet")] InvalidReceive, #[error("IO error")] Io(std::io::Error), } #[derive(Debug, Clone)] pub struct DragOffer { /// the wl_data offer if it exists pub(crate) data_offer: WlDataOffer, /// the serial for this data offer's enter event pub serial: u32, /// the surface that this DnD is active on pub surface: WlSurface, /// the x position on the surface pub x: f64, /// the y position on this surface pub y: f64, /// the timestamp a motion event was received in millisecond granularity pub time: Option, /// the advertised drag actions pub source_actions: DndAction, /// the compositor selected drag action pub selected_action: DndAction, /// whether or not the drag has been dropped pub dropped: bool, /// whether or not the drag has left pub left: bool, } impl DragOffer { pub fn finish(&self) { if self.data_offer.version() >= 3 { self.data_offer.finish(); } } /// Inspect the mime types available on the given offer. pub fn with_mime_types T>(&self, callback: F) -> T { let mime_types = &self.data_offer.data::().unwrap().inner.lock().unwrap().mime_types; callback(mime_types) } /// Set the accepted and preferred drag and drop actions. /// This request determines the final result of the drag-and-drop operation. /// If the end result is that no action is accepted, the drag source will receive wl_data_source.cancelled. pub fn set_actions(&self, actions: DndAction, preferred_action: DndAction) { if self.data_offer.version() >= 3 && !self.left { self.data_offer.set_actions(actions, preferred_action); } } /// Receive data with the given mime type. /// This request may happen multiple times for different mime types, both before and after wl_data_device.drop. /// Drag-and-drop destination clients may preemptively fetch data or examine it more closely to determine acceptance. pub fn receive(&self, mime_type: String) -> std::io::Result { // When the data device has left, we can't receive unless it was previously dropped. if !self.left || self.dropped { receive(&self.data_offer, mime_type) } else { Err(std::io::Error::new(std::io::ErrorKind::Other, "offer has left")) } } /// Accept the given mime type, or None to reject the offer. /// In version 2, this request is used for feedback, but doesn't affect the final result of the drag-and-drop operation. /// In version 3, this request determines the final result of the drag-and-drop operation. pub fn accept_mime_type(&self, serial: u32, mime_type: Option) { if !self.left { self.data_offer.accept(serial, mime_type); } } /// Destroy the data offer. pub fn destroy(&self) { self.data_offer.destroy(); } /// Retrieve a reference to the inner wl_data_offer. pub fn inner(&self) -> &WlDataOffer { &self.data_offer } } impl PartialEq for DragOffer { fn eq(&self, other: &Self) -> bool { self.data_offer == other.data_offer } } #[derive(Debug, Clone)] pub struct SelectionOffer { /// the wl_data offer pub(crate) data_offer: WlDataOffer, } impl SelectionOffer { /// Inspect the mime types available on the given offer. pub fn with_mime_types T>(&self, callback: F) -> T { let mime_types = &self.data_offer.data::().unwrap().inner.lock().unwrap().mime_types; callback(mime_types) } pub fn receive(&self, mime_type: String) -> Result { receive(&self.data_offer, mime_type).map_err(DataOfferError::Io) } pub fn destroy(&self) { self.data_offer.destroy(); } pub fn inner(&self) -> &WlDataOffer { &self.data_offer } } impl PartialEq for SelectionOffer { fn eq(&self, other: &Self) -> bool { self.data_offer == other.data_offer } } #[derive(Debug, Default)] pub struct DataOfferData { pub(crate) inner: Arc>, } impl DataOfferData { /// Inspect the mime types available on the given offer. pub fn with_mime_types T>(&self, callback: F) -> T { let mime_types = &self.inner.lock().unwrap().mime_types; callback(mime_types) } pub(crate) fn push_mime_type(&self, mime_type: String) { self.inner.lock().unwrap().mime_types.push(mime_type); } pub(crate) fn set_source_action(&self, action: DndAction) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(ref mut o) => o.source_actions = action, DataDeviceOffer::Selection(_) => {} DataDeviceOffer::Undetermined(ref mut o) => o.actions = action, }; } pub(crate) fn set_selected_action(&self, action: DndAction) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(ref mut o) => o.selected_action = action, DataDeviceOffer::Selection(_) => {} // error? DataDeviceOffer::Undetermined(_) => {} // error? }; } pub(crate) fn to_selection_offer(&self) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(o) => { inner.offer = DataDeviceOffer::Selection(SelectionOffer { data_offer: o.data_offer.clone() }); } DataDeviceOffer::Selection(_) => {} DataDeviceOffer::Undetermined(o) => { inner.offer = DataDeviceOffer::Selection(SelectionOffer { data_offer: o.data_offer.clone().unwrap(), }); } } } pub(crate) fn init_undetermined_offer(&self, offer: &WlDataOffer) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(o) => { inner.offer = DataDeviceOffer::Undetermined(UndeterminedOffer { data_offer: Some(offer.clone()), actions: o.source_actions, }); } DataDeviceOffer::Selection(_) => { inner.offer = DataDeviceOffer::Undetermined(UndeterminedOffer { data_offer: Some(offer.clone()), actions: DndAction::empty(), }); } DataDeviceOffer::Undetermined(o) => { o.data_offer = Some(offer.clone()); } } } pub(crate) fn to_dnd_offer( &self, serial: u32, surface: WlSurface, x: f64, y: f64, time: Option, ) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(_) => {} DataDeviceOffer::Selection(o) => { inner.offer = DataDeviceOffer::Drag(DragOffer { data_offer: o.data_offer.clone(), source_actions: DndAction::empty(), selected_action: DndAction::empty(), serial, surface, x, y, time, dropped: false, left: false, }); } DataDeviceOffer::Undetermined(o) => { inner.offer = DataDeviceOffer::Drag(DragOffer { data_offer: o.data_offer.clone().unwrap(), source_actions: o.actions, selected_action: DndAction::empty(), serial, surface, x, y, time, dropped: false, left: false, }); } } } pub(crate) fn motion(&self, x: f64, y: f64, time: u32) { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(o) => { o.x = x; o.y = y; o.time = Some(time); } DataDeviceOffer::Selection(_) => {} DataDeviceOffer::Undetermined(_) => {} } } pub(crate) fn as_drag_offer(&self) -> Option { match &self.inner.lock().unwrap().deref().offer { DataDeviceOffer::Drag(o) => Some(o.clone()), _ => None, } } pub(crate) fn leave(&self) -> bool { let mut inner = self.inner.lock().unwrap(); match &mut inner.deref_mut().offer { DataDeviceOffer::Drag(o) => { o.left = true; if !o.dropped { o.data_offer.destroy(); } !o.dropped } _ => { warn!("DataDeviceOffer::leave called on non-drag offer"); false } } } pub(crate) fn as_selection_offer(&self) -> Option { match &self.inner.lock().unwrap().deref().offer { DataDeviceOffer::Selection(o) => Some(o.clone()), _ => None, } } } #[derive(Debug, Default)] pub struct DataDeviceOfferInner { pub(crate) offer: DataDeviceOffer, pub(crate) mime_types: Vec, } #[derive(Debug, Clone, PartialEq)] pub(crate) enum DataDeviceOffer { Drag(DragOffer), Selection(SelectionOffer), Undetermined(UndeterminedOffer), } impl Default for DataDeviceOffer { fn default() -> Self { DataDeviceOffer::Undetermined(UndeterminedOffer { data_offer: None, actions: DndAction::empty(), }) } } impl Dispatch for DataDeviceManagerState where D: Dispatch + DataOfferHandler, { fn event( state: &mut D, _offer: &wl_data_offer::WlDataOffer, event: ::Event, data: &DataOfferData, conn: &wayland_client::Connection, qh: &wayland_client::QueueHandle, ) { match event { wl_data_offer::Event::Offer { mime_type } => { data.push_mime_type(mime_type); } wl_data_offer::Event::SourceActions { source_actions } => { match source_actions { wayland_client::WEnum::Value(a) => { data.set_source_action(a); match &mut data.inner.lock().unwrap().offer { DataDeviceOffer::Drag(o) => { state.source_actions(conn, qh, o, a); } DataDeviceOffer::Selection(_) => {} DataDeviceOffer::Undetermined(_) => {} } } wayland_client::WEnum::Unknown(_) => {} // Ignore } } wl_data_offer::Event::Action { dnd_action } => { match dnd_action { wayland_client::WEnum::Value(a) => { data.set_selected_action(a); match &mut data.inner.lock().unwrap().offer { DataDeviceOffer::Drag(o) => { state.selected_action(conn, qh, o, a); } DataDeviceOffer::Selection(_) => {} DataDeviceOffer::Undetermined(_) => {} } } wayland_client::WEnum::Unknown(_) => {} // Ignore } } _ => unimplemented!(), }; } } #[derive(Debug, Clone)] pub(crate) struct UndeterminedOffer { pub(crate) data_offer: Option, pub actions: DndAction, } impl PartialEq for UndeterminedOffer { fn eq(&self, other: &Self) -> bool { self.data_offer == other.data_offer } } /// Request to receive the data of a given mime type. /// /// You can do this several times, as a reaction to motion of /// the dnd cursor, or to inspect the data in order to choose your /// response. /// /// Note that you should *not* read the contents right away in a /// blocking way, as you may deadlock your application doing so. /// At least make sure you flush your events to the server before /// doing so. /// /// Fails if too many file descriptors were already open and a pipe /// could not be created. pub fn receive(offer: &WlDataOffer, mime_type: String) -> std::io::Result { use rustix::pipe::{pipe_with, PipeFlags}; // create a pipe let (readfd, writefd) = pipe_with(PipeFlags::CLOEXEC)?; receive_to_fd(offer, mime_type, writefd); Ok(ReadPipe::from(readfd)) } /// Receive data to the write end of a raw file descriptor. If you have the read end, you can read from it. /// /// You can do this several times, as a reaction to motion of /// the dnd cursor, or to inspect the data in order to choose your /// response. /// /// Note that you should *not* read the contents right away in a /// blocking way, as you may deadlock your application doing so. /// At least make sure you flush your events to the server before /// doing so. /// /// The provided file destructor must be a valid FD for writing, and will be closed /// once the contents are written. pub fn receive_to_fd(offer: &WlDataOffer, mime_type: String, writefd: OwnedFd) { offer.receive(mime_type, writefd.as_fd()); } smithay-client-toolkit-0.18.0/src/data_device_manager/data_source.rs000064400000000000000000000130131046102023000236750ustar 00000000000000use crate::reexports::client::{ protocol::{ wl_data_device_manager::DndAction, wl_data_source::{self, WlDataSource}, wl_surface::WlSurface, }, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use super::{data_device::DataDevice, DataDeviceManagerState, WritePipe}; #[derive(Debug, Default)] pub struct DataSourceData {} pub trait DataSourceDataExt: Send + Sync { fn data_source_data(&self) -> &DataSourceData; } impl DataSourceDataExt for DataSourceData { fn data_source_data(&self) -> &DataSourceData { self } } /// Handler trait for DataSource events. /// /// The functions defined in this trait are called as DataSource events are received from the compositor. pub trait DataSourceHandler: Sized { /// This may be called multiple times, once for each accepted mime type from the destination, if any. fn accept_mime( &mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource, mime: Option, ); /// The client has requested the data for this source to be sent. /// Send the data, then close the fd. fn send_request( &mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource, mime: String, fd: WritePipe, ); /// The data source is no longer valid /// Cleanup & destroy this resource fn cancelled(&mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource); /// A drop was performed. /// The data source will be used and should not be destroyed yet fn dnd_dropped(&mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource); /// The drag and drop finished. /// The data source may be destroyed. fn dnd_finished(&mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource); /// An action was selected by the compositor. fn action( &mut self, conn: &Connection, qh: &QueueHandle, source: &WlDataSource, action: DndAction, ); } impl Dispatch for DataDeviceManagerState where D: Dispatch + DataSourceHandler, U: DataSourceDataExt, { fn event( state: &mut D, source: &wl_data_source::WlDataSource, event: ::Event, _data: &U, conn: &wayland_client::Connection, qh: &wayland_client::QueueHandle, ) { match event { wl_data_source::Event::Target { mime_type } => { state.accept_mime(conn, qh, source, mime_type) } wl_data_source::Event::Send { mime_type, fd } => { state.send_request(conn, qh, source, mime_type, fd.into()); } wl_data_source::Event::Cancelled => { state.cancelled(conn, qh, source); } wl_data_source::Event::DndDropPerformed => { state.dnd_dropped(conn, qh, source); } wl_data_source::Event::DndFinished => { state.dnd_finished(conn, qh, source); } wl_data_source::Event::Action { dnd_action } => match dnd_action { WEnum::Value(dnd_action) => { state.action(conn, qh, source, dnd_action); } WEnum::Unknown(_) => {} }, _ => unimplemented!(), }; } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct CopyPasteSource { pub(crate) inner: WlDataSource, } impl CopyPasteSource { /// Set the selection of the provided data device as a response to the event with with provided serial. pub fn set_selection(&self, device: &DataDevice, serial: u32) { device.device.set_selection(Some(&self.inner), serial); } pub fn inner(&self) -> &WlDataSource { &self.inner } } impl Drop for CopyPasteSource { fn drop(&mut self) { self.inner.destroy(); } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct DragSource { pub(crate) inner: WlDataSource, } impl DragSource { /// Start a normal drag and drop operation. /// This can be used for both intra-client DnD or inter-client Dnd. /// The drag is cancelled when the DragSource is dropped. pub fn start_drag( &self, device: &DataDevice, origin: &WlSurface, icon: Option<&WlSurface>, serial: u32, ) { device.device.start_drag(Some(&self.inner), origin, icon, serial); } /// Start an internal drag and drop operation. /// This will pass a NULL source, and the client is expected to handle data passing internally. /// Only Enter, Leave, & Motion events will be sent to the client pub fn start_internal_drag( device: &DataDevice, origin: &WlSurface, icon: Option<&WlSurface>, serial: u32, ) { device.device.start_drag(None, origin, icon, serial); } /// Set the actions that this drag source supports. /// This can only be done once, and must be done before the drag is started. pub fn set_actions(&self, dnd_actions: DndAction) { if self.inner.version() >= 3 { self.inner.set_actions(dnd_actions); } self.inner.set_actions(dnd_actions); } /// Retrieve a reference to the inner wl_data_source. pub fn inner(&self) -> &WlDataSource { &self.inner } } impl Drop for DragSource { fn drop(&mut self) { self.inner.destroy(); } } smithay-client-toolkit-0.18.0/src/data_device_manager/mod.rs000064400000000000000000000120561046102023000221710ustar 00000000000000use crate::error::GlobalError; use crate::globals::{GlobalData, ProvidesBoundGlobal}; use crate::reexports::client::{ globals::{BindError, GlobalList}, protocol::{ wl_data_device, wl_data_device_manager::{self, DndAction, WlDataDeviceManager}, wl_data_source::WlDataSource, wl_seat::WlSeat, }, Connection, Dispatch, Proxy, QueueHandle, }; pub mod data_device; pub mod data_offer; pub mod data_source; mod read_pipe; mod write_pipe; pub use read_pipe::*; pub use write_pipe::*; use data_device::{DataDevice, DataDeviceData}; use data_source::{CopyPasteSource, DataSourceData, DragSource}; #[derive(Debug)] pub struct DataDeviceManagerState { manager: WlDataDeviceManager, } impl DataDeviceManagerState { pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where State: Dispatch + 'static, { let manager = globals.bind(qh, 1..=3, GlobalData)?; Ok(Self { manager }) } pub fn data_device_manager(&self) -> &WlDataDeviceManager { &self.manager } /// creates a data source for copy paste pub fn create_copy_paste_source( &self, qh: &QueueHandle, mime_types: impl IntoIterator, ) -> CopyPasteSource where D: Dispatch + 'static, { CopyPasteSource { inner: self.create_data_source(qh, mime_types, None) } } /// creates a data source for drag and drop pub fn create_drag_and_drop_source( &self, qh: &QueueHandle, mime_types: impl IntoIterator, dnd_actions: DndAction, ) -> DragSource where D: Dispatch + 'static, { DragSource { inner: self.create_data_source(qh, mime_types, Some(dnd_actions)) } } /// creates a data source fn create_data_source( &self, qh: &QueueHandle, mime_types: impl IntoIterator, dnd_actions: Option, ) -> WlDataSource where D: Dispatch + 'static, { let source = self.manager.create_data_source(qh, Default::default()); for mime in mime_types { source.offer(mime.to_string()); } if self.manager.version() >= 3 { if let Some(dnd_actions) = dnd_actions { source.set_actions(dnd_actions); } } source } /// create a new data device for a given seat pub fn get_data_device(&self, qh: &QueueHandle, seat: &WlSeat) -> DataDevice where D: Dispatch + 'static, { let data = DataDeviceData::new(seat.clone()); DataDevice { device: self.manager.get_data_device(seat, qh, data) } } } impl ProvidesBoundGlobal for DataDeviceManagerState { fn bound_global(&self) -> Result { Ok(self.manager.clone()) } } impl Dispatch for DataDeviceManagerState where D: Dispatch, { fn event( _state: &mut D, _proxy: &wl_data_device_manager::WlDataDeviceManager, _event: ::Event, _data: &GlobalData, _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!("wl_data_device_manager has no events") } } #[macro_export] macro_rules! delegate_data_device { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_data_device_manager::WlDataDeviceManager: $crate::globals::GlobalData ] => $crate::data_device_manager::DataDeviceManagerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_data_offer::WlDataOffer: $crate::data_device_manager::data_offer::DataOfferData ] => $crate::data_device_manager::DataDeviceManagerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_data_source::WlDataSource: $crate::data_device_manager::data_source::DataSourceData ] => $crate::data_device_manager::DataDeviceManagerState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_data_device::WlDataDevice: $crate::data_device_manager::data_device::DataDeviceData ] => $crate::data_device_manager::DataDeviceManagerState ); }; } smithay-client-toolkit-0.18.0/src/data_device_manager/read_pipe.rs000064400000000000000000000076561046102023000233540ustar 00000000000000use std::{ fs, io, os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, }; /// If the `calloop` cargo feature is enabled, this can be used /// as an `EventSource` in a calloop event loop. #[must_use] #[derive(Debug)] pub struct ReadPipe { #[cfg(feature = "calloop")] file: calloop::generic::Generic, #[cfg(not(feature = "calloop"))] file: fs::File, } #[cfg(feature = "calloop")] impl io::Read for ReadPipe { fn read(&mut self, buf: &mut [u8]) -> io::Result { unsafe { self.file.get_mut().read(buf) } } } #[cfg(not(feature = "calloop"))] impl io::Read for ReadPipe { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.file.read(buf) } } #[cfg(feature = "calloop")] impl FromRawFd for ReadPipe { unsafe fn from_raw_fd(fd: RawFd) -> ReadPipe { ReadPipe { file: calloop::generic::Generic::new( unsafe { FromRawFd::from_raw_fd(fd) }, calloop::Interest::READ, calloop::Mode::Level, ), } } } #[cfg(feature = "calloop")] impl From for ReadPipe { fn from(owned: OwnedFd) -> Self { ReadPipe { file: calloop::generic::Generic::new( owned.into(), calloop::Interest::READ, calloop::Mode::Level, ), } } } #[cfg(not(feature = "calloop"))] impl FromRawFd for ReadPipe { unsafe fn from_raw_fd(fd: RawFd) -> ReadPipe { ReadPipe { file: unsafe { FromRawFd::from_raw_fd(fd) } } } } #[cfg(not(feature = "calloop"))] impl From for ReadPipe { fn from(owned: OwnedFd) -> Self { ReadPipe { file: owned.into() } } } #[cfg(feature = "calloop")] impl AsRawFd for ReadPipe { fn as_raw_fd(&self) -> RawFd { self.file.get_ref().as_raw_fd() } } #[cfg(feature = "calloop")] impl AsFd for ReadPipe { fn as_fd(&self) -> BorrowedFd<'_> { self.file.get_ref().as_fd() } } #[cfg(not(feature = "calloop"))] impl AsRawFd for ReadPipe { fn as_raw_fd(&self) -> RawFd { self.file.as_raw_fd() } } #[cfg(not(feature = "calloop"))] impl AsFd for ReadPipe { fn as_fd(&self) -> BorrowedFd<'_> { self.file.as_fd() } } #[cfg(feature = "calloop")] impl IntoRawFd for ReadPipe { fn into_raw_fd(self) -> RawFd { self.file.unwrap().as_raw_fd() } } #[cfg(feature = "calloop")] impl From for OwnedFd { fn from(read_pipe: ReadPipe) -> Self { read_pipe.file.unwrap().into() } } #[cfg(not(feature = "calloop"))] impl IntoRawFd for ReadPipe { fn into_raw_fd(self) -> RawFd { self.file.into_raw_fd() } } #[cfg(not(feature = "calloop"))] impl From for OwnedFd { fn from(read_pipe: ReadPipe) -> Self { read_pipe.file.into() } } #[cfg(feature = "calloop")] impl calloop::EventSource for ReadPipe { type Event = (); type Error = std::io::Error; type Metadata = calloop::generic::NoIoDrop; type Ret = calloop::PostAction; fn process_events( &mut self, readiness: calloop::Readiness, token: calloop::Token, mut callback: F, ) -> std::io::Result where F: FnMut((), &mut calloop::generic::NoIoDrop) -> Self::Ret, { self.file.process_events(readiness, token, |_, file| Ok(callback((), file))) } fn register( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.file.register(poll, token_factory) } fn reregister( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.file.reregister(poll, token_factory) } fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { self.file.unregister(poll) } } smithay-client-toolkit-0.18.0/src/data_device_manager/write_pipe.rs000064400000000000000000000101671046102023000235620ustar 00000000000000use std::{ fs, io, os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, }; /// If the `calloop` cargo feature is enabled, this can be used /// as an `EventSource` in a calloop event loop. #[must_use] #[derive(Debug)] pub struct WritePipe { #[cfg(feature = "calloop")] file: calloop::generic::Generic, #[cfg(not(feature = "calloop"))] file: fs::File, } #[cfg(feature = "calloop")] impl io::Write for WritePipe { fn write(&mut self, buf: &[u8]) -> io::Result { unsafe { self.file.get_mut().write(buf) } } fn flush(&mut self) -> io::Result<()> { unsafe { self.file.get_mut().flush() } } } #[cfg(not(feature = "calloop"))] impl io::Write for WritePipe { fn write(&mut self, buf: &[u8]) -> io::Result { self.file.write(buf) } fn flush(&mut self) -> io::Result<()> { self.file.flush() } } #[cfg(feature = "calloop")] impl FromRawFd for WritePipe { unsafe fn from_raw_fd(fd: RawFd) -> WritePipe { WritePipe { file: calloop::generic::Generic::new( unsafe { FromRawFd::from_raw_fd(fd) }, calloop::Interest::WRITE, calloop::Mode::Level, ), } } } #[cfg(feature = "calloop")] impl From for WritePipe { fn from(owned: OwnedFd) -> Self { WritePipe { file: calloop::generic::Generic::new( owned.into(), calloop::Interest::WRITE, calloop::Mode::Level, ), } } } #[cfg(not(feature = "calloop"))] impl FromRawFd for WritePipe { unsafe fn from_raw_fd(fd: RawFd) -> WritePipe { WritePipe { file: unsafe { FromRawFd::from_raw_fd(fd) } } } } #[cfg(not(feature = "calloop"))] impl From for WritePipe { fn from(owned: OwnedFd) -> Self { WritePipe { file: owned.into() } } } #[cfg(feature = "calloop")] impl AsRawFd for WritePipe { fn as_raw_fd(&self) -> RawFd { self.file.get_ref().as_raw_fd() } } #[cfg(feature = "calloop")] impl AsFd for WritePipe { fn as_fd(&self) -> BorrowedFd { self.file.get_ref().as_fd() } } #[cfg(not(feature = "calloop"))] impl AsRawFd for WritePipe { fn as_raw_fd(&self) -> RawFd { self.file.as_raw_fd() } } #[cfg(not(feature = "calloop"))] impl AsFd for WritePipe { fn as_fd(&self) -> BorrowedFd<'_> { self.file.as_fd() } } #[cfg(feature = "calloop")] impl IntoRawFd for WritePipe { fn into_raw_fd(self) -> RawFd { self.file.unwrap().into_raw_fd() } } #[cfg(feature = "calloop")] impl From for OwnedFd { fn from(write_pipe: WritePipe) -> Self { write_pipe.file.unwrap().into() } } #[cfg(not(feature = "calloop"))] impl IntoRawFd for WritePipe { fn into_raw_fd(self) -> RawFd { self.file.into_raw_fd() } } #[cfg(not(feature = "calloop"))] impl From for OwnedFd { fn from(write_pipe: WritePipe) -> Self { write_pipe.file.into() } } #[cfg(feature = "calloop")] impl calloop::EventSource for WritePipe { type Event = (); type Error = std::io::Error; type Metadata = calloop::generic::NoIoDrop; type Ret = calloop::PostAction; fn process_events( &mut self, readiness: calloop::Readiness, token: calloop::Token, mut callback: F, ) -> std::io::Result where F: FnMut((), &mut calloop::generic::NoIoDrop) -> Self::Ret, { self.file.process_events(readiness, token, |_, file| Ok(callback((), file))) } fn register( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.file.register(poll, token_factory) } fn reregister( &mut self, poll: &mut calloop::Poll, token_factory: &mut calloop::TokenFactory, ) -> calloop::Result<()> { self.file.reregister(poll, token_factory) } fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { self.file.unregister(poll) } } smithay-client-toolkit-0.18.0/src/dmabuf.rs000064400000000000000000000363661046102023000167400ustar 00000000000000use crate::{error::GlobalError, globals::GlobalData, registry::GlobalProxy}; use memmap2::{Mmap, MmapOptions}; use std::{fmt, mem, os::unix::io::BorrowedFd, slice, sync::Mutex}; use wayland_client::{ globals::GlobalList, protocol::{wl_buffer, wl_surface}, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use wayland_protocols::wp::linux_dmabuf::zv1::client::{ zwp_linux_buffer_params_v1, zwp_linux_dmabuf_feedback_v1::{self, TrancheFlags}, zwp_linux_dmabuf_v1, }; // Workaround until `libc` updates to FreeBSD 12 ABI #[cfg(target_os = "freebsd")] type dev_t = u64; #[cfg(not(target_os = "freebsd"))] use libc::dev_t; /// A preference tranche of dmabuf formats #[derive(Debug)] pub struct DmabufFeedbackTranche { /// `dev_t` value for preferred target device. May be scan-out or /// renderer device. pub device: dev_t, /// Flags for tranche pub flags: WEnum, /// Indices of formats in the format table pub formats: Vec, } impl Default for DmabufFeedbackTranche { fn default() -> DmabufFeedbackTranche { DmabufFeedbackTranche { device: 0, flags: WEnum::Value(TrancheFlags::empty()), formats: Vec::new(), } } } /// A single dmabuf format/modifier pair // Must have correct representation to be able to mmap format table #[repr(C)] pub struct DmabufFormat { /// Fourcc format pub format: u32, _padding: u32, /// Modifier, or `DRM_FORMAT_MOD_INVALID` for implict modifier pub modifier: u64, } impl fmt::Debug for DmabufFormat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DmabufFormat") .field("format", &self.format) .field("modifier", &self.modifier) .finish() } } /// Description of supported and preferred dmabuf formats #[derive(Default)] pub struct DmabufFeedback { format_table: Option<(Mmap, usize)>, main_device: dev_t, tranches: Vec, } impl fmt::Debug for DmabufFeedback { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DmabufFeedback") .field("format_table", &self.format_table()) .field("main_device", &self.main_device) .field("tranches", &self.tranches) .finish() } } impl DmabufFeedback { /// Format/modifier pairs pub fn format_table(&self) -> &[DmabufFormat] { self.format_table.as_ref().map_or(&[], |(mmap, len)| unsafe { slice::from_raw_parts(mmap.as_ptr() as *const DmabufFormat, *len) }) } /// `dev_t` value for main device. Buffers must be importable from main device. pub fn main_device(&self) -> dev_t { self.main_device } /// Tranches in descending order of preference pub fn tranches(&self) -> &[DmabufFeedbackTranche] { &self.tranches } } #[doc(hidden)] #[derive(Debug, Default)] pub struct DmabufFeedbackData { pending: Mutex, pending_tranche: Mutex, } #[doc(hidden)] #[derive(Debug)] pub struct DmaBufferData; /// A handler for [`zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1`] #[derive(Debug)] pub struct DmabufState { zwp_linux_dmabuf: GlobalProxy, modifiers: Vec, } impl DmabufState { /// Bind `zwp_linux_dmabuf_v1` global version 3 or 4, if it exists. /// /// This does not fail if the global does not exist. pub fn new(globals: &GlobalList, qh: &QueueHandle) -> Self where D: Dispatch + 'static, { // Mesa (at least the latest version) also requires version 3 or 4 let zwp_linux_dmabuf = GlobalProxy::from(globals.bind(qh, 3..=4, GlobalData)); Self { zwp_linux_dmabuf, modifiers: Vec::new() } } /// Only populated in version `<4` /// /// On version `4`, use [`DmabufState::get_surface_feedback`]. pub fn modifiers(&self) -> &[DmabufFormat] { &self.modifiers } /// Supported protocol version, if any pub fn version(&self) -> Option { Some(self.zwp_linux_dmabuf.get().ok()?.version()) } /// Create a params object for constructing a buffer /// /// Errors if `zwp_linux_dmabuf_v1` does not exist or has unsupported /// version. An application can then fallback to using `shm` buffers. pub fn create_params(&self, qh: &QueueHandle) -> Result where D: Dispatch + 'static, { let zwp_linux_dmabuf = self.zwp_linux_dmabuf.get()?; let params = zwp_linux_dmabuf.create_params(qh, GlobalData); Ok(DmabufParams { params }) } /// Get default dmabuf feedback. Requires version `4`. /// /// On version `3`, use [`DmabufState::modifiers`]. pub fn get_default_feedback( &self, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; Ok(zwp_linux_dmabuf.get_default_feedback(qh, DmabufFeedbackData::default())) } /// Get default dmabuf feedback for given surface. Requires version `4`. /// /// On version `3`, use [`DmabufState::modifiers`]. pub fn get_surface_feedback( &self, surface: &wl_surface::WlSurface, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { let zwp_linux_dmabuf = self.zwp_linux_dmabuf.with_min_version(4)?; Ok(zwp_linux_dmabuf.get_surface_feedback(surface, qh, DmabufFeedbackData::default())) } } pub trait DmabufHandler: Sized { fn dmabuf_state(&mut self) -> &mut DmabufState; /// Server has sent dmabuf feedback information. This may be received multiple /// times by a `ZwpLinuxDmabufFeedbackV1` object. fn dmabuf_feedback( &mut self, conn: &Connection, qh: &QueueHandle, proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, feedback: DmabufFeedback, ); /// `wl_buffer` associated with `params` has been created successfully. fn created( &mut self, conn: &Connection, qh: &QueueHandle, params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, buffer: wl_buffer::WlBuffer, ); /// Failed to create `wl_buffer` for `params`. fn failed( &mut self, conn: &Connection, qh: &QueueHandle, params: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, ); /// Compositor has released a `wl_buffer` created through [`DmabufParams`]. fn released(&mut self, conn: &Connection, qh: &QueueHandle, buffer: &wl_buffer::WlBuffer); } /// Builder for a dmabuf backed buffer #[derive(Debug)] pub struct DmabufParams { params: zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, } impl DmabufParams { /// Add a plane /// /// In version `4`, it is a protocol error if `format`/`modifier` pair wasn't /// advertised as supported. pub fn add(&self, fd: BorrowedFd<'_>, plane_idx: u32, offset: u32, stride: u32, modifier: u64) { let modifier_hi = (modifier >> 32) as u32; let modifier_lo = (modifier & 0xffffffff) as u32; self.params.add(fd, plane_idx, offset, stride, modifier_hi, modifier_lo); } /// Create buffer. /// /// [`DmabufHandler::created`] or [`DmabufHandler::failed`] will be invoked when the /// operation succeeds or fails. pub fn create( self, width: i32, height: i32, format: u32, flags: zwp_linux_buffer_params_v1::Flags, ) -> zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1 { self.params.create(width, height, format, flags); self.params } /// Create buffer immediately. /// /// On failure buffer is invalid, and server may raise protocol error or /// send [`DmabufHandler::failed`]. pub fn create_immed( self, width: i32, height: i32, format: u32, flags: zwp_linux_buffer_params_v1::Flags, qh: &QueueHandle, ) -> (wl_buffer::WlBuffer, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1) where D: Dispatch + 'static, { let buffer = self.params.create_immed(width, height, format, flags, qh, DmaBufferData); (buffer, self.params) } } impl Dispatch for DmabufState where D: Dispatch + DmabufHandler, { fn event( state: &mut D, proxy: &zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, event: zwp_linux_dmabuf_v1::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { match event { zwp_linux_dmabuf_v1::Event::Format { format: _ } => { // Formats are duplicated by modifier events since version 3. // Ignore this event, like Mesa does. } zwp_linux_dmabuf_v1::Event::Modifier { format, modifier_hi, modifier_lo } => { if proxy.version() < 4 { let modifier = (u64::from(modifier_hi) << 32) | u64::from(modifier_lo); state.dmabuf_state().modifiers.push(DmabufFormat { format, _padding: 0, modifier, }); } } _ => unreachable!(), } } } impl Dispatch for DmabufState where D: Dispatch + DmabufHandler, { fn event( state: &mut D, proxy: &zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, event: zwp_linux_dmabuf_feedback_v1::Event, data: &DmabufFeedbackData, conn: &Connection, qh: &QueueHandle, ) { match event { zwp_linux_dmabuf_feedback_v1::Event::Done => { let feedback = mem::take(&mut *data.pending.lock().unwrap()); state.dmabuf_feedback(conn, qh, proxy, feedback); } zwp_linux_dmabuf_feedback_v1::Event::FormatTable { fd, size } => { let size = size as usize; let mmap = unsafe { MmapOptions::new().map_copy_read_only(&fd).expect("Failed to map format table") }; assert!(mmap.len() >= size); let entry_size = mem::size_of::(); assert!((size % entry_size) == 0); let len = size / entry_size; data.pending.lock().unwrap().format_table = Some((mmap, len)); } zwp_linux_dmabuf_feedback_v1::Event::MainDevice { device } => { let device = dev_t::from_ne_bytes(device.try_into().unwrap()); data.pending.lock().unwrap().main_device = device; } zwp_linux_dmabuf_feedback_v1::Event::TrancheDone => { let tranche = mem::take(&mut *data.pending_tranche.lock().unwrap()); data.pending.lock().unwrap().tranches.push(tranche); } zwp_linux_dmabuf_feedback_v1::Event::TrancheTargetDevice { device } => { let device = dev_t::from_ne_bytes(device.try_into().unwrap()); data.pending_tranche.lock().unwrap().device = device; } zwp_linux_dmabuf_feedback_v1::Event::TrancheFormats { indices } => { assert!((indices.len() % 2) == 0); let indices = indices.chunks(2).map(|i| u16::from_ne_bytes([i[0], i[1]])).collect::>(); data.pending_tranche.lock().unwrap().formats = indices; } zwp_linux_dmabuf_feedback_v1::Event::TrancheFlags { flags } => { data.pending_tranche.lock().unwrap().flags = flags; } _ => unreachable!(), } } } impl Dispatch for DmabufState where D: Dispatch + Dispatch + DmabufHandler + 'static, { fn event( state: &mut D, proxy: &zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, event: zwp_linux_buffer_params_v1::Event, _: &GlobalData, conn: &Connection, qh: &QueueHandle, ) { match event { zwp_linux_buffer_params_v1::Event::Created { buffer } => { state.created(conn, qh, proxy, buffer); } zwp_linux_buffer_params_v1::Event::Failed => { state.failed(conn, qh, proxy); } _ => unreachable!(), } } wayland_client::event_created_child!(D, zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, [ zwp_linux_buffer_params_v1::EVT_CREATED_OPCODE => (wl_buffer::WlBuffer, DmaBufferData) ]); } impl Dispatch for DmabufState where D: Dispatch + DmabufHandler, { fn event( state: &mut D, proxy: &wl_buffer::WlBuffer, event: wl_buffer::Event, _: &DmaBufferData, conn: &Connection, qh: &QueueHandle, ) { match event { wl_buffer::Event::Release => state.released(conn, qh, proxy), _ => unreachable!(), } } } #[macro_export] macro_rules! delegate_dmabuf { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1: $crate::globals::GlobalData ] => $crate::dmabuf::DmabufState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1: $crate::globals::GlobalData ] => $crate::dmabuf::DmabufState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1: $crate::dmabuf::DmabufFeedbackData ] => $crate::dmabuf::DmabufState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_buffer::WlBuffer: $crate::dmabuf::DmaBufferData ] => $crate::dmabuf::DmabufState ); }; } smithay-client-toolkit-0.18.0/src/error.rs000064400000000000000000000010221046102023000166100ustar 00000000000000/// An error that may occur when creating objects using a global. #[derive(Debug, thiserror::Error)] pub enum GlobalError { /// A compositor global was not available #[error("the '{0}' global was not available")] MissingGlobal(&'static str), /// A compositor global was available, but did not support the given minimum version #[error("the '{name}' global does not support interface version {required} (using version {available})")] InvalidVersion { name: &'static str, required: u32, available: u32 }, } smithay-client-toolkit-0.18.0/src/globals.rs000064400000000000000000000050671046102023000171170ustar 00000000000000use crate::error::GlobalError; use wayland_client::Proxy; /// A trait implemented by types that provide access to capability globals. /// /// The returned global must be fully compatible with the provided `API_COMPAT_VERSION` generic /// argument. For example: /// /// - A global that binds to `wl_compositor` with maximum version 4 could implement /// `ProvidesBoundGlobal`, `ProvidesBoundGlobal`, /// `ProvidesBoundGlobal`, and `ProvidesBoundGlobal` because /// versions 2-4 only add additional requests to the `wl_surface` API. /// - A global that binds to `wl_compositor` with maximum version 5 may only implement /// `ProvidesBoundGlobal` because version 5 makes using `wl_surface::attach` with /// a nonzero offset a protocol error. A caller who is only aware of the version 4 API risks /// causing these protocol errors if it uses surfaces created by such a global. /// /// Changes that cause compatibility breaks include: /// /// - Adding a new event to the global or to any object created by the global. /// - Adding a new requirement to an existing request. /// /// The resulting global may have a version lower than `API_COMPAT_VERSION` if, at runtime, the /// compositor does not support the new version. Clients should either be prepared to handle /// earlier versions of the protocol or use [`ProvidesBoundGlobal::with_min_version`] to produce an /// error in this case. /// /// It is permitted to implement `ProvidesBoundGlobal` for versions that are higher than the /// maximum version you bind. When rustc gains the ability to constrain const parameters with /// integer bounds (`where API_COMPAT_VERSION >= 5`), implementations of this trait should be /// provided by specifying a lower bound for the compat version in order to avoid requiring version /// updates be done in lock-step. pub trait ProvidesBoundGlobal { fn bound_global(&self) -> Result; fn with_min_version(&self, version: u32) -> Result { let proxy = self.bound_global()?; if proxy.version() < version { Err(GlobalError::InvalidVersion { name: I::interface().name, required: version, available: proxy.version(), }) } else { Ok(proxy) } } } /// A struct used as the UserData field for globals bound by SCTK. /// /// This is used instead of `()` to allow multiple `Dispatch` impls on the same object. #[derive(Debug)] pub struct GlobalData; smithay-client-toolkit-0.18.0/src/lib.rs000064400000000000000000000015041046102023000162320ustar 00000000000000#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn( // missing_docs, // Commented out for now so the project isn't all yellow. missing_debug_implementations )] #![forbid(unsafe_op_in_unsafe_fn)] #![allow(clippy::new_without_default)] /// Re-exports of some crates, for convenience. pub mod reexports { #[cfg(feature = "calloop")] pub use calloop; #[cfg(feature = "calloop")] pub use calloop_wayland_source; pub use wayland_client as client; pub use wayland_csd_frame as csd_frame; pub use wayland_protocols as protocols; pub use wayland_protocols_wlr as protocols_wlr; } pub mod compositor; pub mod data_device_manager; pub mod dmabuf; pub mod error; pub mod globals; pub mod output; pub mod primary_selection; pub mod registry; pub mod seat; pub mod shell; pub mod shm; pub mod subcompositor; smithay-client-toolkit-0.18.0/src/output.rs000064400000000000000000000611131046102023000170260ustar 00000000000000use std::{ any::Any, fmt::{self, Display, Formatter}, sync::{Arc, Mutex, Weak}, }; use wayland_client::{ globals::GlobalList, protocol::wl_output::{self, Subpixel, Transform}, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use wayland_protocols::xdg::xdg_output::zv1::client::{ zxdg_output_manager_v1::{self, ZxdgOutputManagerV1}, zxdg_output_v1, }; use crate::{ globals::GlobalData, registry::{GlobalProxy, ProvidesRegistryState, RegistryHandler}, }; /// Simplified event handler for [`wl_output::WlOutput`]. /// See [`OutputState`]. pub trait OutputHandler: Sized { fn output_state(&mut self) -> &mut OutputState; /// A new output has been advertised. fn new_output( &mut self, conn: &Connection, qh: &QueueHandle, output: wl_output::WlOutput, ); /// An existing output has changed. fn update_output( &mut self, conn: &Connection, qh: &QueueHandle, output: wl_output::WlOutput, ); /// An output is no longer advertised. /// /// The info passed to this function was the state of the output before destruction. fn output_destroyed( &mut self, conn: &Connection, qh: &QueueHandle, output: wl_output::WlOutput, ); } type ScaleWatcherFn = dyn Fn(&mut dyn Any, &Connection, &dyn Any, &wl_output::WlOutput) + Send + Sync; /// A handler for delegating [`wl_output::WlOutput`](wayland_client::protocol::wl_output::WlOutput). /// /// When implementing [`ProvidesRegistryState`], /// [`registry_handlers!`](crate::registry_handlers) may be used to delegate all /// output events to an instance of this type. It will internally store the internal state of all /// outputs and allow querying them via the `OutputState::outputs` and `OutputState::info` methods. /// /// ## Example /// /// ``` /// use smithay_client_toolkit::output::{OutputHandler,OutputState}; /// use smithay_client_toolkit::registry::{ProvidesRegistryState,RegistryHandler}; /// # use smithay_client_toolkit::registry::RegistryState; /// use smithay_client_toolkit::{registry_handlers,delegate_output, delegate_registry}; /// use wayland_client::{Connection,QueueHandle,protocol::wl_output}; /// /// struct ExampleState { /// # registry_state: RegistryState, /// // The state is usually kept as an attribute of the application state. /// output_state: OutputState, /// } /// /// // When using `OutputState`, an implementation of `OutputHandler` should be supplied: /// impl OutputHandler for ExampleState { /// // Custom implementation handling output events here ... /// # fn output_state(&mut self) -> &mut OutputState { /// # &mut self.output_state /// # } /// # /// # fn new_output(&mut self, _: &Connection, _: &QueueHandle, _: wl_output::WlOutput) { /// # } /// # /// # fn update_output(&mut self, _: &Connection, _: &QueueHandle, _: wl_output::WlOutput) { /// # } /// # /// # fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle, _: wl_output::WlOutput) { /// # } /// } /// /// // Delegating to the registry is required to use `OutputState`. /// delegate_registry!(ExampleState); /// delegate_output!(ExampleState); /// /// impl ProvidesRegistryState for ExampleState { /// # fn registry(&mut self) -> &mut RegistryState { /// # &mut self.registry_state /// # } /// // ... /// /// registry_handlers!(OutputState); /// } /// ``` pub struct OutputState { xdg: GlobalProxy, outputs: Vec, callbacks: Vec>, } impl fmt::Debug for OutputState { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("OutputState") .field("xdg", &self.xdg) .field("outputs", &self.outputs) .field("callbacks", &self.callbacks.len()) .finish() } } pub struct ScaleWatcherHandle(Arc); impl fmt::Debug for ScaleWatcherHandle { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("ScaleWatcherHandle").finish_non_exhaustive() } } impl OutputState { pub fn new< D: Dispatch + Dispatch + Dispatch + 'static, >( global_list: &GlobalList, qh: &QueueHandle, ) -> OutputState { let (outputs, xdg) = global_list.contents().with_list(|globals| { let outputs: Vec = crate::registry::bind_all( global_list.registry(), globals, qh, 1..=4, OutputData::new, ) .expect("Failed to bind global"); let xdg = crate::registry::bind_one(global_list.registry(), globals, qh, 1..=3, GlobalData) .into(); (outputs, xdg) }); let mut output_state = OutputState { xdg, outputs: vec![], callbacks: vec![] }; for wl_output in outputs { output_state.setup(wl_output, qh); } output_state } /// Returns an iterator over all outputs. pub fn outputs(&self) -> impl Iterator { self.outputs.iter().map(|output| &output.wl_output).cloned().collect::>().into_iter() } /// Returns information about an output. /// /// This may be none if the output has been destroyed or the compositor has not sent information about the /// output yet. pub fn info(&self, output: &wl_output::WlOutput) -> Option { self.outputs .iter() .find(|inner| &inner.wl_output == output) .and_then(|inner| inner.current_info.clone()) } pub fn add_scale_watcher(data: &mut D, f: F) -> ScaleWatcherHandle where D: OutputHandler + 'static, F: Fn(&mut D, &Connection, &QueueHandle, &wl_output::WlOutput) + Send + Sync + 'static, { let state = data.output_state(); let rv = ScaleWatcherHandle(Arc::new(move |data, conn, qh, output| { if let (Some(data), Some(qh)) = (data.downcast_mut(), qh.downcast_ref()) { f(data, conn, qh, output); } })); state.callbacks.retain(|f| f.upgrade().is_some()); state.callbacks.push(Arc::downgrade(&rv.0)); rv } fn setup(&mut self, wl_output: wl_output::WlOutput, qh: &QueueHandle) where D: Dispatch + 'static, { let data = wl_output.data::().unwrap().clone(); let pending_info = data.0.lock().unwrap().clone(); let name = pending_info.id; let version = wl_output.version(); let pending_xdg = self.xdg.get().is_ok(); let xdg_output = if pending_xdg { let xdg = self.xdg.get().unwrap(); Some(xdg.get_xdg_output(&wl_output, qh, data)) } else { None }; let inner = OutputInner { name, wl_output, xdg_output, just_created: true, // wl_output::done was added in version 2. // If we have an output at version 1, assume the data was already sent. current_info: if version > 1 { None } else { Some(OutputInfo::new(name)) }, pending_info, pending_wl: true, pending_xdg, }; self.outputs.push(inner); } } #[derive(Debug, Clone)] pub struct OutputData(Arc>); impl OutputData { pub fn new(name: u32) -> OutputData { OutputData(Arc::new(Mutex::new(OutputInfo::new(name)))) } /// Get the output scale factor. pub fn scale_factor(&self) -> i32 { let guard = self.0.lock().unwrap(); guard.scale_factor } /// Get the output transform. pub fn transform(&self) -> wl_output::Transform { let guard = self.0.lock().unwrap(); guard.transform } /// Access the underlying [`OutputInfo`]. /// /// Reentrant calls within the `callback` will deadlock. pub fn with_output_info T>(&self, callback: F) -> T { let guard = self.0.lock().unwrap(); callback(&guard) } } #[derive(Debug, Clone)] pub struct Mode { /// Number of pixels of this mode in format `(width, height)` /// /// for example `(1920, 1080)` pub dimensions: (i32, i32), /// Refresh rate for this mode. /// /// The refresh rate is specified in terms of millihertz (mHz). To convert approximately to Hertz, /// divide the value by 1000. /// /// This value could be zero if an output has no correct refresh rate, such as a virtual output. pub refresh_rate: i32, /// Whether this is the current mode for this output. /// /// Per the Wayland protocol, non-current modes are deprecated and clients should not rely on deprecated /// modes. pub current: bool, /// Whether this is the preferred mode for this output. pub preferred: bool, } impl Display for Mode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if self.current { write!(f, "(current) ")?; } if self.preferred { write!(f, "(preferred) ")?; } write!( f, "{}×{}px @ {}.{:03} Hz", self.dimensions.0, self.dimensions.1, // Print the refresh rate in hertz since it is more familiar unit. self.refresh_rate / 1000, self.refresh_rate % 1000 ) } } /// Information about an output. #[derive(Debug, Clone)] #[non_exhaustive] pub struct OutputInfo { /// The id of the output. /// /// This corresponds to the global `name` of the wl_output. pub id: u32, /// The model name of this output as advertised by the server. pub model: String, /// The make name of this output as advertised by the server. pub make: String, /// Location of the top-left corner of this output in compositor space. /// /// Note that the compositor may decide to always report (0,0) if it decides clients are not allowed to /// know this information. pub location: (i32, i32), /// Physical dimensions of this output, in millimeters. /// /// This value may be set to (0, 0) if a physical size does not make sense for the output (e.g. projectors /// and virtual outputs). pub physical_size: (i32, i32), /// The subpixel layout for this output. pub subpixel: Subpixel, /// The current transformation applied to this output /// /// You can pre-render your buffers taking this information into account and advertising it via /// `wl_buffer.set_transform` for better performance. pub transform: Transform, /// The scaling factor of this output /// /// Any buffer whose scaling factor does not match the one of the output it is displayed on will be /// rescaled accordingly. /// /// For example, a buffer of scaling factor 1 will be doubled in size if the output scaling factor is 2. /// /// You can pre-render your buffers taking this information into account and advertising it via /// `wl_surface.set_buffer_scale` so you may advertise a higher detail image. pub scale_factor: i32, /// Possible modes for an output. pub modes: Vec, /// Logical position in global compositor space pub logical_position: Option<(i32, i32)>, /// Logical size in global compositor space pub logical_size: Option<(i32, i32)>, /// The name of the this output as advertised by the surface. /// /// Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do not assume that the name is a /// reflection of an underlying DRM connector, X11 connection, etc. /// /// Compositors are not required to provide a name for the output and the value may be [`None`]. /// /// The name will be [`None`] if the compositor does not support version 4 of the wl-output protocol or /// version 2 of the zxdg-output-v1 protocol. pub name: Option, /// The description of this output as advertised by the server /// /// The description is a UTF-8 string with no convention defined for its contents. The description is not /// guaranteed to be unique among all wl_output globals. Examples might include 'Foocorp 11" Display' or /// 'Virtual X11 output via :1'. /// /// Compositors are not required to provide a description of the output and the value may be [`None`]. /// /// The value will be [`None`] if the compositor does not support version 4 of the wl-output /// protocol, version 2 of the zxdg-output-v1 protocol. pub description: Option, } #[macro_export] macro_rules! delegate_output { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_output::WlOutput: $crate::output::OutputData ] => $crate::output::OutputState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1: $crate::globals::GlobalData ] => $crate::output::OutputState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::ZxdgOutputV1: $crate::output::OutputData ] => $crate::output::OutputState); }; } impl Dispatch for OutputState where D: Dispatch + OutputHandler + 'static, { fn event( state: &mut D, output: &wl_output::WlOutput, event: wl_output::Event, data: &OutputData, conn: &Connection, qh: &QueueHandle, ) { let inner = state .output_state() .outputs .iter_mut() .find(|inner| &inner.wl_output == output) .expect("Received event for dead output"); match event { wl_output::Event::Geometry { x, y, physical_width, physical_height, subpixel, make, model, transform, } => { inner.pending_info.location = (x, y); inner.pending_info.physical_size = (physical_width, physical_height); inner.pending_info.subpixel = match subpixel { WEnum::Value(subpixel) => subpixel, WEnum::Unknown(_) => todo!("Warn about invalid subpixel value"), }; inner.pending_info.make = make; inner.pending_info.model = model; inner.pending_info.transform = match transform { WEnum::Value(subpixel) => subpixel, WEnum::Unknown(_) => todo!("Warn about invalid transform value"), }; inner.pending_wl = true; } wl_output::Event::Mode { flags, width, height, refresh } => { // Remove the old mode inner.pending_info.modes.retain(|mode| { mode.dimensions != (width, height) || mode.refresh_rate != refresh }); let flags = match flags { WEnum::Value(flags) => flags, WEnum::Unknown(_) => panic!("Invalid flags"), }; let current = flags.contains(wl_output::Mode::Current); let preferred = flags.contains(wl_output::Mode::Preferred); // Any mode that isn't current is deprecated, let's deprecate any existing modes that may be // marked as current. // // If a new mode is advertised as preferred, then mark the existing preferred mode as not. for mode in &mut inner.pending_info.modes { // This mode is no longer preferred. if preferred { mode.preferred = false; } // This mode is no longer current. if current { mode.current = false; } } // Now create the new mode. inner.pending_info.modes.push(Mode { dimensions: (width, height), refresh_rate: refresh, current, preferred, }); inner.pending_wl = true; } wl_output::Event::Scale { factor } => { inner.pending_info.scale_factor = factor; inner.pending_wl = true; } wl_output::Event::Name { name } => { inner.pending_info.name = Some(name); inner.pending_wl = true; } wl_output::Event::Description { description } => { inner.pending_info.description = Some(description); inner.pending_wl = true; } wl_output::Event::Done => { let info = inner.pending_info.clone(); inner.current_info = Some(info.clone()); inner.pending_wl = false; // Set the user data, see if we need to run scale callbacks let run_callbacks = data.set(info); // Don't call `new_output` until we have xdg output info if !inner.pending_xdg { if inner.just_created { inner.just_created = false; state.new_output(conn, qh, output.clone()); } else { state.update_output(conn, qh, output.clone()); } } if run_callbacks { let callbacks = state.output_state().callbacks.clone(); for cb in callbacks { if let Some(cb) = cb.upgrade() { cb(state, conn, qh, output); } } } } _ => unreachable!(), } } } impl Dispatch for OutputState where D: Dispatch + OutputHandler, { fn event( _: &mut D, _: &zxdg_output_manager_v1::ZxdgOutputManagerV1, _: zxdg_output_manager_v1::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("zxdg_output_manager_v1 has no events") } } impl Dispatch for OutputState where D: Dispatch + OutputHandler, { fn event( state: &mut D, output: &zxdg_output_v1::ZxdgOutputV1, event: zxdg_output_v1::Event, data: &OutputData, conn: &Connection, qh: &QueueHandle, ) { let inner = state .output_state() .outputs .iter_mut() .find(|inner| inner.xdg_output.as_ref() == Some(output)) .expect("Received event for dead output"); // zxdg_output_v1::done is deprecated in version 3. So we only need // to wait for wl_output::done, once we get any xdg output info. if output.version() >= 3 { inner.pending_xdg = false; } match event { zxdg_output_v1::Event::LogicalPosition { x, y } => { inner.pending_info.logical_position = Some((x, y)); if output.version() < 3 { inner.pending_xdg = true; } } zxdg_output_v1::Event::LogicalSize { width, height } => { inner.pending_info.logical_size = Some((width, height)); if output.version() < 3 { inner.pending_xdg = true; } } zxdg_output_v1::Event::Name { name } => { if inner.wl_output.version() < 4 { inner.pending_info.name = Some(name); } if output.version() < 3 { inner.pending_xdg = true; } } zxdg_output_v1::Event::Description { description } => { if inner.wl_output.version() < 4 { inner.pending_info.description = Some(description); } if output.version() < 3 { inner.pending_xdg = true; } } zxdg_output_v1::Event::Done => { // This event is deprecated starting in version 3, wl_output::done should be sent instead. if output.version() < 3 { let info = inner.pending_info.clone(); inner.current_info = Some(info.clone()); inner.pending_xdg = false; // Set the user data data.set(info); let pending_wl = inner.pending_wl; let just_created = inner.just_created; let output = inner.wl_output.clone(); if just_created { inner.just_created = false; } if !pending_wl { if just_created { state.new_output(conn, qh, output); } else { state.update_output(conn, qh, output); } } } } _ => unreachable!(), } } } impl RegistryHandler for OutputState where D: Dispatch + Dispatch + Dispatch + OutputHandler + ProvidesRegistryState + 'static, { fn new_global( data: &mut D, _: &Connection, qh: &QueueHandle, name: u32, interface: &str, _version: u32, ) { if interface == "wl_output" { let output = data .registry() .bind_specific(qh, name, 1..=4, OutputData::new(name)) .expect("Failed to bind global"); data.output_state().setup(output, qh); } } fn remove_global( data: &mut D, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, ) { if interface == "wl_output" { let output = data .output_state() .outputs .iter() .position(|o| o.name == name) .expect("Removed non-existing output"); let wl_output = data.output_state().outputs[output].wl_output.clone(); data.output_destroyed(conn, qh, wl_output); let output = data.output_state().outputs.remove(output); if let Some(xdg_output) = &output.xdg_output { xdg_output.destroy(); } if output.wl_output.version() >= 3 { output.wl_output.release(); } } } } impl OutputInfo { fn new(id: u32) -> OutputInfo { OutputInfo { id, model: String::new(), make: String::new(), location: (0, 0), physical_size: (0, 0), subpixel: Subpixel::Unknown, transform: Transform::Normal, scale_factor: 1, modes: vec![], logical_position: None, logical_size: None, name: None, description: None, } } } impl OutputData { pub(crate) fn set(&self, info: OutputInfo) -> bool { let mut guard = self.0.lock().unwrap(); let rv = guard.scale_factor != info.scale_factor; *guard = info; rv } } #[derive(Debug)] struct OutputInner { /// The name of the wl_output global. name: u32, wl_output: wl_output::WlOutput, xdg_output: Option, /// Whether this output was just created and has not an event yet. just_created: bool, current_info: Option, pending_info: OutputInfo, pending_wl: bool, pending_xdg: bool, } smithay-client-toolkit-0.18.0/src/primary_selection/device.rs000064400000000000000000000113511046102023000224540ustar 00000000000000use std::sync::{Arc, Mutex}; use crate::reexports::client::{ event_created_child, protocol::wl_seat::WlSeat, Connection, Dispatch, Proxy, QueueHandle, }; use crate::reexports::protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, }; use super::{ offer::{PrimarySelectionOffer, PrimarySelectionOfferData}, PrimarySelectionManagerState, }; pub trait PrimarySelectionDeviceHandler: Sized { /// The new selection is received. /// /// The given primary selection device could be used to identify [`PrimarySelectionDevice`]. fn selection( &mut self, conn: &Connection, qh: &QueueHandle, primary_selection_device: &ZwpPrimarySelectionDeviceV1, ); } #[derive(Debug)] pub struct PrimarySelectionDevice { pub(crate) device: ZwpPrimarySelectionDeviceV1, } impl PrimarySelectionDevice { /// Remove the currently active selection. /// /// The passed `serial` is the serial of the input event. pub fn unset_selection(&self, serial: u32) { self.device.set_selection(None, serial); } /// Get the underlying data. pub fn data(&self) -> &PrimarySelectionDeviceData { self.device.data::().unwrap() } pub fn inner(&self) -> &ZwpPrimarySelectionDeviceV1 { &self.device } } impl Drop for PrimarySelectionDevice { fn drop(&mut self) { self.device.destroy(); } } impl Dispatch for PrimarySelectionManagerState where State: Dispatch + Dispatch + PrimarySelectionDeviceHandler + 'static, { event_created_child!(State, ZwpPrimarySelectionDeviceV1, [ 0 => (ZwpPrimarySelectionOfferV1, PrimarySelectionOfferData::default()) ]); fn event( state: &mut State, proxy: &ZwpPrimarySelectionDeviceV1, event: ::Event, data: &PrimarySelectionDeviceData, conn: &Connection, qhandle: &QueueHandle, ) { use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::Event; let mut data = data.inner.lock().unwrap(); match event { Event::DataOffer { offer } => { // Try to resist faulty compositors. if let Some(pending_offer) = data.pending_offer.take() { pending_offer.destroy(); } data.pending_offer = Some(offer); } Event::Selection { id } => { // We must drop the current offer regardless. if let Some(offer) = data.offer.take() { offer.destroy(); } if id == data.pending_offer { data.offer = data.pending_offer.take(); } else { // Remove the pending offer, assign the new delivered one. if let Some(offer) = data.pending_offer.take() { offer.destroy() } data.offer = id; } // Release the user data lock before calling into user. drop(data); state.selection(conn, qhandle, proxy); } _ => unreachable!(), } } } /// The user data associated with the [`ZwpPrimarySelectionDeviceV1`]. #[derive(Debug)] pub struct PrimarySelectionDeviceData { /// The seat associated with this device. seat: WlSeat, /// The inner mutable storage. inner: Arc>, } impl PrimarySelectionDeviceData { pub(crate) fn new(seat: WlSeat) -> Self { Self { seat, inner: Default::default() } } /// The seat used to create this primary selection device. pub fn seat(&self) -> &WlSeat { &self.seat } /// The active selection offer. pub fn selection_offer(&self) -> Option { self.inner .lock() .unwrap() .offer .as_ref() .map(|offer| PrimarySelectionOffer { offer: offer.clone() }) } } #[derive(Debug, Default)] struct PrimarySelectionDeviceDataInner { /// The offer is valid until either `NULL` or new selection is received via the /// `selection` event. offer: Option, /// The offer we've got in `offer` event, but not finished it in `selection`. pending_offer: Option, } smithay-client-toolkit-0.18.0/src/primary_selection/mod.rs000064400000000000000000000110261046102023000217730ustar 00000000000000use crate::globals::GlobalData; use crate::reexports::client::{ globals::{BindError, GlobalList}, protocol::wl_seat::WlSeat, Dispatch, QueueHandle, }; use crate::reexports::protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1, zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }; pub mod device; pub mod offer; pub mod selection; use self::device::{PrimarySelectionDevice, PrimarySelectionDeviceData}; use selection::PrimarySelectionSource; #[derive(Debug)] pub struct PrimarySelectionManagerState { manager: ZwpPrimarySelectionDeviceManagerV1, } impl PrimarySelectionManagerState { pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where State: Dispatch + 'static, { let manager = globals.bind(qh, 1..=1, GlobalData)?; Ok(Self { manager }) } /// The underlying wayland object. pub fn primary_selection_manager(&self) -> &ZwpPrimarySelectionDeviceManagerV1 { &self.manager } /// Create a primary selection source. pub fn create_selection_source( &self, qh: &QueueHandle, mime_types: I, ) -> PrimarySelectionSource where State: Dispatch + 'static, I: IntoIterator, T: ToString, { let source = self.manager.create_source(qh, GlobalData); for mime_type in mime_types { source.offer(mime_type.to_string()); } PrimarySelectionSource::new(source) } /// Get the primary selection data device for the given seat. pub fn get_selection_device( &self, qh: &QueueHandle, seat: &WlSeat, ) -> PrimarySelectionDevice where State: Dispatch + 'static, { PrimarySelectionDevice { device: self.manager.get_device( seat, qh, PrimarySelectionDeviceData::new(seat.clone()), ), } } } impl Drop for PrimarySelectionManagerState { fn drop(&mut self) { self.manager.destroy(); } } impl Dispatch for PrimarySelectionManagerState where D: Dispatch, { fn event( _: &mut D, _: &ZwpPrimarySelectionDeviceManagerV1, _: ::Event, _: &GlobalData, _: &wayland_client::Connection, _: &QueueHandle, ) { unreachable!("zwp_primary_selection_device_manager_v1 has no events") } } #[macro_export] macro_rules! delegate_primary_selection { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1: $crate::globals::GlobalData ] => $crate::primary_selection::PrimarySelectionManagerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1: $crate::primary_selection::device::PrimarySelectionDeviceData ] => $crate::primary_selection::PrimarySelectionManagerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1: $crate::primary_selection::offer::PrimarySelectionOfferData ] => $crate::primary_selection::PrimarySelectionManagerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1: $crate::globals::GlobalData ] => $crate::primary_selection::PrimarySelectionManagerState); }; } smithay-client-toolkit-0.18.0/src/primary_selection/offer.rs000064400000000000000000000055371046102023000223270ustar 00000000000000use std::{ os::unix::io::{AsFd, OwnedFd}, sync::Mutex, }; use crate::reexports::client::{Connection, Dispatch, QueueHandle, Proxy}; use crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1; use crate::data_device_manager::ReadPipe; use super::PrimarySelectionManagerState; /// Wrapper around the [`ZwpPrimarySelectionOfferV1`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PrimarySelectionOffer { pub(crate) offer: ZwpPrimarySelectionOfferV1, } impl PrimarySelectionOffer { /// Inspect the mime types available on the given offer. pub fn with_mime_types T>(&self, callback: F) -> T { let mime_types = self.offer.data::().unwrap().mimes.lock().unwrap(); callback(mime_types.as_ref()) } /// Request to receive the data of a given mime type. /// /// You can call this function several times. /// /// Note that you should *not* read the contents right away in a /// blocking way, as you may deadlock your application doing so. /// At least make sure you flush your events to the server before /// doing so. /// /// Fails if too many file descriptors were already open and a pipe /// could not be created. pub fn receive(&self, mime_type: String) -> std::io::Result { use rustix::pipe::{pipe_with, PipeFlags}; // create a pipe let (readfd, writefd) = pipe_with(PipeFlags::CLOEXEC)?; self.receive_to_fd(mime_type, writefd); Ok(ReadPipe::from(readfd)) } /// Request to receive the data of a given mime type, writen to `writefd`. /// /// The provided file destructor must be a valid FD for writing, and will be closed /// once the contents are written. pub fn receive_to_fd(&self, mime_type: String, writefd: OwnedFd) { self.offer.receive(mime_type, writefd.as_fd()); } } impl Dispatch for PrimarySelectionManagerState where State: Dispatch, { fn event( _: &mut State, _: &ZwpPrimarySelectionOfferV1, event: ::Event, data: &PrimarySelectionOfferData, _: &Connection, _: &QueueHandle, ) { use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::Event; match event { Event::Offer { mime_type } => { data.mimes.lock().unwrap().push(mime_type); } _ => unreachable!(), } } } /// The data associated with the [`ZwpPrimarySelectionOfferV1`]. #[derive(Debug, Default)] pub struct PrimarySelectionOfferData { mimes: Mutex>, } smithay-client-toolkit-0.18.0/src/primary_selection/selection.rs000064400000000000000000000053671046102023000232140ustar 00000000000000use crate::reexports::client::{Connection, Dispatch, QueueHandle}; use crate::reexports::protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1; use crate::{data_device_manager::WritePipe, globals::GlobalData}; use super::{device::PrimarySelectionDevice, PrimarySelectionManagerState}; /// Handler trait for `PrimarySelectionSource` events. /// /// The functions defined in this trait are called as DataSource events are received from the compositor. pub trait PrimarySelectionSourceHandler: Sized { /// The client has requested the data for this source to be sent. /// Send the data, then close the fd. fn send_request( &mut self, conn: &Connection, qh: &QueueHandle, source: &ZwpPrimarySelectionSourceV1, mime: String, write_pipe: WritePipe, ); /// The data source is no longer valid /// Cleanup & destroy this resource fn cancelled( &mut self, conn: &Connection, qh: &QueueHandle, source: &ZwpPrimarySelectionSourceV1, ); } /// Wrapper around the [`ZwpPrimarySelectionSourceV1`]. #[derive(Debug, PartialEq, Eq)] pub struct PrimarySelectionSource { source: ZwpPrimarySelectionSourceV1, } impl PrimarySelectionSource { pub(crate) fn new(source: ZwpPrimarySelectionSourceV1) -> Self { Self { source } } /// Set the selection on the given [`PrimarySelectionDevice`]. pub fn set_selection(&self, device: &PrimarySelectionDevice, serial: u32) { device.device.set_selection(Some(&self.source), serial); } /// The underlying wayland object. pub fn inner(&self) -> &ZwpPrimarySelectionSourceV1 { &self.source } } impl Drop for PrimarySelectionSource { fn drop(&mut self) { self.source.destroy(); } } impl Dispatch for PrimarySelectionManagerState where State: Dispatch + PrimarySelectionSourceHandler, { fn event( state: &mut State, proxy: &ZwpPrimarySelectionSourceV1, event: ::Event, _: &GlobalData, conn: &wayland_client::Connection, qhandle: &QueueHandle, ) { use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_source_v1::Event as PrimarySelectionSourceEvent; match event { PrimarySelectionSourceEvent::Send { mime_type, fd } => { state.send_request(conn, qhandle, proxy, mime_type, fd.into()) } PrimarySelectionSourceEvent::Cancelled => state.cancelled(conn, qhandle, proxy), _ => unreachable!(), } } } smithay-client-toolkit-0.18.0/src/registry.rs000064400000000000000000000446141046102023000173450ustar 00000000000000//! Utilities for binding globals with [`wl_registry`] in delegates. //! //! This module is based around the [`RegistryHandler`] trait and [`RegistryState`]. //! //! [`RegistryState`] provides an interface to bind globals regularly, creating an object with each new //! instantiation or caching bound globals to prevent duplicate object instances from being created. Binding //! a global regularly is accomplished through [`RegistryState::bind_one`]. //! //! The [`delegate_registry`](crate::delegate_registry) macro is used to implement handling for [`wl_registry`]. //! //! ## Sample implementation of [`RegistryHandler`] //! //! ``` //! use smithay_client_toolkit::reexports::client::{ //! Connection, Dispatch, QueueHandle, //! delegate_dispatch, //! globals::GlobalList, //! protocol::wl_output, //! }; //! //! use smithay_client_toolkit::registry::{ //! GlobalProxy, ProvidesRegistryState, RegistryHandler, RegistryState, //! }; //! //! struct ExampleApp { //! /// The registry state is needed to use the global abstractions. //! registry_state: RegistryState, //! /// This is a type we want to delegate global handling to. //! delegate_that_wants_registry: Delegate, //! } //! //! /// The delegate a global should be provided to. //! struct Delegate { //! outputs: Vec, //! } //! //! // When implementing RegistryHandler, you must be able to dispatch any type you could bind using the registry state. //! impl RegistryHandler for Delegate //! where //! // In order to bind a global, you must statically assert the global may be handled with the data type. //! D: Dispatch //! // ProvidesRegistryState provides a function to access the RegistryState within the impl. //! + ProvidesRegistryState //! // We need some way to access our part of the application's state. This uses AsMut, //! // but you may prefer to create your own trait to avoid making .as_mut() ambiguous. //! + AsMut //! + 'static, //! { //! /// New global added after initial enumeration. //! fn new_global( //! data: &mut D, //! conn: &Connection, //! qh: &QueueHandle, //! name: u32, //! interface: &str, //! version: u32, //! ) { //! if interface == "wl_output" { //! // Bind `wl_output` with newest version from 1 to 4 the compositor supports //! let output = data.registry().bind_specific(qh, name, 1..=4, ()).unwrap(); //! data.as_mut().outputs.push(output); //! } //! //! // You could either handle errors here or when attempting to use the interface. Most //! // Wayland protocols are optional, so if your application can function without a //! // protocol it should try to do so; the From impl of GlobalProxy is written to make //! // this straightforward. //! } //! } //! ``` use crate::{error::GlobalError, globals::ProvidesBoundGlobal}; use wayland_client::{ globals::{BindError, Global, GlobalList, GlobalListContents}, protocol::wl_registry, Connection, Dispatch, Proxy, QueueHandle, }; /// A trait implemented by modular parts of a smithay's client toolkit and protocol delegates that may be used /// to receive notification of a global being created or destroyed. /// /// Delegates that choose to implement this trait may be used in [`registry_handlers`] which /// automatically notifies delegates about the creation and destruction of globals. /// /// [`registry_handlers`]: crate::registry_handlers /// /// Note that in order to delegate registry handling to a type which implements this trait, your `D` data type /// must implement [`ProvidesRegistryState`]. pub trait RegistryHandler where D: ProvidesRegistryState, { /// Called when a new global has been advertised by the compositor. /// /// The provided registry handle may be used to bind the global. This is not called during /// initial enumeration of globals. It is primarily useful for multi-instance globals such as /// `wl_output` and `wl_seat`. /// /// The default implementation does nothing. fn new_global( data: &mut D, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, version: u32, ) { let _ = (data, conn, qh, name, interface, version); } /// Called when a global has been destroyed by the compositor. /// /// The default implementation does nothing. fn remove_global( data: &mut D, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, ) { let _ = (data, conn, qh, name, interface); } } /// Trait which asserts a data type may provide a mutable reference to the registry state. /// /// Typically this trait will be required by delegates or [`RegistryHandler`] implementations which need /// to access the registry utilities provided by Smithay's client toolkit. pub trait ProvidesRegistryState: Sized { /// Returns a mutable reference to the registry state. fn registry(&mut self) -> &mut RegistryState; /// Called when a new global has been advertised by the compositor. /// /// This is not called during initial global enumeration. fn runtime_add_global( &mut self, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, version: u32, ); /// Called when a global has been destroyed by the compositor. fn runtime_remove_global( &mut self, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, ); } /// State object associated with the registry handling for smithay's client toolkit. /// /// This object provides utilities to cache bound globals that are needed by multiple modules. #[derive(Debug)] pub struct RegistryState { registry: wl_registry::WlRegistry, globals: Vec, } impl RegistryState { /// Creates a new registry handle. /// /// This type may be used to bind globals as they are advertised. pub fn new(global_list: &GlobalList) -> Self { let registry = global_list.registry().clone(); let globals = global_list.contents().clone_list(); RegistryState { registry, globals } } pub fn registry(&self) -> &wl_registry::WlRegistry { &self.registry } /// Returns an iterator over all globals. /// /// This list may change if the compositor adds or removes globals after initial /// enumeration. /// /// No guarantees are provided about the ordering of the globals in this iterator. pub fn globals(&self) -> impl Iterator + '_ { self.globals.iter() } /// Returns an iterator over all globals implementing the given interface. /// /// This may be more efficient than searching [Self::globals]. pub fn globals_by_interface<'a>( &'a self, interface: &'a str, ) -> impl Iterator + 'a { self.globals.iter().filter(move |g| g.interface == interface) } /// Binds a global, returning a new object associated with the global. /// /// This should not be used to bind globals that have multiple instances such as `wl_output`; /// use [Self::bind_all] instead. pub fn bind_one( &self, qh: &QueueHandle, version: std::ops::RangeInclusive, udata: U, ) -> Result where D: Dispatch + 'static, I: Proxy + 'static, U: Send + Sync + 'static, { bind_one(&self.registry, &self.globals, qh, version, udata) } /// Binds a global, returning a new object associated with the global. /// /// This binds a specific object by its name as provided by the [RegistryHandler::new_global] /// callback. pub fn bind_specific( &self, qh: &QueueHandle, name: u32, version: std::ops::RangeInclusive, udata: U, ) -> Result where D: Dispatch + 'static, I: Proxy + 'static, U: Send + Sync + 'static, { let iface = I::interface(); if *version.end() > iface.version { // This is a panic because it's a compile-time programmer error, not a runtime error. panic!("Maximum version ({}) was higher than the proxy's maximum version ({}); outdated wayland XML files?", version.end(), iface.version); } // Optimize for runtime_add_global which will use the last entry for global in self.globals.iter().rev() { if global.name != name || global.interface != iface.name { continue; } if global.version < *version.start() { return Err(BindError::UnsupportedVersion); } let version = global.version.min(*version.end()); let proxy = self.registry.bind(global.name, version, qh, udata); log::debug!(target: "sctk", "Bound new global [{}] {} v{}", global.name, iface.name, version); return Ok(proxy); } Err(BindError::NotPresent) } /// Binds all globals with a given interface. pub fn bind_all( &self, qh: &QueueHandle, version: std::ops::RangeInclusive, make_udata: F, ) -> Result, BindError> where D: Dispatch + 'static, I: Proxy + 'static, F: FnMut(u32) -> U, U: Send + Sync + 'static, { bind_all(&self.registry, &self.globals, qh, version, make_udata) } } /// Delegates the handling of [`wl_registry`]. /// /// Anything which implements [`RegistryHandler`] may be used in the delegate. /// /// ## Usage /// /// ``` /// use smithay_client_toolkit::{ /// delegate_registry, delegate_shm, registry_handlers, /// shm::{ShmHandler, Shm}, /// }; /// /// struct ExampleApp { /// shm_state: Shm, /// } /// /// // Here is the implementation of wl_shm to compile: /// delegate_shm!(ExampleApp); /// /// impl ShmHandler for ExampleApp { /// fn shm_state(&mut self) -> &mut Shm { /// &mut self.shm_state /// } /// } /// ``` #[macro_export] macro_rules! delegate_registry { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_registry::WlRegistry: $crate::reexports::client::globals::GlobalListContents ] => $crate::registry::RegistryState ); }; } impl Dispatch for RegistryState where D: Dispatch + ProvidesRegistryState, { fn event( state: &mut D, _: &wl_registry::WlRegistry, event: wl_registry::Event, _: &GlobalListContents, conn: &Connection, qh: &QueueHandle, ) { match event { wl_registry::Event::Global { name, interface, version } => { let iface = interface.clone(); state.registry().globals.push(Global { name, interface, version }); state.runtime_add_global(conn, qh, name, &iface, version); } wl_registry::Event::GlobalRemove { name } => { if let Some(i) = state.registry().globals.iter().position(|g| g.name == name) { let global = state.registry().globals.swap_remove(i); state.runtime_remove_global(conn, qh, name, &global.interface); } } _ => unreachable!("wl_registry is frozen"), } } } /// A helper for storing a bound global. /// /// This helper is intended to simplify the implementation of [RegistryHandler] for state objects /// that cache a bound global. #[derive(Debug)] pub enum GlobalProxy { /// The requested global was not present after a complete enumeration. NotPresent, /// The cached global. Bound(I), } impl From> for GlobalProxy { fn from(r: Result) -> Self { match r { Ok(proxy) => GlobalProxy::Bound(proxy), Err(_) => GlobalProxy::NotPresent, } } } impl GlobalProxy { pub fn get(&self) -> Result<&I, GlobalError> { self.with_min_version(0) } pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { match self { GlobalProxy::Bound(proxy) => { if proxy.version() < min_version { Err(GlobalError::InvalidVersion { name: I::interface().name, required: min_version, available: proxy.version(), }) } else { Ok(proxy) } } GlobalProxy::NotPresent => Err(GlobalError::MissingGlobal(I::interface().name)), } } } #[derive(Debug)] pub struct SimpleGlobal { proxy: GlobalProxy, } impl SimpleGlobal { pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where State: Dispatch + 'static, { let proxy = globals.bind(qh, 0..=MAX_VERSION, ())?; Ok(Self { proxy: GlobalProxy::Bound(proxy) }) } pub fn get(&self) -> Result<&I, GlobalError> { self.proxy.get() } pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { self.proxy.with_min_version(min_version) } } impl ProvidesBoundGlobal for SimpleGlobal { fn bound_global(&self) -> Result { self.proxy.get().cloned() } } impl Dispatch for SimpleGlobal where D: Dispatch, I: Proxy, { fn event(_: &mut D, _: &I, _: ::Event, _: &(), _: &Connection, _: &QueueHandle) { unreachable!("SimpleGlobal is not suitable for {} which has events", I::interface().name); } } /// Binds all globals with a given interface. pub(crate) fn bind_all( registry: &wl_registry::WlRegistry, globals: &[Global], qh: &QueueHandle, version: std::ops::RangeInclusive, mut make_udata: F, ) -> Result, BindError> where D: Dispatch + 'static, I: Proxy + 'static, F: FnMut(u32) -> U, U: Send + Sync + 'static, { let iface = I::interface(); if *version.end() > iface.version { // This is a panic because it's a compile-time programmer error, not a runtime error. panic!("Maximum version ({}) was higher than the proxy's maximum version ({}); outdated wayland XML files?", version.end(), iface.version); } let mut rv = Vec::new(); for global in globals { if global.interface != iface.name { continue; } if global.version < *version.start() { return Err(BindError::UnsupportedVersion); } let version = global.version.min(*version.end()); let udata = make_udata(global.name); let proxy = registry.bind(global.name, version, qh, udata); log::debug!(target: "sctk", "Bound new global [{}] {} v{}", global.name, iface.name, version); rv.push(proxy); } Ok(rv) } /// Binds a global, returning a new object associated with the global. pub(crate) fn bind_one( registry: &wl_registry::WlRegistry, globals: &[Global], qh: &QueueHandle, version: std::ops::RangeInclusive, udata: U, ) -> Result where D: Dispatch + 'static, I: Proxy + 'static, U: Send + Sync + 'static, { let iface = I::interface(); if *version.end() > iface.version { // This is a panic because it's a compile-time programmer error, not a runtime error. panic!("Maximum version ({}) of {} was higher than the proxy's maximum version ({}); outdated wayland XML files?", version.end(), iface.name, iface.version); } if *version.end() < iface.version { // This is a reminder to evaluate the new API and bump the maximum in order to be able // to use new APIs. Actual use of new APIs still needs runtime version checks. log::trace!(target: "sctk", "Version {} of {} is available; binding is currently limited to {}", iface.version, iface.name, version.end()); } for global in globals { if global.interface != iface.name { continue; } if global.version < *version.start() { return Err(BindError::UnsupportedVersion); } let version = global.version.min(*version.end()); let proxy = registry.bind(global.name, version, qh, udata); log::debug!(target: "sctk", "Bound new global [{}] {} v{}", global.name, iface.name, version); return Ok(proxy); } Err(BindError::NotPresent) } #[macro_export] macro_rules! delegate_simple { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty:ty, $iface:ty, $max:expr) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $iface: () ] => $crate::registry::SimpleGlobal<$iface, $max> ); }; } /// A helper macro for implementing [`ProvidesRegistryState`]. /// /// See [`delegate_registry`] for an example. #[macro_export] macro_rules! registry_handlers { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $($ty:ty),* $(,)?) => { fn runtime_add_global( &mut self, conn: &$crate::reexports::client::Connection, qh: &$crate::reexports::client::QueueHandle, name: u32, interface: &str, version: u32, ) { $( <$ty as $crate::registry::RegistryHandler>::new_global(self, conn, qh, name, interface, version); )* } fn runtime_remove_global( &mut self, conn: &$crate::reexports::client::Connection, qh: &$crate::reexports::client::QueueHandle, name: u32, interface: &str, ) { $( <$ty as $crate::registry::RegistryHandler>::remove_global(self, conn, qh, name, interface); )* } } } smithay-client-toolkit-0.18.0/src/seat/keyboard/mod.rs000064400000000000000000001006001046102023000207740ustar 00000000000000use std::{ convert::TryInto, env, fmt::Debug, marker::PhantomData, num::NonZeroU32, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, time::Duration, }; #[doc(inline)] pub use xkeysym::{KeyCode, Keysym}; #[cfg(feature = "calloop")] use calloop::timer::{TimeoutAction, Timer}; use wayland_client::{ protocol::{wl_keyboard, wl_seat, wl_surface}, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use xkbcommon::xkb; #[cfg(feature = "calloop")] use repeat::{RepeatData, RepeatedKey}; use super::{Capability, SeatError, SeatHandler, SeatState}; #[cfg(feature = "calloop")] pub mod repeat; /// Error when creating a keyboard. #[must_use] #[derive(Debug, thiserror::Error)] pub enum KeyboardError { /// Seat error. #[error(transparent)] Seat(#[from] SeatError), /// The specified keymap (RMLVO) is not valid. #[error("invalid keymap was specified")] InvalidKeymap, } impl SeatState { /// Creates a keyboard from a seat. /// /// This keyboard implementation uses libxkbcommon for the keymap. /// /// Typically the compositor will provide a keymap, but you may specify your own keymap using the `rmlvo` /// field. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a keyboard. pub fn get_keyboard( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, rmlvo: Option, ) -> Result where D: Dispatch> + SeatHandler + KeyboardHandler + 'static, { let udata = match rmlvo { Some(rmlvo) => KeyboardData::from_rmlvo(seat.clone(), rmlvo)?, None => KeyboardData::new(seat.clone()), }; self.get_keyboard_with_data(qh, seat, udata) } /// Creates a keyboard from a seat. /// /// This keyboard implementation uses libxkbcommon for the keymap. /// /// Typically the compositor will provide a keymap, but you may specify your own keymap using the `rmlvo` /// field. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a keyboard. pub fn get_keyboard_with_data( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, udata: U, ) -> Result where D: Dispatch + SeatHandler + KeyboardHandler + 'static, U: KeyboardDataExt + 'static, { let inner = self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; if !inner.data.has_keyboard.load(Ordering::SeqCst) { return Err(SeatError::UnsupportedCapability(Capability::Keyboard).into()); } Ok(seat.get_keyboard(qh, udata)) } } /// Wrapper around a libxkbcommon keymap #[allow(missing_debug_implementations)] pub struct Keymap<'a>(&'a xkb::Keymap); impl<'a> Keymap<'a> { /// Get keymap as string in text format. The keymap should always be valid. pub fn as_string(&self) -> String { self.0.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1) } } /// Handler trait for keyboard input. /// /// The functions defined in this trait are called as keyboard events are received from the compositor. pub trait KeyboardHandler: Sized { /// The keyboard has entered a surface. /// /// When called, you may assume the specified surface has keyboard focus. /// /// When a keyboard enters a surface, the `raw` and `keysym` fields indicate which keys are currently /// pressed. #[allow(clippy::too_many_arguments)] fn enter( &mut self, conn: &Connection, qh: &QueueHandle, keyboard: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, serial: u32, raw: &[u32], keysyms: &[Keysym], ); /// The keyboard has left a surface. /// /// When called, keyboard focus leaves the specified surface. /// /// All currently held down keys are released when this event occurs. fn leave( &mut self, conn: &Connection, qh: &QueueHandle, keyboard: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, serial: u32, ); /// A key has been pressed on the keyboard. /// /// The key will repeat if there is no other press event afterwards or the key is released. fn press_key( &mut self, conn: &Connection, qh: &QueueHandle, keyboard: &wl_keyboard::WlKeyboard, serial: u32, event: KeyEvent, ); /// A key has been released. /// /// This stops the key from being repeated if the key is the last key which was pressed. fn release_key( &mut self, conn: &Connection, qh: &QueueHandle, keyboard: &wl_keyboard::WlKeyboard, serial: u32, event: KeyEvent, ); /// Keyboard modifiers have been updated. /// /// This happens when one of the modifier keys, such as "Shift", "Control" or "Alt" is pressed or /// released. fn update_modifiers( &mut self, conn: &Connection, qh: &QueueHandle, keyboard: &wl_keyboard::WlKeyboard, serial: u32, modifiers: Modifiers, ); /// The keyboard has updated the rate and delay between repeating key inputs. /// /// This function does nothing by default but is provided if a repeat mechanism outside of calloop is\ /// used. fn update_repeat_info( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &wl_keyboard::WlKeyboard, _info: RepeatInfo, ) { } /// Keyboard keymap has been updated. /// /// `keymap.as_string()` can be used get the keymap as a string. It cannot be exposed directly /// as an `xkbcommon::xkb::Keymap` due to the fact xkbcommon uses non-thread-safe reference /// counting. But can be used to create an independent `Keymap`. /// /// This is called after the default handler for keymap changes and does nothing by default. fn update_keymap( &mut self, _conn: &Connection, _qh: &QueueHandle, _keyboard: &wl_keyboard::WlKeyboard, _keymap: Keymap<'_>, ) { } } /// The rate at which a pressed key is repeated. #[derive(Debug, Clone, Copy)] pub enum RepeatInfo { /// Keys will be repeated at the specified rate and delay. Repeat { /// The number of repetitions per second that should occur. rate: NonZeroU32, /// Delay (in milliseconds) between a key press and the start of repetition. delay: u32, }, /// Keys should not be repeated. Disable, } /// Data associated with a key press or release event. #[derive(Debug, Clone)] pub struct KeyEvent { /// Time at which the keypress occurred. pub time: u32, /// The raw value of the key. pub raw_code: u32, /// The interpreted symbol of the key. /// /// This corresponds to one of the assoiated values on the [`Keysym`] type. pub keysym: Keysym, /// UTF-8 interpretation of the entered text. /// /// This will always be [`None`] on release events. pub utf8: Option, } /// The state of keyboard modifiers /// /// Each field of this indicates whether a specified modifier is active. /// /// Depending on the modifier, the modifier key may currently be pressed or toggled. #[derive(Debug, Clone, Copy, Default)] pub struct Modifiers { /// 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" or "super" key on a keyboard. #[doc(alias = "windows")] #[doc(alias = "super")] pub logo: bool, /// The "Num lock" key pub num_lock: bool, } /// The RMLVO description of a keymap /// /// All fields are optional, and the system default /// will be used if set to `None`. #[derive(Debug)] #[allow(clippy::upper_case_acronyms)] pub struct RMLVO { /// The rules file to use pub rules: Option, /// The keyboard model by which to interpret keycodes and LEDs pub model: Option, /// A comma separated list of layouts (languages) to include in the keymap pub layout: Option, /// A comma separated list of variants, one per layout, which may modify or /// augment the respective layout in various ways pub variant: Option, /// A comma separated list of options, through which the user specifies /// non-layout related preferences, like which key combinations are /// used for switching layouts, or which key is the Compose key. pub options: Option, } pub struct KeyboardData { seat: wl_seat::WlSeat, first_event: AtomicBool, xkb_context: Mutex, /// If the user manually specified the RMLVO to use. user_specified_rmlvo: bool, xkb_state: Mutex>, xkb_compose: Mutex>, #[cfg(feature = "calloop")] repeat_data: Arc>>>, focus: Mutex>, _phantom_data: PhantomData, } impl Debug for KeyboardData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("KeyboardData").finish_non_exhaustive() } } #[macro_export] macro_rules! delegate_keyboard { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_keyboard::WlKeyboard: $crate::seat::keyboard::KeyboardData<$ty> ] => $crate::seat::SeatState ); }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, keyboard: [$($udata:ty),* $(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $( $crate::reexports::client::protocol::wl_keyboard::WlKeyboard: $udata, )* ] => $crate::seat::SeatState ); }; } // SAFETY: The state does not share state with any other rust types. unsafe impl Send for KeyboardData {} // SAFETY: The state is guarded by a mutex since libxkbcommon has no internal synchronization. unsafe impl Sync for KeyboardData {} impl KeyboardData { pub fn new(seat: wl_seat::WlSeat) -> Self { let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); let udata = KeyboardData { seat, first_event: AtomicBool::new(false), xkb_context: Mutex::new(xkb_context), xkb_state: Mutex::new(None), user_specified_rmlvo: false, xkb_compose: Mutex::new(None), #[cfg(feature = "calloop")] repeat_data: Arc::new(Mutex::new(None)), focus: Mutex::new(None), _phantom_data: PhantomData, }; udata.init_compose(); udata } pub fn seat(&self) -> &wl_seat::WlSeat { &self.seat } pub fn from_rmlvo(seat: wl_seat::WlSeat, rmlvo: RMLVO) -> Result { let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); let keymap = xkb::Keymap::new_from_names( &xkb_context, &rmlvo.rules.unwrap_or_default(), &rmlvo.model.unwrap_or_default(), &rmlvo.layout.unwrap_or_default(), &rmlvo.variant.unwrap_or_default(), rmlvo.options, xkb::COMPILE_NO_FLAGS, ); if keymap.is_none() { return Err(KeyboardError::InvalidKeymap); } let xkb_state = Some(xkb::State::new(&keymap.unwrap())); let udata = KeyboardData { seat, first_event: AtomicBool::new(false), xkb_context: Mutex::new(xkb_context), xkb_state: Mutex::new(xkb_state), user_specified_rmlvo: true, xkb_compose: Mutex::new(None), #[cfg(feature = "calloop")] repeat_data: Arc::new(Mutex::new(None)), focus: Mutex::new(None), _phantom_data: PhantomData, }; udata.init_compose(); Ok(udata) } fn init_compose(&self) { let xkb_context = self.xkb_context.lock().unwrap(); if let Some(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()) .to_str() { // TODO: Pending new release of xkbcommon to use new_from_locale with OsStr if let Ok(table) = xkb::compose::Table::new_from_locale( &xkb_context, locale.as_ref(), xkb::compose::COMPILE_NO_FLAGS, ) { let compose_state = xkb::compose::State::new(&table, xkb::compose::COMPILE_NO_FLAGS); *self.xkb_compose.lock().unwrap() = Some(compose_state); } } } fn update_modifiers(&self) -> Modifiers { let guard = self.xkb_state.lock().unwrap(); let state = guard.as_ref().unwrap(); Modifiers { ctrl: state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE), alt: state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE), shift: state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE), caps_lock: state.mod_name_is_active(xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE), logo: state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE), num_lock: state.mod_name_is_active(xkb::MOD_NAME_NUM, xkb::STATE_MODS_EFFECTIVE), } } } pub trait KeyboardDataExt: Send + Sync { type State: 'static; fn keyboard_data(&self) -> &KeyboardData; fn keyboard_data_mut(&mut self) -> &mut KeyboardData; } impl KeyboardDataExt for KeyboardData { /// The type of the user defined state type State = T; fn keyboard_data(&self) -> &KeyboardData { self } fn keyboard_data_mut(&mut self) -> &mut KeyboardData { self } } impl Dispatch for SeatState where D: Dispatch + KeyboardHandler, U: KeyboardDataExt, { fn event( data: &mut D, keyboard: &wl_keyboard::WlKeyboard, event: wl_keyboard::Event, udata: &U, conn: &Connection, qh: &QueueHandle, ) { let udata = udata.keyboard_data(); // The compositor has no way to tell clients if the seat is not version 4 or above. // In this case, send a synthetic repeat info event using the default repeat values used by the X // server. if keyboard.version() < 4 && udata.first_event.load(Ordering::SeqCst) { udata.first_event.store(true, Ordering::SeqCst); data.update_repeat_info( conn, qh, keyboard, RepeatInfo::Repeat { rate: NonZeroU32::new(200).unwrap(), delay: 200 }, ); } match event { wl_keyboard::Event::Keymap { format, fd, size } => { match format { WEnum::Value(format) => match format { wl_keyboard::KeymapFormat::NoKeymap => { log::warn!(target: "sctk", "non-xkb compatible keymap"); } wl_keyboard::KeymapFormat::XkbV1 => { if udata.user_specified_rmlvo { // state is locked, ignore keymap updates return; } let context = udata.xkb_context.lock().unwrap(); // 0.5.0-beta.0 does not mark this function as unsafe but upstream rightly makes // this function unsafe. // // Version 7 of wl_keyboard requires the file descriptor to be mapped using // MAP_PRIVATE. xkbcommon-rs does mmap the file descriptor properly. // // SAFETY: // - wayland-client guarantees we have received a valid file descriptor. #[allow(unused_unsafe)] // Upstream release will change this match unsafe { xkb::Keymap::new_from_fd( &context, fd, size as usize, xkb::KEYMAP_FORMAT_TEXT_V1, xkb::COMPILE_NO_FLAGS, ) } { Ok(Some(keymap)) => { let state = xkb::State::new(&keymap); { let mut state_guard = udata.xkb_state.lock().unwrap(); *state_guard = Some(state); } data.update_keymap(conn, qh, keyboard, Keymap(&keymap)); } Ok(None) => { log::error!(target: "sctk", "invalid keymap"); } Err(err) => { log::error!(target: "sctk", "{}", err); } } } _ => unreachable!(), }, WEnum::Unknown(value) => { log::warn!(target: "sctk", "unknown keymap format 0x{:x}", value) } } } wl_keyboard::Event::Enter { serial, surface, keys } => { let state_guard = udata.xkb_state.lock().unwrap(); if let Some(guard) = state_guard.as_ref() { // Keysyms are encoded as an array of u32 let raw = keys .chunks_exact(4) .flat_map(TryInto::<[u8; 4]>::try_into) .map(u32::from_le_bytes) .collect::>(); let keysyms = raw .iter() .copied() // We must add 8 to the keycode for any functions we pass the raw keycode into per // wl_keyboard protocol. .map(|raw| guard.key_get_one_sym(KeyCode::new(raw + 8))) .collect::>(); // Drop guard before calling user code. drop(state_guard); data.enter( conn, qh, keyboard, &surface, serial, &raw, bytemuck::cast_slice(&keysyms), ); } *udata.focus.lock().unwrap() = Some(surface); } wl_keyboard::Event::Leave { serial, surface } => { // We can send this event without any other checks in the protocol will guarantee a leave is // sent before entering a new surface. #[cfg(feature = "calloop")] { if let Some(repeat_data) = udata.repeat_data.lock().unwrap().as_mut() { repeat_data.current_repeat.take(); } } data.leave(conn, qh, keyboard, &surface, serial); *udata.focus.lock().unwrap() = None; } wl_keyboard::Event::Key { serial, time, key, state } => match state { WEnum::Value(state) => { let state_guard = udata.xkb_state.lock().unwrap(); if let Some(guard) = state_guard.as_ref() { // We must add 8 to the keycode for any functions we pass the raw keycode into per // wl_keyboard protocol. let keycode = KeyCode::new(key + 8); let keysym = guard.key_get_one_sym(keycode); let utf8 = if state == wl_keyboard::KeyState::Pressed { let mut compose = udata.xkb_compose.lock().unwrap(); match compose.as_mut() { Some(compose) => match compose.feed(keysym) { xkb::FeedResult::Ignored => None, xkb::FeedResult::Accepted => match compose.status() { xkb::Status::Composed => compose.utf8(), xkb::Status::Nothing => Some(guard.key_get_utf8(keycode)), _ => None, }, }, // No compose None => Some(guard.key_get_utf8(keycode)), } } else { None }; // Drop guard before calling user code. drop(state_guard); let event = KeyEvent { time, raw_code: key, keysym, utf8 }; match state { wl_keyboard::KeyState::Released => { #[cfg(feature = "calloop")] { if let Some(repeat_data) = udata.repeat_data.lock().unwrap().as_mut() { if Some(event.raw_code) == repeat_data .current_repeat .as_ref() .map(|r| r.key.raw_code) { repeat_data.current_repeat = None; } } } data.release_key(conn, qh, keyboard, serial, event); } wl_keyboard::KeyState::Pressed => { #[cfg(feature = "calloop")] { if let Some(repeat_data) = udata.repeat_data.lock().unwrap().as_mut() { let loop_handle = &mut repeat_data.loop_handle; let state_guard = udata.xkb_state.lock().unwrap(); let key_repeats = state_guard .as_ref() .map(|guard| { guard .get_keymap() .key_repeats(KeyCode::new(event.raw_code + 8)) }) .unwrap_or_default(); if key_repeats { // Cancel the previous timer / repeat. if let Some(token) = repeat_data.repeat_token.take() { loop_handle.remove(token); } let surface = udata .focus .lock() .unwrap() .as_ref() .cloned() .expect("wl_keyboard::key with no focused surface"); // Update the current repeat key. repeat_data.current_repeat.replace(RepeatedKey { key: event.clone(), is_first: true, surface, }); let (delay, rate) = match repeat_data.repeat_info { RepeatInfo::Disable => return, RepeatInfo::Repeat { delay, rate } => (delay, rate), }; let gap = Duration::from_micros( 1_000_000 / rate.get() as u64, ); let timer = Timer::from_duration( Duration::from_millis(delay as u64), ); let repeat_data2 = udata.repeat_data.clone(); // Start the timer. let kbd = keyboard.clone(); if let Ok(token) = loop_handle.insert_source( timer, move |_, _, state| { let mut repeat_data = repeat_data2.lock().unwrap(); let repeat_data = match repeat_data.as_mut() { Some(repeat_data) => repeat_data, None => return TimeoutAction::Drop, }; let callback = &mut repeat_data.callback; let key = &mut repeat_data.current_repeat; if key.is_none() { return TimeoutAction::Drop; } let key = key.as_mut().unwrap(); // If surface was closed while focused, no `Leave` // event occurred. if !key.surface.is_alive() { return TimeoutAction::Drop; } key.key.time += if key.is_first { key.is_first = false; delay } else { gap.as_millis() as u32 }; callback(state, &kbd, key.key.clone()); TimeoutAction::ToDuration(gap) }, ) { repeat_data.repeat_token = Some(token); } } } } data.press_key(conn, qh, keyboard, serial, event); } _ => unreachable!(), } }; } WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "{}: compositor sends invalid key state: {:x}", keyboard.id(), unknown); } }, wl_keyboard::Event::Modifiers { serial, mods_depressed, mods_latched, mods_locked, group, } => { let mut guard = udata.xkb_state.lock().unwrap(); let state = match guard.as_mut() { Some(state) => state, None => return, }; // Apply the new xkb state with the new modifiers. let _ = state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group); // Update the currently repeating key if any. #[cfg(feature = "calloop")] if let Some(repeat_data) = udata.repeat_data.lock().unwrap().as_mut() { if let Some(mut event) = repeat_data.current_repeat.take() { // Apply new modifiers to get new utf8. event.key.utf8 = { let mut compose = udata.xkb_compose.lock().unwrap(); match compose.as_mut() { Some(compose) => match compose.feed(event.key.keysym) { xkb::FeedResult::Ignored => None, xkb::FeedResult::Accepted => match compose.status() { xkb::Status::Composed => compose.utf8(), xkb::Status::Nothing => Some( state .key_get_utf8(KeyCode::new(event.key.raw_code + 8)), ), _ => None, }, }, // No compose. None => { Some(state.key_get_utf8(KeyCode::new(event.key.raw_code + 8))) } } }; // Update the stored event. repeat_data.current_repeat = Some(event); } } // Drop guard before calling user code. drop(guard); // Always issue the modifiers update for the user. let modifiers = udata.update_modifiers(); data.update_modifiers(conn, qh, keyboard, serial, modifiers); } wl_keyboard::Event::RepeatInfo { rate, delay } => { let info = if rate != 0 { RepeatInfo::Repeat { rate: NonZeroU32::new(rate as u32).unwrap(), delay: delay as u32, } } else { RepeatInfo::Disable }; #[cfg(feature = "calloop")] { if let Some(repeat_data) = udata.repeat_data.lock().unwrap().as_mut() { repeat_data.repeat_info = info; } } data.update_repeat_info(conn, qh, keyboard, info); } _ => unreachable!(), } } } smithay-client-toolkit-0.18.0/src/seat/keyboard/repeat.rs000064400000000000000000000100041046102023000214730ustar 00000000000000use std::sync::atomic::Ordering; use calloop::{LoopHandle, RegistrationToken}; use wayland_client::{ protocol::{ wl_keyboard::{self, WlKeyboard}, wl_seat, wl_surface, }, Dispatch, QueueHandle, }; use super::{ Capability, KeyEvent, KeyboardData, KeyboardDataExt, KeyboardError, KeyboardHandler, RepeatInfo, SeatError, RMLVO, }; use crate::seat::SeatState; pub(crate) struct RepeatedKey { pub(crate) key: KeyEvent, /// Whether this is the first event of the repeat sequence. pub(crate) is_first: bool, pub(crate) surface: wl_surface::WlSurface, } pub type RepeatCallback = Box; pub(crate) struct RepeatData { pub(crate) current_repeat: Option, pub(crate) repeat_info: RepeatInfo, pub(crate) loop_handle: LoopHandle<'static, T>, pub(crate) callback: RepeatCallback, pub(crate) repeat_token: Option, } impl Drop for RepeatData { fn drop(&mut self) { if let Some(token) = self.repeat_token.take() { self.loop_handle.remove(token); } } } impl SeatState { /// Creates a keyboard from a seat. /// /// This function returns an [`EventSource`] that indicates when a key press is going to repeat. /// /// This keyboard implementation uses libxkbcommon for the keymap. /// /// Typically the compositor will provide a keymap, but you may specify your own keymap using the `rmlvo` /// field. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a keyboard. /// /// [`EventSource`]: calloop::EventSource pub fn get_keyboard_with_repeat( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, rmlvo: Option, loop_handle: LoopHandle<'static, T>, callback: RepeatCallback, ) -> Result where D: Dispatch> + KeyboardHandler + 'static, T: 'static, { let udata = match rmlvo { Some(rmlvo) => KeyboardData::from_rmlvo(seat.clone(), rmlvo)?, None => KeyboardData::new(seat.clone()), }; self.get_keyboard_with_repeat_with_data(qh, seat, udata, loop_handle, callback) } /// Creates a keyboard from a seat. /// /// This function returns an [`EventSource`] that indicates when a key press is going to repeat. /// /// This keyboard implementation uses libxkbcommon for the keymap. /// /// Typically the compositor will provide a keymap, but you may specify your own keymap using the `rmlvo` /// field. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a keyboard. /// /// [`EventSource`]: calloop::EventSource pub fn get_keyboard_with_repeat_with_data( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, mut udata: U, loop_handle: LoopHandle<'static, ::State>, callback: RepeatCallback<::State>, ) -> Result where D: Dispatch + KeyboardHandler + 'static, U: KeyboardDataExt + 'static, { let inner = self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; if !inner.data.has_keyboard.load(Ordering::SeqCst) { return Err(SeatError::UnsupportedCapability(Capability::Keyboard).into()); } let kbd_data = udata.keyboard_data_mut(); kbd_data.repeat_data.lock().unwrap().replace(RepeatData { current_repeat: None, repeat_info: RepeatInfo::Disable, loop_handle: loop_handle.clone(), callback, repeat_token: None, }); kbd_data.init_compose(); Ok(seat.get_keyboard(qh, udata)) } } smithay-client-toolkit-0.18.0/src/seat/mod.rs000064400000000000000000000425321046102023000172050ustar 00000000000000use std::{ fmt::{self, Display, Formatter}, sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, }; use crate::reexports::client::{ globals::{Global, GlobalList}, protocol::{wl_pointer, wl_registry::WlRegistry, wl_seat, wl_shm, wl_surface, wl_touch}, Connection, Dispatch, Proxy, QueueHandle, }; use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use crate::{ compositor::SurfaceDataExt, globals::GlobalData, registry::{ProvidesRegistryState, RegistryHandler}, }; #[cfg(feature = "xkbcommon")] pub mod keyboard; pub mod pointer; pub mod pointer_constraints; pub mod relative_pointer; pub mod touch; use pointer::cursor_shape::CursorShapeManager; use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes}; use touch::{TouchData, TouchDataExt, TouchHandler}; #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Capability { Keyboard, Pointer, Touch, } impl Display for Capability { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Capability::Keyboard => write!(f, "keyboard"), Capability::Pointer => write!(f, "pointer"), Capability::Touch => write!(f, "touch"), } } } #[derive(Debug, thiserror::Error)] pub enum SeatError { #[error("the capability \"{0}\" is not supported")] /// The capability is not supported. UnsupportedCapability(Capability), /// The seat is dead. #[error("the seat is dead")] DeadObject, } #[derive(Debug)] pub struct SeatState { // (name, seat) seats: Vec, cursor_shape_manager_state: CursorShapeManagerState, } #[derive(Debug)] enum CursorShapeManagerState { NotPresent, Pending { registry: WlRegistry, global: Global }, Bound(CursorShapeManager), } impl SeatState { pub fn new + 'static>( global_list: &GlobalList, qh: &QueueHandle, ) -> SeatState { let (seats, cursor_shape_manager) = global_list.contents().with_list(|globals| { let global = globals .iter() .find(|global| global.interface == WpCursorShapeManagerV1::interface().name) .map(|global| CursorShapeManagerState::Pending { registry: global_list.registry().clone(), global: global.clone(), }) .unwrap_or(CursorShapeManagerState::NotPresent); ( crate::registry::bind_all(global_list.registry(), globals, qh, 1..=7, |id| { SeatData { has_keyboard: Arc::new(AtomicBool::new(false)), has_pointer: Arc::new(AtomicBool::new(false)), has_touch: Arc::new(AtomicBool::new(false)), name: Arc::new(Mutex::new(None)), id, } }) .expect("failed to bind global"), global, ) }); let mut state = SeatState { seats: vec![], cursor_shape_manager_state: cursor_shape_manager }; for seat in seats { let data = seat.data::().unwrap().clone(); state.seats.push(SeatInner { seat: seat.clone(), data }); } state } /// Returns an iterator over all the seats. pub fn seats(&self) -> impl Iterator { self.seats.iter().map(|inner| inner.seat.clone()).collect::>().into_iter() } /// Returns information about a seat. /// /// This will return [`None`] if the seat is dead. pub fn info(&self, seat: &wl_seat::WlSeat) -> Option { self.seats.iter().find(|inner| &inner.seat == seat).map(|inner| { let name = inner.data.name.lock().unwrap().clone(); SeatInfo { name, has_keyboard: inner.data.has_keyboard.load(Ordering::SeqCst), has_pointer: inner.data.has_pointer.load(Ordering::SeqCst), has_touch: inner.data.has_touch.load(Ordering::SeqCst), } }) } /// Creates a pointer from a seat. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. pub fn get_pointer( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, ) -> Result where D: Dispatch + PointerHandler + 'static, { self.get_pointer_with_data(qh, seat, PointerData::new(seat.clone())) } /// Creates a pointer from a seat with the provided theme. /// /// This will use [`CursorShapeManager`] under the hood when it's available. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. pub fn get_pointer_with_theme( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, shm: &wl_shm::WlShm, surface: wl_surface::WlSurface, theme: ThemeSpec, ) -> Result, SeatError> where D: Dispatch + Dispatch + Dispatch + Dispatch + PointerHandler + 'static, S: SurfaceDataExt + 'static, { self.get_pointer_with_theme_and_data( qh, seat, shm, surface, theme, PointerData::new(seat.clone()), ) } /// Creates a pointer from a seat. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. pub fn get_pointer_with_data( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, pointer_data: U, ) -> Result where D: Dispatch + PointerHandler + 'static, U: PointerDataExt + 'static, { let inner = self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; if !inner.data.has_pointer.load(Ordering::SeqCst) { return Err(SeatError::UnsupportedCapability(Capability::Pointer)); } Ok(seat.get_pointer(qh, pointer_data)) } /// Creates a pointer from a seat with the provided theme and data. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. pub fn get_pointer_with_theme_and_data( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, shm: &wl_shm::WlShm, surface: wl_surface::WlSurface, theme: ThemeSpec, pointer_data: U, ) -> Result, SeatError> where D: Dispatch + Dispatch + Dispatch + Dispatch + PointerHandler + 'static, S: SurfaceDataExt + 'static, U: PointerDataExt + 'static, { let inner = self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; if !inner.data.has_pointer.load(Ordering::SeqCst) { return Err(SeatError::UnsupportedCapability(Capability::Pointer)); } let wl_ptr = seat.get_pointer(qh, pointer_data); if let CursorShapeManagerState::Pending { registry, global } = &self.cursor_shape_manager_state { self.cursor_shape_manager_state = match crate::registry::bind_one(registry, &[global.clone()], qh, 1..=1, GlobalData) { Ok(bound) => { CursorShapeManagerState::Bound(CursorShapeManager::from_existing(bound)) } Err(_) => CursorShapeManagerState::NotPresent, } } let shape_device = if let CursorShapeManagerState::Bound(ref bound) = self.cursor_shape_manager_state { Some(bound.get_shape_device(&wl_ptr, qh)) } else { None }; Ok(ThemedPointer { themes: Arc::new(Mutex::new(Themes::new(theme))), pointer: wl_ptr, shm: shm.clone(), surface, shape_device, _marker: std::marker::PhantomData, _surface_data: std::marker::PhantomData, }) } /// Creates a touch handle from a seat. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support touch. pub fn get_touch( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, ) -> Result where D: Dispatch + TouchHandler + 'static, { self.get_touch_with_data(qh, seat, TouchData::new(seat.clone())) } /// Creates a touch handle from a seat. /// /// ## Errors /// /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support touch. pub fn get_touch_with_data( &mut self, qh: &QueueHandle, seat: &wl_seat::WlSeat, udata: U, ) -> Result where D: Dispatch + TouchHandler + 'static, U: TouchDataExt + 'static, { let inner = self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; if !inner.data.has_touch.load(Ordering::SeqCst) { return Err(SeatError::UnsupportedCapability(Capability::Touch)); } Ok(seat.get_touch(qh, udata)) } } pub trait SeatHandler: Sized { fn seat_state(&mut self) -> &mut SeatState; /// A new seat has been created. /// /// This function only indicates that a seat has been created, you will need to wait for [`new_capability`](SeatHandler::new_capability) /// to be called before creating any keyboards, fn new_seat(&mut self, conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat); /// A new capability is available on the seat. /// /// This allows you to create the corresponding object related to the capability. fn new_capability( &mut self, conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ); /// A capability has been removed from the seat. /// /// If an object has been created from the capability, it should be destroyed. fn remove_capability( &mut self, conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ); /// A seat has been removed. /// /// The seat is destroyed and all capability objects created from it are invalid. fn remove_seat(&mut self, conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat); } /// Description of a seat. #[non_exhaustive] #[derive(Debug, Clone)] pub struct SeatInfo { /// The name of the seat. pub name: Option, /// Does the seat support a keyboard. pub has_keyboard: bool, /// Does the seat support a pointer. pub has_pointer: bool, /// Does the seat support touch input. pub has_touch: bool, } impl Display for SeatInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(ref name) = self.name { write!(f, "name: \"{name}\" ")?; } write!(f, "capabilities: (")?; if !self.has_keyboard && !self.has_pointer && !self.has_touch { write!(f, "none")?; } else { if self.has_keyboard { write!(f, "keyboard")?; if self.has_pointer || self.has_touch { write!(f, ", ")?; } } if self.has_pointer { write!(f, "pointer")?; if self.has_touch { write!(f, ", ")?; } } if self.has_touch { write!(f, "touch")?; } } write!(f, ")") } } #[derive(Debug, Clone)] pub struct SeatData { has_keyboard: Arc, has_pointer: Arc, has_touch: Arc, name: Arc>>, id: u32, } #[macro_export] macro_rules! delegate_seat { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_seat::WlSeat: $crate::seat::SeatData ] => $crate::seat::SeatState ); }; } #[derive(Debug)] struct SeatInner { seat: wl_seat::WlSeat, data: SeatData, } impl Dispatch for SeatState where D: Dispatch + SeatHandler, { fn event( state: &mut D, seat: &wl_seat::WlSeat, event: wl_seat::Event, data: &SeatData, conn: &Connection, qh: &QueueHandle, ) { match event { wl_seat::Event::Capabilities { capabilities } => { let capabilities = wl_seat::Capability::from_bits_truncate(capabilities.into()); let keyboard = capabilities.contains(wl_seat::Capability::Keyboard); let has_keyboard = data.has_keyboard.load(Ordering::SeqCst); let pointer = capabilities.contains(wl_seat::Capability::Pointer); let has_pointer = data.has_pointer.load(Ordering::SeqCst); let touch = capabilities.contains(wl_seat::Capability::Touch); let has_touch = data.has_touch.load(Ordering::SeqCst); // Update capabilities as necessary if keyboard != has_keyboard { data.has_keyboard.store(keyboard, Ordering::SeqCst); match keyboard { true => state.new_capability(conn, qh, seat.clone(), Capability::Keyboard), false => { state.remove_capability(conn, qh, seat.clone(), Capability::Keyboard) } } } if pointer != has_pointer { data.has_pointer.store(pointer, Ordering::SeqCst); match pointer { true => state.new_capability(conn, qh, seat.clone(), Capability::Pointer), false => { state.remove_capability(conn, qh, seat.clone(), Capability::Pointer) } } } if touch != has_touch { data.has_touch.store(touch, Ordering::SeqCst); match touch { true => state.new_capability(conn, qh, seat.clone(), Capability::Touch), false => state.remove_capability(conn, qh, seat.clone(), Capability::Touch), } } } wl_seat::Event::Name { name } => { *data.name.lock().unwrap() = Some(name); } _ => unreachable!(), } } } impl RegistryHandler for SeatState where D: Dispatch + SeatHandler + ProvidesRegistryState + 'static, { fn new_global( state: &mut D, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, _: u32, ) { if interface == wl_seat::WlSeat::interface().name { let seat = state .registry() .bind_specific( qh, name, 1..=7, SeatData { has_keyboard: Arc::new(AtomicBool::new(false)), has_pointer: Arc::new(AtomicBool::new(false)), has_touch: Arc::new(AtomicBool::new(false)), name: Arc::new(Mutex::new(None)), id: name, }, ) .expect("failed to bind global"); let data = seat.data::().unwrap().clone(); state.seat_state().seats.push(SeatInner { seat: seat.clone(), data }); state.new_seat(conn, qh, seat); } } fn remove_global( state: &mut D, conn: &Connection, qh: &QueueHandle, name: u32, interface: &str, ) { if interface == wl_seat::WlSeat::interface().name { if let Some(seat) = state.seat_state().seats.iter().find(|inner| inner.data.id == name) { let seat = seat.seat.clone(); state.remove_seat(conn, qh, seat); state.seat_state().seats.retain(|inner| inner.data.id != name); } } } } smithay-client-toolkit-0.18.0/src/seat/pointer/cursor_shape.rs000064400000000000000000000102271046102023000225770ustar 00000000000000use cursor_icon::CursorIcon; use crate::globals::GlobalData; use crate::reexports::client::globals::{BindError, GlobalList}; use crate::reexports::client::protocol::wl_pointer::WlPointer; use crate::reexports::client::{Connection, Dispatch, Proxy, QueueHandle}; use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; #[derive(Debug)] pub struct CursorShapeManager { cursor_shape_manager: WpCursorShapeManagerV1, } impl CursorShapeManager { pub fn bind( globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result where State: Dispatch + 'static, { let cursor_shape_manager = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(Self { cursor_shape_manager }) } pub(crate) fn from_existing(cursor_shape_manager: WpCursorShapeManagerV1) -> Self { Self { cursor_shape_manager } } pub fn get_shape_device( &self, pointer: &WlPointer, queue_handle: &QueueHandle, ) -> WpCursorShapeDeviceV1 where State: Dispatch + 'static, { self.cursor_shape_manager.get_pointer(pointer, queue_handle, GlobalData) } pub fn inner(&self) -> &WpCursorShapeManagerV1 { &self.cursor_shape_manager } } impl Dispatch for CursorShapeManager where State: Dispatch, { fn event( _: &mut State, _: &WpCursorShapeManagerV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wl_cursor_shape_manager_v1 has no events") } } impl Dispatch for CursorShapeManager where State: Dispatch, { fn event( _: &mut State, _: &WpCursorShapeDeviceV1, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wl_cursor_shape_device_v1 has no events") } } pub(crate) fn cursor_icon_to_shape(cursor_icon: CursorIcon) -> Shape { match cursor_icon { CursorIcon::Default => Shape::Default, CursorIcon::ContextMenu => Shape::ContextMenu, CursorIcon::Help => Shape::Help, CursorIcon::Pointer => Shape::Pointer, CursorIcon::Progress => Shape::Progress, CursorIcon::Wait => Shape::Wait, CursorIcon::Cell => Shape::Cell, CursorIcon::Crosshair => Shape::Crosshair, CursorIcon::Text => Shape::Text, CursorIcon::VerticalText => Shape::VerticalText, CursorIcon::Alias => Shape::Alias, CursorIcon::Copy => Shape::Copy, CursorIcon::Move => Shape::Move, CursorIcon::NoDrop => Shape::NoDrop, CursorIcon::NotAllowed => Shape::NotAllowed, CursorIcon::Grab => Shape::Grab, CursorIcon::Grabbing => Shape::Grabbing, CursorIcon::EResize => Shape::EResize, CursorIcon::NResize => Shape::NResize, CursorIcon::NeResize => Shape::NeResize, CursorIcon::NwResize => Shape::NwResize, CursorIcon::SResize => Shape::SResize, CursorIcon::SeResize => Shape::SeResize, CursorIcon::SwResize => Shape::SwResize, CursorIcon::WResize => Shape::WResize, CursorIcon::EwResize => Shape::EwResize, CursorIcon::NsResize => Shape::NsResize, CursorIcon::NeswResize => Shape::NeswResize, CursorIcon::NwseResize => Shape::NwseResize, CursorIcon::ColResize => Shape::ColResize, CursorIcon::RowResize => Shape::RowResize, CursorIcon::AllScroll => Shape::AllScroll, CursorIcon::ZoomIn => Shape::ZoomIn, CursorIcon::ZoomOut => Shape::ZoomOut, _ => Shape::Default, } } smithay-client-toolkit-0.18.0/src/seat/pointer/mod.rs000064400000000000000000000512051046102023000206620ustar 00000000000000use std::{ collections::{hash_map::Entry, HashMap}, env, mem, sync::{Arc, Mutex}, }; use wayland_backend::{client::InvalidId, smallvec::SmallVec}; use wayland_client::{ protocol::{ wl_pointer::{self, WlPointer}, wl_seat::WlSeat, wl_shm::WlShm, wl_surface::WlSurface, }, Connection, Dispatch, Proxy, QueueHandle, WEnum, }; use wayland_cursor::{Cursor, CursorTheme}; use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use crate::{ compositor::{SurfaceData, SurfaceDataExt}, error::GlobalError, }; use super::SeatState; #[doc(inline)] pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; pub mod cursor_shape; use cursor_shape::cursor_icon_to_shape; /* From linux/input-event-codes.h - the buttons usually used by mice */ pub const BTN_LEFT: u32 = 0x110; pub const BTN_RIGHT: u32 = 0x111; pub const BTN_MIDDLE: u32 = 0x112; /// The fourth non-scroll button, which is often used as "back" in web browsers. pub const BTN_SIDE: u32 = 0x113; /// The fifth non-scroll button, which is often used as "forward" in web browsers. pub const BTN_EXTRA: u32 = 0x114; /// See also [`BTN_EXTRA`]. pub const BTN_FORWARD: u32 = 0x115; /// See also [`BTN_SIDE`]. pub const BTN_BACK: u32 = 0x116; pub const BTN_TASK: u32 = 0x117; /// Describes a scroll along one axis #[derive(Default, Debug, Clone, Copy, PartialEq)] pub struct AxisScroll { /// The scroll measured in pixels. pub absolute: f64, /// The scroll measured in steps. /// /// Note: this might always be zero if the scrolling is due to a touchpad or other continuous /// source. pub discrete: i32, /// The scroll was stopped. /// /// Generally this is encountered when hardware indicates the end of some continuous scrolling. pub stop: bool, } impl AxisScroll { /// Returns true if there was no movement along this axis. pub fn is_none(&self) -> bool { *self == Self::default() } fn merge(&mut self, other: &Self) { self.absolute += other.absolute; self.discrete += other.discrete; self.stop |= other.stop; } } /// A single pointer event. #[derive(Debug, Clone)] pub struct PointerEvent { pub surface: WlSurface, pub position: (f64, f64), pub kind: PointerEventKind, } #[derive(Debug, Clone)] pub enum PointerEventKind { Enter { serial: u32, }, Leave { serial: u32, }, Motion { time: u32, }, Press { time: u32, button: u32, serial: u32, }, Release { time: u32, button: u32, serial: u32, }, Axis { time: u32, horizontal: AxisScroll, vertical: AxisScroll, source: Option, }, } pub trait PointerHandler: Sized { /// One or more pointer events are available. /// /// Multiple related events may be grouped together in a single frame. Some examples: /// /// - A drag that terminates outside the surface may send the Release and Leave events as one frame /// - Movement from one surface to another may send the Enter and Leave events in one frame fn pointer_frame( &mut self, conn: &Connection, qh: &QueueHandle, pointer: &WlPointer, events: &[PointerEvent], ); } #[derive(Debug)] pub struct PointerData { seat: WlSeat, pub(crate) inner: Mutex, } impl PointerData { pub fn new(seat: WlSeat) -> Self { Self { seat, inner: Default::default() } } /// The seat associated with this pointer. pub fn seat(&self) -> &WlSeat { &self.seat } /// Serial from the latest [`PointerEventKind::Enter`] event. pub fn latest_enter_serial(&self) -> Option { self.inner.lock().unwrap().latest_enter } /// Serial from the latest button [`PointerEventKind::Press`] and /// [`PointerEventKind::Release`] events. pub fn latest_button_serial(&self) -> Option { self.inner.lock().unwrap().latest_btn } } pub trait PointerDataExt: Send + Sync { fn pointer_data(&self) -> &PointerData; } impl PointerDataExt for PointerData { fn pointer_data(&self) -> &PointerData { self } } #[macro_export] macro_rules! delegate_pointer { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_pointer::WlPointer: $crate::seat::pointer::PointerData ] => $crate::seat::SeatState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData ] => $crate::seat::pointer::cursor_shape::CursorShapeManager ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData ] => $crate::seat::pointer::cursor_shape::CursorShapeManager ); }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, pointer: [$($pointer_data:ty),* $(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $( $crate::reexports::client::protocol::wl_pointer::WlPointer: $pointer_data, )* ] => $crate::seat::SeatState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData ] => $crate::seat::pointer::cursor_shape::CursorShapeManager ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData ] => $crate::seat::pointer::cursor_shape::CursorShapeManager ); }; } #[derive(Debug, Default)] pub(crate) struct PointerDataInner { /// Surface the pointer most recently entered pub(crate) surface: Option, /// Position relative to the surface pub(crate) position: (f64, f64), /// List of pending events. Only used for version >= 5. pub(crate) pending: SmallVec<[PointerEvent; 3]>, /// The serial of the latest enter event for the pointer pub(crate) latest_enter: Option, /// The serial of the latest enter event for the pointer pub(crate) latest_btn: Option, } impl Dispatch for SeatState where D: Dispatch + PointerHandler, U: PointerDataExt, { fn event( data: &mut D, pointer: &WlPointer, event: wl_pointer::Event, udata: &U, conn: &Connection, qh: &QueueHandle, ) { let udata = udata.pointer_data(); let mut guard = udata.inner.lock().unwrap(); let mut leave_surface = None; let kind = match event { wl_pointer::Event::Enter { surface, surface_x, surface_y, serial } => { guard.surface = Some(surface); guard.position = (surface_x, surface_y); guard.latest_enter.replace(serial); PointerEventKind::Enter { serial } } wl_pointer::Event::Leave { surface, serial } => { if guard.surface.as_ref() == Some(&surface) { guard.surface = None; } leave_surface = Some(surface); PointerEventKind::Leave { serial } } wl_pointer::Event::Motion { time, surface_x, surface_y } => { guard.position = (surface_x, surface_y); PointerEventKind::Motion { time } } wl_pointer::Event::Button { time, button, state, serial } => { guard.latest_btn.replace(serial); match state { WEnum::Value(wl_pointer::ButtonState::Pressed) => { PointerEventKind::Press { time, button, serial } } WEnum::Value(wl_pointer::ButtonState::Released) => { PointerEventKind::Release { time, button, serial } } WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "{}: invalid pointer button state: {:x}", pointer.id(), unknown); return; } _ => unreachable!(), } } // Axis logical events. wl_pointer::Event::Axis { time, axis, value } => match axis { WEnum::Value(axis) => { let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default(); match axis { wl_pointer::Axis::VerticalScroll => { vertical.absolute = value; } wl_pointer::Axis::HorizontalScroll => { horizontal.absolute = value; } _ => unreachable!(), }; PointerEventKind::Axis { time, horizontal, vertical, source: None } } WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown); return; } }, wl_pointer::Event::AxisSource { axis_source } => match axis_source { WEnum::Value(source) => PointerEventKind::Axis { horizontal: AxisScroll::default(), vertical: AxisScroll::default(), source: Some(source), time: 0, }, WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "unknown pointer axis source: {:x}", unknown); return; } }, wl_pointer::Event::AxisStop { time, axis } => match axis { WEnum::Value(axis) => { let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default(); match axis { wl_pointer::Axis::VerticalScroll => vertical.stop = true, wl_pointer::Axis::HorizontalScroll => horizontal.stop = true, _ => unreachable!(), } PointerEventKind::Axis { time, horizontal, vertical, source: None } } WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown); return; } }, wl_pointer::Event::AxisDiscrete { axis, discrete } => match axis { WEnum::Value(axis) => { let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default(); match axis { wl_pointer::Axis::VerticalScroll => { vertical.discrete = discrete; } wl_pointer::Axis::HorizontalScroll => { horizontal.discrete = discrete; } _ => unreachable!(), }; PointerEventKind::Axis { time: 0, horizontal, vertical, source: None } } WEnum::Unknown(unknown) => { log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown); return; } }, wl_pointer::Event::Frame => { let pending = mem::take(&mut guard.pending); drop(guard); if !pending.is_empty() { data.pointer_frame(conn, qh, pointer, &pending); } return; } _ => unreachable!(), }; let surface = match (leave_surface, &guard.surface) { (Some(surface), _) => surface, (None, Some(surface)) => surface.clone(), (None, None) => { log::warn!(target: "sctk", "{}: got pointer event {:?} without an entered surface", pointer.id(), kind); return; } }; let event = PointerEvent { surface, position: guard.position, kind }; if pointer.version() < 5 { drop(guard); // No Frame events, send right away data.pointer_frame(conn, qh, pointer, &[event]); } else { // Merge a new Axis event with the previous event to create an event with more // information and potentially diagonal scrolling. if let ( Some(PointerEvent { kind: PointerEventKind::Axis { time: ot, horizontal: oh, vertical: ov, source: os }, .. }), PointerEvent { kind: PointerEventKind::Axis { time: nt, horizontal: nh, vertical: nv, source: ns }, .. }, ) = (guard.pending.last_mut(), &event) { // A time of 0 is "don't know", so avoid using it if possible. if *ot == 0 { *ot = *nt; } oh.merge(nh); ov.merge(nv); *os = os.or(*ns); return; } guard.pending.push(event); } } } /// Pointer themeing #[derive(Debug)] pub struct ThemedPointer { pub(super) themes: Arc>, /// The underlying wl_pointer. pub(super) pointer: WlPointer, pub(super) shm: WlShm, /// The surface owned by the cursor to present the icon. pub(super) surface: WlSurface, pub(super) shape_device: Option, pub(super) _marker: std::marker::PhantomData, pub(super) _surface_data: std::marker::PhantomData, } impl ThemedPointer { /// Set the cursor to the given [`CursorIcon`]. /// /// The cursor icon should be reloaded on every [`PointerEventKind::Enter`] event. pub fn set_cursor(&self, conn: &Connection, icon: CursorIcon) -> Result<(), PointerThemeError> { let serial = match self .pointer .data::() .and_then(|data| data.pointer_data().latest_enter_serial()) { Some(serial) => serial, None => return Err(PointerThemeError::MissingEnterSerial), }; if let Some(shape_device) = self.shape_device.as_ref() { shape_device.set_shape(serial, cursor_icon_to_shape(icon)); Ok(()) } else { self.set_cursor_legacy(conn, serial, icon) } } /// The legacy method of loading the cursor from the system cursor /// theme instead of relying on compositor to set the cursor. fn set_cursor_legacy( &self, conn: &Connection, serial: u32, icon: CursorIcon, ) -> Result<(), PointerThemeError> { let mut themes = self.themes.lock().unwrap(); let scale = self.surface.data::().unwrap().surface_data().scale_factor(); let cursor = themes .get_cursor(conn, icon.name(), scale as u32, &self.shm) .map_err(PointerThemeError::InvalidId)? .ok_or(PointerThemeError::CursorNotFound)?; let image = &cursor[0]; let (w, h) = image.dimensions(); let (hx, hy) = image.hotspot(); self.surface.set_buffer_scale(scale); self.surface.attach(Some(image), 0, 0); if self.surface.version() >= 4 { self.surface.damage_buffer(0, 0, w as i32, h as i32); } else { // Fallback for the old old surface. self.surface.damage(0, 0, w as i32 / scale, h as i32 / scale); } // Commit the surface to place the cursor image in the compositor's memory. self.surface.commit(); // Set the pointer surface to change the pointer. self.pointer.set_cursor(serial, Some(&self.surface), hx as i32 / scale, hy as i32 / scale); Ok(()) } /// Hide the cursor by providing empty surface for it. /// /// The cursor should be hidden on every [`PointerEventKind::Enter`] event. pub fn hide_cursor(&self) -> Result<(), PointerThemeError> { let data = self.pointer.data::(); if let Some(serial) = data.and_then(|data| data.pointer_data().latest_enter_serial()) { self.pointer.set_cursor(serial, None, 0, 0); Ok(()) } else { Err(PointerThemeError::MissingEnterSerial) } } /// The [`WlPointer`] associated with this [`ThemedPointer`]. pub fn pointer(&self) -> &WlPointer { &self.pointer } /// The associated [`WlSurface`] with this [`ThemedPointer`]. pub fn surface(&self) -> &WlSurface { &self.surface } } impl Drop for ThemedPointer { fn drop(&mut self) { if let Some(shape_device) = self.shape_device.take() { shape_device.destroy(); } if self.pointer.version() >= 3 { self.pointer.release(); } self.surface.destroy(); } } /// Specifies which cursor theme should be used by the theme manager. #[derive(Debug)] pub enum ThemeSpec<'a> { /// Use this specific theme with the given base size. Named { /// Name of the cursor theme. name: &'a str, /// Base size of the cursor names. /// /// Note this size assumes a scale factor of 1. Cursor image sizes may be multiplied by the base size /// for HiDPI outputs. size: u32, }, /// Use the system provided theme /// /// In this case SCTK will read the `XCURSOR_THEME` and /// `XCURSOR_SIZE` environment variables to figure out the /// theme to use. System, } impl<'a> Default for ThemeSpec<'a> { fn default() -> Self { Self::System } } /// An error indicating that the cursor was not found. #[derive(Debug, thiserror::Error)] pub enum PointerThemeError { /// An invalid ObjectId was used. #[error("Invalid ObjectId")] InvalidId(InvalidId), /// A global error occurred. #[error("A Global Error occured")] GlobalError(GlobalError), /// The requested cursor was not found. #[error("Cursor not found")] CursorNotFound, /// There has been no enter event yet for the pointer. #[error("Missing enter event serial")] MissingEnterSerial, } #[derive(Debug)] pub(crate) struct Themes { name: String, size: u32, // Scale -> CursorTheme themes: HashMap, } impl Default for Themes { fn default() -> Self { Themes::new(ThemeSpec::default()) } } impl Themes { pub(crate) fn new(spec: ThemeSpec) -> Themes { let (name, size) = match spec { ThemeSpec::Named { name, size } => (name.into(), size), ThemeSpec::System => { let name = env::var("XCURSOR_THEME").ok().unwrap_or_else(|| "default".into()); let size = env::var("XCURSOR_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(24); (name, size) } }; Themes { name, size, themes: HashMap::new() } } fn get_cursor( &mut self, conn: &Connection, name: &str, scale: u32, shm: &WlShm, ) -> Result, InvalidId> { // Check if the theme has been initialized at the specified scale. if let Entry::Vacant(e) = self.themes.entry(scale) { // Initialize the theme for the specified scale let theme = CursorTheme::load_from_name( conn, shm.clone(), // TODO: Does the cursor theme need to clone wl_shm? &self.name, self.size * scale, )?; e.insert(theme); } let theme = self.themes.get_mut(&scale).unwrap(); Ok(theme.get_cursor(name)) } } smithay-client-toolkit-0.18.0/src/seat/pointer_constraints.rs000064400000000000000000000176141046102023000225400ustar 00000000000000use wayland_client::{ globals::GlobalList, protocol::{wl_pointer, wl_region, wl_surface}, Connection, Dispatch, QueueHandle, }; use wayland_protocols::wp::pointer_constraints::zv1::client::{ zwp_confined_pointer_v1, zwp_locked_pointer_v1, zwp_pointer_constraints_v1, }; use crate::{ error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, registry::GlobalProxy, }; #[derive(Debug)] pub struct PointerConstraintsState { pointer_constraints: GlobalProxy, } impl PointerConstraintsState { /// Bind `zwp_pointer_constraints_v1` global, if it exists pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Self where D: Dispatch + 'static, { let pointer_constraints = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData)); Self { pointer_constraints } } /// Request that the compositor confine the pointer to a region /// /// It is a protocol error to call when a constraint already exists for a pointer on the seat. pub fn confine_pointer( &self, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, region: Option<&wl_region::WlRegion>, lifetime: zwp_pointer_constraints_v1::Lifetime, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { let udata = PointerConstraintData { surface: surface.clone(), pointer: pointer.clone() }; Ok(self .pointer_constraints .get()? .confine_pointer(surface, pointer, region, lifetime, qh, udata)) } /// Request that the compositor lock the pointer in place /// /// It is a protocol error to call when a constraint already exists for a pointer on the seat. pub fn lock_pointer( &self, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, region: Option<&wl_region::WlRegion>, lifetime: zwp_pointer_constraints_v1::Lifetime, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { let udata = PointerConstraintData { surface: surface.clone(), pointer: pointer.clone() }; Ok(self .pointer_constraints .get()? .lock_pointer(surface, pointer, region, lifetime, qh, udata)) } } impl ProvidesBoundGlobal for PointerConstraintsState { fn bound_global( &self, ) -> Result { self.pointer_constraints.get().cloned() } } pub trait PointerConstraintsHandler: Sized { /// Pointer confinement activated by compositor fn confined( &mut self, conn: &Connection, qh: &QueueHandle, confined_pointer: &zwp_confined_pointer_v1::ZwpConfinedPointerV1, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, ); /// Pointer confinement deactivated by compositor /// /// For `Oneshot` constraints, it will not be reactivated. fn unconfined( &mut self, conn: &Connection, qh: &QueueHandle, confined_pointer: &zwp_confined_pointer_v1::ZwpConfinedPointerV1, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, ); /// Pointer lock activated by compositor fn locked( &mut self, conn: &Connection, qh: &QueueHandle, locked_pointer: &zwp_locked_pointer_v1::ZwpLockedPointerV1, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, ); /// Pointer lock deactivated by compositor /// /// For `Oneshot` constraints, it will not be reactivated. fn unlocked( &mut self, conn: &Connection, qh: &QueueHandle, locked_pointer: &zwp_locked_pointer_v1::ZwpLockedPointerV1, surface: &wl_surface::WlSurface, pointer: &wl_pointer::WlPointer, ); } #[doc(hidden)] #[derive(Debug)] pub struct PointerConstraintData { surface: wl_surface::WlSurface, pointer: wl_pointer::WlPointer, } impl Dispatch for PointerConstraintsState where D: Dispatch + PointerConstraintsHandler, { fn event( _data: &mut D, _constraints: &zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, _event: zwp_pointer_constraints_v1::Event, _: &GlobalData, _conn: &Connection, _qh: &QueueHandle, ) { unreachable!() } } impl Dispatch for PointerConstraintsState where D: Dispatch + PointerConstraintsHandler, { fn event( data: &mut D, confined_pointer: &zwp_confined_pointer_v1::ZwpConfinedPointerV1, event: zwp_confined_pointer_v1::Event, udata: &PointerConstraintData, conn: &Connection, qh: &QueueHandle, ) { match event { zwp_confined_pointer_v1::Event::Confined => { data.confined(conn, qh, confined_pointer, &udata.surface, &udata.pointer) } zwp_confined_pointer_v1::Event::Unconfined => { data.unconfined(conn, qh, confined_pointer, &udata.surface, &udata.pointer) } _ => unreachable!(), } } } impl Dispatch for PointerConstraintsState where D: Dispatch + PointerConstraintsHandler, { fn event( data: &mut D, locked_pointer: &zwp_locked_pointer_v1::ZwpLockedPointerV1, event: zwp_locked_pointer_v1::Event, udata: &PointerConstraintData, conn: &Connection, qh: &QueueHandle, ) { match event { zwp_locked_pointer_v1::Event::Locked => { data.locked(conn, qh, locked_pointer, &udata.surface, &udata.pointer) } zwp_locked_pointer_v1::Event::Unlocked => { data.unlocked(conn, qh, locked_pointer, &udata.surface, &udata.pointer) } _ => unreachable!(), } } } #[macro_export] macro_rules! delegate_pointer_constraints { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1: $crate::globals::GlobalData ] => $crate::seat::pointer_constraints::PointerConstraintsState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1: $crate::seat::pointer_constraints::PointerConstraintData ] => $crate::seat::pointer_constraints::PointerConstraintsState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1: $crate::seat::pointer_constraints::PointerConstraintData ] => $crate::seat::pointer_constraints::PointerConstraintsState); }; } smithay-client-toolkit-0.18.0/src/seat/relative_pointer.rs000064400000000000000000000111371046102023000217760ustar 00000000000000use wayland_client::{ globals::GlobalList, protocol::wl_pointer, Connection, Dispatch, QueueHandle, }; use wayland_protocols::wp::relative_pointer::zv1::client::{ zwp_relative_pointer_manager_v1, zwp_relative_pointer_v1, }; use crate::{error::GlobalError, globals::GlobalData, registry::GlobalProxy}; #[derive(Debug)] pub struct RelativePointerState { relative_pointer_manager: GlobalProxy, } impl RelativePointerState { /// Bind `zwp_relative_pointer_manager_v1` global, if it exists pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Self where D: Dispatch + 'static, { let relative_pointer_manager = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData)); Self { relative_pointer_manager } } pub fn get_relative_pointer( &self, pointer: &wl_pointer::WlPointer, qh: &QueueHandle, ) -> Result where D: Dispatch + 'static, { let udata = RelativePointerData { wl_pointer: pointer.clone() }; Ok(self.relative_pointer_manager.get()?.get_relative_pointer(pointer, qh, udata)) } } #[derive(Debug)] pub struct RelativeMotionEvent { /// (x, y) motion vector pub delta: (f64, f64), /// Unaccelerated (x, y) motion vector pub delta_unaccel: (f64, f64), /// Timestamp in microseconds pub utime: u64, } pub trait RelativePointerHandler: Sized { fn relative_pointer_motion( &mut self, conn: &Connection, qh: &QueueHandle, relative_pointer: &zwp_relative_pointer_v1::ZwpRelativePointerV1, pointer: &wl_pointer::WlPointer, event: RelativeMotionEvent, ); } #[doc(hidden)] #[derive(Debug)] pub struct RelativePointerData { wl_pointer: wl_pointer::WlPointer, } impl Dispatch for RelativePointerState where D: Dispatch + RelativePointerHandler, { fn event( _data: &mut D, _manager: &zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, _event: zwp_relative_pointer_manager_v1::Event, _: &GlobalData, _conn: &Connection, _qh: &QueueHandle, ) { unreachable!() } } impl Dispatch for RelativePointerState where D: Dispatch + RelativePointerHandler, { fn event( data: &mut D, relative_pointer: &zwp_relative_pointer_v1::ZwpRelativePointerV1, event: zwp_relative_pointer_v1::Event, udata: &RelativePointerData, conn: &Connection, qh: &QueueHandle, ) { match event { zwp_relative_pointer_v1::Event::RelativeMotion { utime_hi, utime_lo, dx, dy, dx_unaccel, dy_unaccel, } => { data.relative_pointer_motion( conn, qh, relative_pointer, &udata.wl_pointer, RelativeMotionEvent { utime: ((utime_hi as u64) << 32) | (utime_lo as u64), delta: (dx, dy), delta_unaccel: (dx_unaccel, dy_unaccel), }, ); } _ => unreachable!(), } } } #[macro_export] macro_rules! delegate_relative_pointer { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1: $crate::globals::GlobalData ] => $crate::seat::relative_pointer::RelativePointerState); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1: $crate::seat::relative_pointer::RelativePointerData ] => $crate::seat::relative_pointer::RelativePointerState); }; } smithay-client-toolkit-0.18.0/src/seat/touch.rs000064400000000000000000000145451046102023000175530ustar 00000000000000use std::sync::Mutex; use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::protocol::wl_touch::{Event as TouchEvent, WlTouch}; use wayland_client::{Connection, Dispatch, QueueHandle}; use crate::seat::SeatState; #[derive(Debug)] pub struct TouchData { seat: WlSeat, inner: Mutex, } impl TouchData { /// Create the new touch data associated with the given seat. pub fn new(seat: WlSeat) -> Self { Self { seat, inner: Default::default() } } /// Get the associated seat from the data. pub fn seat(&self) -> &WlSeat { &self.seat } } #[derive(Debug, Default)] pub(crate) struct TouchDataInner { events: Vec, } #[macro_export] macro_rules! delegate_touch { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_touch::WlTouch: $crate::seat::touch::TouchData ] => $crate::seat::SeatState ); }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, touch: [$($td:ty),* $(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $( $crate::reexports::client::protocol::wl_touch::WlTouch: $td, )* ] => $crate::seat::SeatState ); }; } pub trait TouchDataExt: Send + Sync { fn touch_data(&self) -> &TouchData; } impl TouchDataExt for TouchData { fn touch_data(&self) -> &TouchData { self } } pub trait TouchHandler: Sized { /// New touch point. /// /// Indicates a new touch point has appeared on the surface, starting a touch sequence. The ID /// associated with this event identifies this touch point for devices with multi-touch and /// will be referenced in future events. /// /// The associated touch ID ceases to be valid after the touch up event with the associated ID /// and may be reused for other touch points after that. /// /// Coordinates are surface-local. #[allow(clippy::too_many_arguments)] fn down( &mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch, serial: u32, time: u32, surface: WlSurface, id: i32, position: (f64, f64), ); /// End of touch sequence. fn up( &mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch, serial: u32, time: u32, id: i32, ); /// Touch point motion. /// /// Coordinates are surface-local. fn motion( &mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch, time: u32, id: i32, position: (f64, f64), ); /// Touch point shape change. /// /// The shape of a touch point is approximated by an ellipse through the major and minor axis /// length. Major always represents the larger of the two axis and is orthogonal to minor. /// /// The dimensions are specified in surface-local coordinates and the locations reported by /// other events always report the center of the ellipse. fn shape( &mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch, id: i32, major: f64, minor: f64, ); /// Touch point shape orientation. /// /// The orientation describes the clockwise angle of a touch point's major axis to the positive /// surface y-axis and is normalized to the -180° to +180° range. fn orientation( &mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch, id: i32, orientation: f64, ); /// Cancel active touch sequence. /// /// This indicates that the compositor has cancelled the active touch sequence, for example due /// to detection of a touch gesture. fn cancel(&mut self, conn: &Connection, qh: &QueueHandle, touch: &WlTouch); } impl Dispatch for SeatState where D: Dispatch + TouchHandler, U: TouchDataExt, { fn event( data: &mut D, touch: &WlTouch, event: TouchEvent, udata: &U, conn: &Connection, qh: &QueueHandle, ) { let udata = udata.touch_data(); match event { // Buffer events until frame is received. TouchEvent::Down { .. } | TouchEvent::Up { .. } | TouchEvent::Motion { .. } | TouchEvent::Shape { .. } | TouchEvent::Orientation { .. } => { let mut guard = udata.inner.lock().unwrap(); guard.events.push(event); } // Process all buffered events. TouchEvent::Frame => { let mut guard = udata.inner.lock().unwrap(); for event in guard.events.drain(..) { process_framed_event(data, touch, conn, qh, event); } } TouchEvent::Cancel => { let mut guard = udata.inner.lock().unwrap(); guard.events.clear(); data.cancel(conn, qh, touch); } _ => unreachable!(), } } } /// Process a single frame-buffered touch event. fn process_framed_event( data: &mut D, touch: &WlTouch, conn: &Connection, qh: &QueueHandle, event: TouchEvent, ) where D: TouchHandler, { match event { TouchEvent::Down { serial, time, surface, id, x, y } => { data.down(conn, qh, touch, serial, time, surface, id, (x, y)); } TouchEvent::Up { serial, time, id } => { data.up(conn, qh, touch, serial, time, id); } TouchEvent::Motion { time, id, x, y } => { data.motion(conn, qh, touch, time, id, (x, y)); } TouchEvent::Shape { id, major, minor } => { data.shape(conn, qh, touch, id, major, minor); } TouchEvent::Orientation { id, orientation } => { data.orientation(conn, qh, touch, id, orientation); } // No other events should be frame-buffered. _ => unreachable!(), } } smithay-client-toolkit-0.18.0/src/shell/mod.rs000064400000000000000000000114161046102023000173550ustar 00000000000000//! # Shell abstractions //! //! A shell describes a set of wayland protocol extensions which define the capabilities of a surface and how //! the surface is displayed. //! //! ## Cross desktop group (XDG) shell //! //! The XDG shell describes the semantics of desktop application windows. //! //! The XDG shell defines two types of surfaces: //! - [`Window`] - An application window[^window]. //! - [`Popup`] - A child surface positioned relative to a window. //! //! ### Why use the XDG shell //! //! The XDG shell is the primary protocol through which application windows are created. You can be near //! certain every desktop compositor will implement this shell so that applications may create windows. //! //! See the [XDG shell module documentation] for more information about creating application windows. //! //! ## Layer shell //! //! The layer shell is a protocol which allows the creation of "layers". A layer refers to a surface rendered //! at some specific z-depth relative to other layers. A layer may also be anchored to some edge and corner of //! the screen. //! //! The layer shell defines one type of surface: the [`wlr_layer::LayerSurface`]. //! //! There is no guarantee that the layer shell will be available in every compositor. //! //! ### Why use the layer shell //! //! The layer shell may be used to implement many desktop shell components, such as backgrounds, docks and //! launchers. //! //! [^window]: The XDG shell protocol actually refers to a window as a toplevel surface, but we use the more //! familiar term "window" for the sake of clarity. //! //! [XDG shell module documentation]: self::xdg //! [`Window`]: self::xdg::window::Window //! [`Popup`]: self::xdg::popup::Popup //! //! [`Layer`]: self::layer::LayerSurface use wayland_client::{ protocol::{wl_buffer, wl_output, wl_region, wl_surface}, Proxy, }; pub mod wlr_layer; pub mod xdg; /// An unsupported operation, often due to the version of the protocol. #[derive(Debug, Default)] pub struct Unsupported; /// Functionality shared by all [`wl_surface::WlSurface`] backed shell role objects. pub trait WaylandSurface: Sized { /// The underlying [`WlSurface`](wl_surface::WlSurface). fn wl_surface(&self) -> &wl_surface::WlSurface; fn attach(&self, buffer: Option<&wl_buffer::WlBuffer>, x: u32, y: u32) { // In version 5 and later, the x and y offset of `wl_surface::attach` must be zero and uses the // `offset` request instead. let (attach_x, attach_y) = if self.wl_surface().version() >= 5 { (0, 0) } else { (x, y) }; self.wl_surface().attach(buffer, attach_x as i32, attach_y as i32); if self.wl_surface().version() >= 5 { // Ignore the error since the version is garunteed to be at least 5 here. let _ = self.offset(x, y); } } // TODO: Damage (Buffer and Surface-local) // TODO: Frame (a nice helper for this could exist). fn set_opaque_region(&self, region: Option<&wl_region::WlRegion>) { self.wl_surface().set_opaque_region(region); } fn set_input_region(&self, region: Option<&wl_region::WlRegion>) { self.wl_surface().set_input_region(region); } fn set_buffer_transform(&self, transform: wl_output::Transform) -> Result<(), Unsupported> { if self.wl_surface().version() < 2 { return Err(Unsupported); } self.wl_surface().set_buffer_transform(transform); Ok(()) } fn set_buffer_scale(&self, scale: u32) -> Result<(), Unsupported> { if self.wl_surface().version() < 3 { return Err(Unsupported); } self.wl_surface().set_buffer_scale(scale as i32); Ok(()) } fn offset(&self, x: u32, y: u32) -> Result<(), Unsupported> { if self.wl_surface().version() < 5 { return Err(Unsupported); } self.wl_surface().offset(x as i32, y as i32); Ok(()) } /// Commits pending surface state. /// /// On commit, the pending double buffered state from the surface, including role dependent state is /// applied. /// /// # Initial commit /// /// In many protocol extensions, the concept of an initial commit is used. A initial commit provides the /// initial state of a surface to the compositor. For example with the [xdg shell](xdg), /// creating a window requires an initial commit. /// /// # Protocol Errors /// /// If the commit is the initial commit, no buffers must have been attached to the surface. This rule /// applies whether attaching the buffer was done using [`WaylandSurface::attach`] or under the hood in /// via window system integration in graphics APIs such as Vulkan (using `vkQueuePresentKHR`) and EGL /// (using `eglSwapBuffers`). fn commit(&self) { self.wl_surface().commit(); } } smithay-client-toolkit-0.18.0/src/shell/wlr_layer/dispatch.rs000064400000000000000000000055751046102023000224060ustar 00000000000000use wayland_client::{Connection, Dispatch, QueueHandle}; use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{ error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, }; use super::{LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, LayerSurfaceData}; // Layer shell has only added requests and enum variants in versions 2-4, so its client-facing API // is still compatible. impl ProvidesBoundGlobal for LayerShell { fn bound_global(&self) -> Result { Ok(self.wlr_layer_shell.clone()) } } impl ProvidesBoundGlobal for LayerShell { fn bound_global(&self) -> Result { Ok(self.wlr_layer_shell.clone()) } } impl ProvidesBoundGlobal for LayerShell { fn bound_global(&self) -> Result { Ok(self.wlr_layer_shell.clone()) } } impl ProvidesBoundGlobal for LayerShell { fn bound_global(&self) -> Result { Ok(self.wlr_layer_shell.clone()) } } impl Dispatch for LayerShell where D: Dispatch + LayerShellHandler + 'static, { fn event( _: &mut D, _: &zwlr_layer_shell_v1::ZwlrLayerShellV1, _: zwlr_layer_shell_v1::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("zwlr_layer_shell_v1 has no events") } } impl Dispatch for LayerShell where D: Dispatch + LayerShellHandler + 'static, { fn event( data: &mut D, surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, event: zwlr_layer_surface_v1::Event, _udata: &LayerSurfaceData, conn: &Connection, qh: &QueueHandle, ) { if let Some(layer_surface) = LayerSurface::from_wlr_surface(surface) { match event { zwlr_layer_surface_v1::Event::Configure { serial, width, height } => { surface.ack_configure(serial); let configure = LayerSurfaceConfigure { new_size: (width, height) }; data.configure(conn, qh, &layer_surface, configure, serial); } zwlr_layer_surface_v1::Event::Closed => { data.closed(conn, qh, &layer_surface); } _ => unreachable!(), } } } } smithay-client-toolkit-0.18.0/src/shell/wlr_layer/mod.rs000064400000000000000000000256641046102023000213670ustar 00000000000000mod dispatch; use std::{ convert::TryFrom, sync::{Arc, Weak}, }; use bitflags::bitflags; use wayland_client::{ globals::{BindError, GlobalList}, protocol::{wl_output, wl_surface}, Connection, Dispatch, Proxy, QueueHandle, }; use wayland_protocols::xdg::shell::client::xdg_popup::XdgPopup; use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{compositor::Surface, globals::GlobalData}; use super::WaylandSurface; #[derive(Debug)] pub struct LayerShell { wlr_layer_shell: zwlr_layer_shell_v1::ZwlrLayerShellV1, } impl LayerShell { /// Binds the wlr layer shell global, `zwlr_layer_shell_v1`. /// /// # Errors /// /// This function will return [`Err`] if the `zwlr_layer_shell_v1` global is not available. pub fn bind( globals: &GlobalList, qh: &QueueHandle, ) -> Result where State: Dispatch + LayerShellHandler + 'static, { let wlr_layer_shell = globals.bind(qh, 1..=4, GlobalData)?; Ok(LayerShell { wlr_layer_shell }) } #[must_use] pub fn create_layer_surface( &self, qh: &QueueHandle, surface: impl Into, layer: Layer, namespace: Option>, output: Option<&wl_output::WlOutput>, ) -> LayerSurface where State: Dispatch + 'static, { // Freeze the queue during the creation of the Arc to avoid a race between events on the // new objects being processed and the Weak in the PopupData becoming usable. let freeze = qh.freeze(); let surface = surface.into(); let inner = Arc::new_cyclic(|weak| { let layer_surface = self.wlr_layer_shell.get_layer_surface( surface.wl_surface(), output, layer.into(), namespace.map(Into::into).unwrap_or_default(), qh, LayerSurfaceData { inner: weak.clone() }, ); LayerSurfaceInner { wl_surface: surface, kind: SurfaceKind::Wlr(layer_surface) } }); drop(freeze); LayerSurface(inner) } } /// Handler for operations on a [`LayerSurface`] pub trait LayerShellHandler: Sized { /// The layer surface has been closed. /// /// When this requested is called, the layer surface is no longer shown and all handles of the [`LayerSurface`] /// should be dropped. fn closed(&mut self, conn: &Connection, qh: &QueueHandle, layer: &LayerSurface); /// Apply a suggested surface change. /// /// When this function is called, the compositor is requesting the layer surfaces's size or state to change. fn configure( &mut self, conn: &Connection, qh: &QueueHandle, layer: &LayerSurface, configure: LayerSurfaceConfigure, serial: u32, ); } #[derive(Debug, Clone)] pub struct LayerSurface(Arc); impl PartialEq for LayerSurface { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } impl LayerSurface { pub fn from_wlr_surface( surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ) -> Option { surface.data::().and_then(|data| data.inner.upgrade()).map(LayerSurface) } pub fn get_popup(&self, popup: &XdgPopup) { match self.0.kind { SurfaceKind::Wlr(ref s) => s.get_popup(popup), } } // Double buffered state pub fn set_size(&self, width: u32, height: u32) { match self.0.kind { SurfaceKind::Wlr(ref wlr) => wlr.set_size(width, height), } } pub fn set_anchor(&self, anchor: Anchor) { match self.0.kind { // We currently rely on the bitsets being the same SurfaceKind::Wlr(ref wlr) => { wlr.set_anchor(zwlr_layer_surface_v1::Anchor::from_bits_truncate(anchor.bits())) } } } pub fn set_exclusive_zone(&self, zone: i32) { match self.0.kind { SurfaceKind::Wlr(ref wlr) => wlr.set_exclusive_zone(zone), } } pub fn set_margin(&self, top: i32, right: i32, bottom: i32, left: i32) { match self.0.kind { SurfaceKind::Wlr(ref wlr) => wlr.set_margin(top, right, bottom, left), } } pub fn set_keyboard_interactivity(&self, value: KeyboardInteractivity) { match self.0.kind { SurfaceKind::Wlr(ref wlr) => wlr.set_keyboard_interactivity(value.into()), } } pub fn set_layer(&self, layer: Layer) { match self.0.kind { SurfaceKind::Wlr(ref wlr) => wlr.set_layer(layer.into()), } } pub fn kind(&self) -> &SurfaceKind { &self.0.kind } } impl WaylandSurface for LayerSurface { fn wl_surface(&self) -> &wl_surface::WlSurface { self.0.wl_surface.wl_surface() } } #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum SurfaceKind { Wlr(zwlr_layer_surface_v1::ZwlrLayerSurfaceV1), } #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum KeyboardInteractivity { /// No keyboard focus is possible. /// /// This is the default value for all newly created layer shells. None, /// Request exclusive keyboard focus if the layer is above shell surfaces. /// /// For [`Layer::Top`] and [`Layer::Overlay`], the seat will always give exclusive access to the layer /// which has this interactivity mode set. /// /// This setting is intended for applications that need to ensure they receive all keyboard events, such /// as a lock screen or a password prompt. Exclusive, /// The compositor should focus and unfocus this surface by the user in an implementation specific manner. /// /// Compositors may use their normal mechanisms to manage keyboard focus between layers and regular /// desktop surfaces. /// /// This setting is intended for applications which allow keyboard interaction. OnDemand, } impl Default for KeyboardInteractivity { fn default() -> Self { Self::None } } /// The z-depth of a layer. /// /// These values indicate which order in which layer surfaces are rendered. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Layer { Background, Bottom, Top, Overlay, } /// Error when converting a [`zwlr_layer_shell_v1::Layer`] to a [`Layer`] #[derive(Debug, thiserror::Error)] #[error("unknown layer")] pub struct UnknownLayer; bitflags! { /// Specifies which edges and corners a layer should be placed at in the anchor rectangle. /// /// A combination of two orthogonal edges will cause the layer's anchor point to be the intersection of /// the edges. For example [`Anchor::TOP`] and [`Anchor::LEFT`] will result in an anchor point in the top /// left of the anchor rectangle. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Anchor: u32 { /// Top edge of the anchor rectangle. const TOP = 1; /// The bottom edge of the anchor rectangle. const BOTTOM = 2; /// The left edge of the anchor rectangle. const LEFT = 4; /// The right edge of the anchor rectangle. const RIGHT = 8; } } /// A layer surface configure. /// /// A configure describes a compositor request to resize the layer surface or change it's state. #[non_exhaustive] #[derive(Debug, Clone)] pub struct LayerSurfaceConfigure { /// The compositor suggested new size of the layer in surface-local coordinates. /// /// The size is a hint, meaning the new size can be ignored. A smaller size could be picked to satisfy /// some aspect ratio or resize in steps. If the size is smaller than suggested and the layer surface is /// anchored to two opposite anchors then the layer surface will be centered on that axis. /// /// If the width is zero, you may choose any width you want. If the height is zero, you may choose any /// height you want. pub new_size: (u32, u32), } #[derive(Debug)] pub struct LayerSurfaceData { inner: Weak, } impl LayerSurfaceData { pub fn layer_surface(&self) -> Option { self.inner.upgrade().map(LayerSurface) } } #[macro_export] macro_rules! delegate_layer { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1: $crate::globals::GlobalData ] => $crate::shell::wlr_layer::LayerShell); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1: $crate::shell::wlr_layer::LayerSurfaceData ] => $crate::shell::wlr_layer::LayerShell); }; } #[derive(Debug)] struct LayerSurfaceInner { wl_surface: Surface, kind: SurfaceKind, } impl TryFrom for Layer { type Error = UnknownLayer; fn try_from(layer: zwlr_layer_shell_v1::Layer) -> Result { match layer { zwlr_layer_shell_v1::Layer::Background => Ok(Self::Background), zwlr_layer_shell_v1::Layer::Bottom => Ok(Self::Bottom), zwlr_layer_shell_v1::Layer::Top => Ok(Self::Top), zwlr_layer_shell_v1::Layer::Overlay => Ok(Self::Overlay), _ => Err(UnknownLayer), } } } impl From for zwlr_layer_shell_v1::Layer { fn from(depth: Layer) -> Self { match depth { Layer::Background => Self::Background, Layer::Bottom => Self::Bottom, Layer::Top => Self::Top, Layer::Overlay => Self::Overlay, } } } impl From for zwlr_layer_surface_v1::KeyboardInteractivity { fn from(interactivity: KeyboardInteractivity) -> Self { match interactivity { KeyboardInteractivity::None => zwlr_layer_surface_v1::KeyboardInteractivity::None, KeyboardInteractivity::Exclusive => { zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive } KeyboardInteractivity::OnDemand => { zwlr_layer_surface_v1::KeyboardInteractivity::OnDemand } } } } impl Drop for LayerSurfaceInner { fn drop(&mut self) { // Layer shell protocol dictates we must destroy the role object before the surface. match self.kind { SurfaceKind::Wlr(ref wlr) => wlr.destroy(), } // Surface will destroy the wl_surface // self.wl_surface.destroy(); } } smithay-client-toolkit-0.18.0/src/shell/xdg/fallback_frame.rs000064400000000000000000000570221046102023000222740ustar 00000000000000//! The default fallback frame which is intended to show some very basic derocations. use std::mem; use std::sync::Arc; use std::time::Duration; use std::{error::Error, num::NonZeroU32}; use crate::reexports::client::{ protocol::{wl_shm, wl_subsurface::WlSubsurface, wl_surface::WlSurface}, Dispatch, Proxy, QueueHandle, }; use crate::reexports::csd_frame::{ DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowManagerCapabilities, WindowState, }; use crate::{ compositor::SurfaceData, seat::pointer::CursorIcon, shell::WaylandSurface, shm::{slot::SlotPool, Shm}, subcompositor::{SubcompositorState, SubsurfaceData}, }; use wayland_backend::client::ObjectId; /// The size of the header bar. const HEADER_SIZE: u32 = 24; /// The size of the border. const BORDER_SIZE: u32 = 4; const HEADER: usize = 0; const TOP_BORDER: usize = 1; const RIGHT_BORDER: usize = 2; const BOTTOM_BORDER: usize = 3; const LEFT_BORDER: usize = 4; const BTN_ICON_COLOR: u32 = 0xFFCCCCCC; const BTN_HOVER_BG: u32 = 0xFF808080; const PRIMARY_COLOR_ACTIVE: u32 = 0xFF3A3A3A; const PRIMARY_COLOR_INACTIVE: u32 = 0xFF242424; /// The default ugly frame. #[derive(Debug)] pub struct FallbackFrame { /// The parent surface. parent: WlSurface, /// The latest window state. state: WindowState, /// The wm capabilities. wm_capabilities: WindowManagerCapabilities, /// Whether the frame is resizable. resizable: bool, /// Whether the frame is waiting for redraw. dirty: bool, /// The location of the mouse. mouse_location: Location, /// The location of the mouse. mouse_coords: (i32, i32), /// The frame rendering data. When `None` the frame is hidden. render_data: Option, /// Whether the frame should sync with the parent. /// /// This should happen in reaction to scale or resize changes. should_sync: bool, /// The active scale factor of the frame. scale_factor: f64, /// The frame queue handle. queue_handle: QueueHandle, /// The memory pool to use for drawing. pool: SlotPool, /// The subcompositor. subcompositor: Arc, /// Buttons state. buttons: [Option; 3], } impl FallbackFrame where State: Dispatch + Dispatch + 'static, { pub fn new( parent: &impl WaylandSurface, shm: &Shm, subcompositor: Arc, queue_handle: QueueHandle, ) -> Result> { let parent = parent.wl_surface().clone(); let pool = SlotPool::new(1, shm)?; let render_data = Some(FrameRenderData::new(&parent, &subcompositor, &queue_handle)); let wm_capabilities = WindowManagerCapabilities::all(); Ok(Self { parent, resizable: true, state: WindowState::empty(), wm_capabilities, dirty: true, scale_factor: 1., pool, should_sync: true, queue_handle, subcompositor, render_data, mouse_location: Location::None, mouse_coords: (0, 0), buttons: Self::supported_buttons(wm_capabilities), }) } fn supported_buttons(wm_capabilities: WindowManagerCapabilities) -> [Option; 3] { let maximize = wm_capabilities .contains(WindowManagerCapabilities::MAXIMIZE) .then_some(UIButton::Maximize); let minimize = wm_capabilities .contains(WindowManagerCapabilities::MINIMIZE) .then_some(UIButton::Minimize); [Some(UIButton::Close), maximize, minimize] } fn precise_location( buttons: &[Option], old: Location, width: u32, x: f64, y: f64, ) -> Location { match old { Location::Head | Location::Button(_) => Self::find_button(buttons, x, y, width), Location::Top | Location::TopLeft | Location::TopRight => { if x <= f64::from(BORDER_SIZE) { Location::TopLeft } else if x >= f64::from(width - BORDER_SIZE) { Location::TopRight } else { Location::Top } } Location::Bottom | Location::BottomLeft | Location::BottomRight => { if x <= f64::from(BORDER_SIZE) { Location::BottomLeft } else if x >= f64::from(width - BORDER_SIZE) { Location::BottomRight } else { Location::Bottom } } other => other, } } fn find_button(buttons: &[Option], x: f64, y: f64, w: u32) -> Location { for (idx, &button) in buttons.iter().flatten().enumerate() { let idx = idx as u32; if w >= (idx + 1) * HEADER_SIZE && x >= f64::from(w - (idx + 1) * HEADER_SIZE) && x <= f64::from(w - idx * HEADER_SIZE) && y <= f64::from(HEADER_SIZE) && y >= f64::from(0) { return Location::Button(button); } } Location::Head } #[inline] fn part_index_for_surface(&mut self, surface_id: &ObjectId) -> Option { self.render_data.as_ref()?.parts.iter().position(|part| &part.surface.id() == surface_id) } fn draw_buttons( buttons: &[Option], canvas: &mut [u8], width: u32, scale: u32, is_active: bool, mouse_location: &Location, ) { let scale = scale as usize; for (idx, &button) in buttons.iter().flatten().enumerate() { if width >= (idx + 1) as u32 * HEADER_SIZE { if is_active && mouse_location == &Location::Button(button) { Self::draw_button( canvas, idx * HEADER_SIZE as usize, scale, width as usize, BTN_HOVER_BG.to_le_bytes(), ); } Self::draw_icon( canvas, width as usize, idx * HEADER_SIZE as usize, scale, BTN_ICON_COLOR.to_le_bytes(), button, ); } } } fn draw_button( canvas: &mut [u8], x_offset: usize, scale: usize, width: usize, btn_color: [u8; 4], ) { let h = HEADER_SIZE as usize; let x_start = width - h - x_offset; // main square for y in 0..h * scale { let canvas = &mut canvas [(x_start + y * width) * 4 * scale..(x_start + y * width + h) * scale * 4]; for pixel in canvas.chunks_exact_mut(4) { pixel[0] = btn_color[0]; pixel[1] = btn_color[1]; pixel[2] = btn_color[2]; pixel[3] = btn_color[3]; } } } fn draw_icon( canvas: &mut [u8], width: usize, x_offset: usize, scale: usize, icon_color: [u8; 4], icon: UIButton, ) { let h = HEADER_SIZE as usize; let sh = scale * h; let x_start = width - h - x_offset; match icon { UIButton::Close => { // Draw black rectangle for y in sh / 4..3 * sh / 4 { let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale ..(x_start + y * width + 3 * h / 4) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } } } UIButton::Maximize => { // Draw an empty rectangle for y in 2 * sh / 8..3 * sh / 8 { let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale ..(x_start + y * width + 3 * h / 4) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } } for y in 3 * sh / 8..5 * sh / 8 { let line = &mut canvas[(x_start + y * width + 2 * h / 8) * 4 * scale ..(x_start + y * width + 3 * h / 8) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } let line = &mut canvas[(x_start + y * width + 5 * h / 8) * 4 * scale ..(x_start + y * width + 6 * h / 8) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } } for y in 5 * sh / 8..6 * sh / 8 { let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale ..(x_start + y * width + 3 * h / 4) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } } } UIButton::Minimize => { // Draw an underline for y in 5 * sh / 8..3 * sh / 4 { let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale ..(x_start + y * width + 3 * h / 4) * 4 * scale]; for pixel in line.chunks_exact_mut(4) { pixel[0] = icon_color[0]; pixel[1] = icon_color[1]; pixel[2] = icon_color[2]; pixel[3] = icon_color[3]; } } } } } } impl DecorationsFrame for FallbackFrame where State: Dispatch + Dispatch + 'static, { fn set_scaling_factor(&mut self, scale_factor: f64) { self.scale_factor = scale_factor; self.dirty = true; self.should_sync = true; } fn on_click( &mut self, _timestamp: Duration, click: FrameClick, pressed: bool, ) -> Option { // Handle alternate click before everything else. if click == FrameClick::Alternate { return if Location::Head != self.mouse_location || !self.wm_capabilities.contains(WindowManagerCapabilities::WINDOW_MENU) { None } else { Some(FrameAction::ShowMenu( self.mouse_coords.0, self.mouse_coords.1 - HEADER_SIZE as i32, )) }; } let resize = pressed && self.resizable; match self.mouse_location { Location::Head if pressed => Some(FrameAction::Move), Location::Button(UIButton::Close) if !pressed => Some(FrameAction::Close), Location::Button(UIButton::Minimize) if !pressed => Some(FrameAction::Minimize), Location::Button(UIButton::Maximize) if !pressed && !self.state.contains(WindowState::MAXIMIZED) => { Some(FrameAction::Maximize) } Location::Button(UIButton::Maximize) if !pressed && self.state.contains(WindowState::MAXIMIZED) => { Some(FrameAction::UnMaximize) } Location::Top if resize => Some(FrameAction::Resize(ResizeEdge::Top)), Location::TopLeft if resize => Some(FrameAction::Resize(ResizeEdge::TopLeft)), Location::Left if resize => Some(FrameAction::Resize(ResizeEdge::Left)), Location::BottomLeft if resize => Some(FrameAction::Resize(ResizeEdge::BottomLeft)), Location::Bottom if resize => Some(FrameAction::Resize(ResizeEdge::Bottom)), Location::BottomRight if resize => Some(FrameAction::Resize(ResizeEdge::BottomRight)), Location::Right if resize => Some(FrameAction::Resize(ResizeEdge::Right)), Location::TopRight if resize => Some(FrameAction::Resize(ResizeEdge::TopRight)), _ => None, } } fn click_point_moved( &mut self, _timestamp: Duration, surface_id: &ObjectId, x: f64, y: f64, ) -> Option { let part_index = self.part_index_for_surface(surface_id)?; let location = match part_index { LEFT_BORDER => Location::Left, RIGHT_BORDER => Location::Right, BOTTOM_BORDER => Location::Bottom, TOP_BORDER => Location::Top, _ => Location::Head, }; let old_location = self.mouse_location; self.mouse_coords = (x as i32, y as i32); self.mouse_location = Self::precise_location( &self.buttons, location, self.render_data.as_ref().unwrap().parts[part_index].width, x, y, ); // Set dirty if we moved the cursor between the buttons. self.dirty |= (matches!(old_location, Location::Button(_)) || matches!(self.mouse_location, Location::Button(_))) && old_location != self.mouse_location; Some(match self.mouse_location { Location::Top => CursorIcon::NResize, Location::TopRight => CursorIcon::NeResize, Location::Right => CursorIcon::EResize, Location::BottomRight => CursorIcon::SeResize, Location::Bottom => CursorIcon::SResize, Location::BottomLeft => CursorIcon::SwResize, Location::Left => CursorIcon::WResize, Location::TopLeft => CursorIcon::NwResize, _ => CursorIcon::Default, }) } fn click_point_left(&mut self) { self.mouse_location = Location::None; self.dirty = true; } fn set_hidden(&mut self, hidden: bool) { if self.is_hidden() == hidden { return; } if hidden { self.render_data = None; } else { let _ = self.pool.resize(1); self.render_data = Some(FrameRenderData::new(&self.parent, &self.subcompositor, &self.queue_handle)); } } fn set_resizable(&mut self, resizable: bool) { self.resizable = resizable; } fn update_state(&mut self, state: WindowState) { let difference = self.state.symmetric_difference(state); self.state = state; self.dirty |= !difference .intersection(WindowState::ACTIVATED | WindowState::FULLSCREEN | WindowState::MAXIMIZED) .is_empty(); } fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) { let parts = &mut self.render_data.as_mut().expect("trying to resize hidden frame").parts; let width = width.get(); let height = height.get(); parts[HEADER].width = width; parts[TOP_BORDER].width = width + 2 * BORDER_SIZE; parts[BOTTOM_BORDER].width = width + 2 * BORDER_SIZE; parts[BOTTOM_BORDER].pos.1 = height as i32; parts[LEFT_BORDER].height = height + HEADER_SIZE; parts[RIGHT_BORDER].height = parts[LEFT_BORDER].height; parts[RIGHT_BORDER].pos.0 = width as i32; self.dirty = true; self.should_sync = true; } fn subtract_borders( &self, width: NonZeroU32, height: NonZeroU32, ) -> (Option, Option) { if self.state.contains(WindowState::FULLSCREEN) || self.render_data.is_none() { (Some(width), Some(height)) } else { ( NonZeroU32::new(width.get().saturating_sub(2 * BORDER_SIZE)), NonZeroU32::new(height.get().saturating_sub(HEADER_SIZE + 2 * BORDER_SIZE)), ) } } fn add_borders(&self, width: u32, height: u32) -> (u32, u32) { if self.state.contains(WindowState::FULLSCREEN) || self.render_data.is_none() { (width, height) } else { (width + 2 * BORDER_SIZE, height + (HEADER_SIZE + 2 * BORDER_SIZE)) } } fn is_hidden(&self) -> bool { self.render_data.is_none() } fn location(&self) -> (i32, i32) { if self.state.contains(WindowState::FULLSCREEN) || self.is_hidden() { (0, 0) } else { self.render_data.as_ref().unwrap().parts[TOP_BORDER].pos } } fn is_dirty(&self) -> bool { self.dirty } fn draw(&mut self) -> bool { let render_data = match self.render_data.as_mut() { Some(render_data) => render_data, None => return false, }; // Reset the dirty bit and sync option. self.dirty = false; let should_sync = mem::take(&mut self.should_sync); if self.state.contains(WindowState::FULLSCREEN) { // Don't draw the decorations for the full screen surface. for part in &render_data.parts { part.surface.attach(None, 0, 0); part.surface.commit(); } return should_sync; } let is_active = self.state.contains(WindowState::ACTIVATED); let fill_color = if is_active { PRIMARY_COLOR_ACTIVE } else { PRIMARY_COLOR_INACTIVE }.to_le_bytes(); for (idx, part) in render_data.parts.iter().enumerate() { // We don't support fractinal scaling here, so round up. let scale = self.scale_factor.ceil() as i32; let (buffer, canvas) = match self.pool.create_buffer( part.width as i32 * scale, part.height as i32 * scale, part.width as i32 * 4 * scale, wl_shm::Format::Argb8888, ) { Ok((buffer, canvas)) => (buffer, canvas), Err(_) => continue, }; // Fill the canvas. for pixel in canvas.chunks_exact_mut(4) { pixel[0] = fill_color[0]; pixel[1] = fill_color[1]; pixel[2] = fill_color[2]; pixel[3] = fill_color[3]; } // Draw the buttons for the header. if idx == HEADER { Self::draw_buttons( &self.buttons, canvas, part.width, scale as u32, is_active, &self.mouse_location, ); } part.surface.set_buffer_scale(scale); if should_sync { part.subsurface.set_sync(); } else { part.subsurface.set_desync(); } // Update the subsurface position. part.subsurface.set_position(part.pos.0, part.pos.1); buffer.attach_to(&part.surface).expect("failed to attach the buffer"); if part.surface.version() >= 4 { part.surface.damage_buffer(0, 0, i32::MAX, i32::MAX); } else { part.surface.damage(0, 0, i32::MAX, i32::MAX); } part.surface.commit(); } should_sync } fn update_wm_capabilities(&mut self, capabilities: WindowManagerCapabilities) { self.dirty |= self.wm_capabilities != capabilities; self.wm_capabilities = capabilities; self.buttons = Self::supported_buttons(capabilities); } fn set_title(&mut self, _: impl Into) {} } /// Inner state to simplify dropping. #[derive(Debug)] struct FrameRenderData { /// The header subsurface. parts: [FramePart; 5], } impl FrameRenderData { fn new( parent: &WlSurface, subcompositor: &SubcompositorState, queue_handle: &QueueHandle, ) -> Self where State: Dispatch + Dispatch + 'static, { let parts = [ // Header. FramePart::new( subcompositor.create_subsurface(parent.clone(), queue_handle), 0, HEADER_SIZE, (0, -(HEADER_SIZE as i32)), ), // Top border. FramePart::new( subcompositor.create_subsurface(parent.clone(), queue_handle), 0, BORDER_SIZE, (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32 + BORDER_SIZE as i32)), ), // Right border. FramePart::new( subcompositor.create_subsurface(parent.clone(), queue_handle), BORDER_SIZE, 0, (0, -(HEADER_SIZE as i32)), ), // Bottom border. FramePart::new( subcompositor.create_subsurface(parent.clone(), queue_handle), 0, BORDER_SIZE, (-(BORDER_SIZE as i32), 0), ), // Left border. FramePart::new( subcompositor.create_subsurface(parent.clone(), queue_handle), BORDER_SIZE, 0, (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32)), ), ]; Self { parts } } } #[derive(Debug)] struct FramePart { /// The surface used for the frame part. subsurface: WlSubsurface, /// The surface used for this part. surface: WlSurface, /// The width of the Frame part in logical pixels. width: u32, /// The height of the Frame part in logical pixels. height: u32, /// The position for the subsurface. pos: (i32, i32), } impl FramePart { fn new(surfaces: (WlSubsurface, WlSurface), width: u32, height: u32, pos: (i32, i32)) -> Self { let (subsurface, surface) = surfaces; // XXX sync subsurfaces with the main surface. subsurface.set_sync(); Self { surface, subsurface, width, height, pos } } } impl Drop for FramePart { fn drop(&mut self) { self.subsurface.destroy(); self.surface.destroy(); } } /// The location inside the #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Location { /// The location doesn't belong to the frame. None, /// Header bar. Head, /// Top border. Top, /// Top right corner. TopRight, /// Right border. Right, /// Bottom right corner. BottomRight, /// Bottom border. Bottom, /// Bottom left corner. BottomLeft, /// Left border. Left, /// Top left corner. TopLeft, /// One of the buttons. Button(UIButton), } /// The frame button. #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum UIButton { /// The minimize button, the left most. Minimize, /// The maximize button, in the middle. Maximize, /// The close botton, the right most. Close, } smithay-client-toolkit-0.18.0/src/shell/xdg/mod.rs000064400000000000000000000300421046102023000201330ustar 00000000000000//! ## Cross desktop group (XDG) shell // TODO: Examples use std::os::unix::io::OwnedFd; use std::sync::{Arc, Mutex}; use crate::reexports::client::globals::{BindError, GlobalList}; use crate::reexports::client::Connection; use crate::reexports::client::{protocol::wl_surface, Dispatch, Proxy, QueueHandle}; use crate::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::Mode; use crate::reexports::protocols::xdg::decoration::zv1::client::{ zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1, }; use crate::reexports::protocols::xdg::shell::client::{ xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base, }; use crate::compositor::Surface; use crate::error::GlobalError; use crate::globals::{GlobalData, ProvidesBoundGlobal}; use crate::registry::GlobalProxy; use self::window::inner::WindowInner; use self::window::{ DecorationMode, Window, WindowConfigure, WindowData, WindowDecorations, WindowHandler, }; use super::WaylandSurface; pub mod fallback_frame; pub mod popup; pub mod window; /// The xdg shell globals. #[derive(Debug)] pub struct XdgShell { xdg_wm_base: xdg_wm_base::XdgWmBase, xdg_decoration_manager: GlobalProxy, } impl XdgShell { /// Binds the xdg shell global, `xdg_wm_base`. /// /// If available, the `zxdg_decoration_manager_v1` global will be bound to allow server side decorations /// for windows. /// /// # Errors /// /// This function will return [`Err`] if the `xdg_wm_base` global is not available. pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where State: Dispatch + Dispatch + 'static, { let xdg_wm_base = globals.bind(qh, 1..=6, GlobalData)?; let xdg_decoration_manager = GlobalProxy::from(globals.bind(qh, 1..=1, GlobalData)); Ok(Self { xdg_wm_base, xdg_decoration_manager }) } /// Creates a new, unmapped window. /// /// # Protocol errors /// /// If the surface already has a role object, the compositor will raise a protocol error. /// /// A surface is considered to have a role object if some other type of surface was created using the /// surface. For example, creating a window, popup, layer or subsurface all assign a role object to a /// surface. /// /// This function takes ownership of the surface. /// /// For more info related to creating windows, see [`the module documentation`](self). #[must_use = "Dropping all window handles will destroy the window"] pub fn create_window( &self, surface: impl Into, decorations: WindowDecorations, qh: &QueueHandle, ) -> Window where State: Dispatch + Dispatch + Dispatch + WindowHandler + 'static, { let decoration_manager = self.xdg_decoration_manager.get().ok(); let surface = surface.into(); // Freeze the queue during the creation of the Arc to avoid a race between events on the // new objects being processed and the Weak in the PopupData becoming usable. let freeze = qh.freeze(); let inner = Arc::new_cyclic(|weak| { let xdg_surface = self.xdg_wm_base.get_xdg_surface( surface.wl_surface(), qh, WindowData(weak.clone()), ); let xdg_surface = XdgShellSurface { surface, xdg_surface }; let xdg_toplevel = xdg_surface.xdg_surface().get_toplevel(qh, WindowData(weak.clone())); // If server side decorations are available, create the toplevel decoration. let toplevel_decoration = decoration_manager.and_then(|decoration_manager| { match decorations { // Window does not want any server side decorations. WindowDecorations::ClientOnly | WindowDecorations::None => None, _ => { // Create the toplevel decoration. let toplevel_decoration = decoration_manager.get_toplevel_decoration( &xdg_toplevel, qh, WindowData(weak.clone()), ); // Tell the compositor we would like a specific mode. let mode = match decorations { WindowDecorations::RequestServer => Some(Mode::ServerSide), WindowDecorations::RequestClient => Some(Mode::ClientSide), _ => None, }; if let Some(mode) = mode { toplevel_decoration.set_mode(mode); } Some(toplevel_decoration) } } }); WindowInner { xdg_surface, xdg_toplevel, toplevel_decoration, pending_configure: Mutex::new(WindowConfigure { new_size: (None, None), suggested_bounds: None, // Initial configure will indicate whether there are server side decorations. decoration_mode: DecorationMode::Client, state: WindowState::empty(), // XXX by default we assume that everything is supported. capabilities: WindowManagerCapabilities::all(), }), } }); // Explicitly drop the queue freeze to allow the queue to resume work. drop(freeze); Window(inner) } pub fn xdg_wm_base(&self) -> &xdg_wm_base::XdgWmBase { &self.xdg_wm_base } } /// A trivial wrapper for an [`xdg_positioner::XdgPositioner`]. /// /// This wrapper calls [`destroy`][xdg_positioner::XdgPositioner::destroy] on the contained /// positioner when it is dropped. #[derive(Debug)] pub struct XdgPositioner(xdg_positioner::XdgPositioner); impl XdgPositioner { pub fn new( wm_base: &impl ProvidesBoundGlobal, ) -> Result { wm_base .bound_global() .map(|wm_base| { wm_base .send_constructor( xdg_wm_base::Request::CreatePositioner {}, Arc::new(PositionerData), ) .unwrap_or_else(|_| Proxy::inert(wm_base.backend().clone())) }) .map(XdgPositioner) } } impl std::ops::Deref for XdgPositioner { type Target = xdg_positioner::XdgPositioner; fn deref(&self) -> &Self::Target { &self.0 } } impl Drop for XdgPositioner { fn drop(&mut self) { self.0.destroy() } } struct PositionerData; impl wayland_client::backend::ObjectData for PositionerData { fn event( self: Arc, _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message, ) -> Option> { unreachable!("xdg_positioner has no events"); } fn destroyed(&self, _: wayland_client::backend::ObjectId) {} } /// A surface role for functionality common in desktop-like surfaces. #[derive(Debug)] pub struct XdgShellSurface { xdg_surface: xdg_surface::XdgSurface, surface: Surface, } impl XdgShellSurface { /// Creates an [`XdgShellSurface`]. /// /// This function is generally intended to be called in a higher level abstraction, such as /// [`XdgShell::create_window`]. /// /// The created [`XdgShellSurface`] will destroy the underlying [`XdgSurface`] or [`WlSurface`] when /// dropped. Higher level abstractions are responsible for ensuring the destruction order of protocol /// objects is correct. Since this function consumes the [`WlSurface`], it may be accessed using /// [`XdgShellSurface::wl_surface`]. /// /// # Protocol errors /// /// If the surface already has a role object, the compositor will raise a protocol error. /// /// A surface is considered to have a role object if some other type of surface was created using the /// surface. For example, creating a window, popup, layer, subsurface or some other type of surface object /// all assign a role object to a surface. /// /// [`XdgSurface`]: xdg_surface::XdgSurface /// [`WlSurface`]: wl_surface::WlSurface pub fn new( wm_base: &impl ProvidesBoundGlobal, qh: &QueueHandle, surface: impl Into, udata: U, ) -> Result where D: Dispatch + 'static, U: Send + Sync + 'static, { let surface = surface.into(); let xdg_surface = wm_base.bound_global()?.get_xdg_surface(surface.wl_surface(), qh, udata); Ok(XdgShellSurface { xdg_surface, surface }) } pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface { &self.xdg_surface } pub fn wl_surface(&self) -> &wl_surface::WlSurface { self.surface.wl_surface() } } pub trait XdgSurface: WaylandSurface + Sized { /// The underlying [`XdgSurface`](xdg_surface::XdgSurface). fn xdg_surface(&self) -> &xdg_surface::XdgSurface; fn set_window_geometry(&self, x: u32, y: u32, width: u32, height: u32) { self.xdg_surface().set_window_geometry(x as i32, y as i32, width as i32, height as i32); } } impl WaylandSurface for XdgShellSurface { fn wl_surface(&self) -> &wl_surface::WlSurface { self.wl_surface() } } impl XdgSurface for XdgShellSurface { fn xdg_surface(&self) -> &xdg_surface::XdgSurface { &self.xdg_surface } } #[macro_export] macro_rules! delegate_xdg_shell { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::shell::client::xdg_wm_base::XdgWmBase: $crate::globals::GlobalData ] => $crate::shell::xdg::XdgShell); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1: $crate::globals::GlobalData ] => $crate::shell::xdg::XdgShell); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1: $crate::shell::xdg::window::WindowData ] => $crate::shell::xdg::XdgShell); }; } impl Drop for XdgShellSurface { fn drop(&mut self) { // Surface role must be destroyed before the wl_surface self.xdg_surface.destroy(); } } // Version 4 adds the configure_bounds event, which is a break impl ProvidesBoundGlobal for XdgShell { fn bound_global(&self) -> Result { Ok(self.xdg_wm_base.clone()) } } impl Dispatch for XdgShell where D: Dispatch, { fn event( _state: &mut D, xdg_wm_base: &xdg_wm_base::XdgWmBase, event: xdg_wm_base::Event, _data: &GlobalData, _conn: &Connection, _qh: &QueueHandle, ) { match event { xdg_wm_base::Event::Ping { serial } => { xdg_wm_base.pong(serial); } _ => unreachable!(), } } } smithay-client-toolkit-0.18.0/src/shell/xdg/popup.rs000064400000000000000000000226651046102023000205330ustar 00000000000000use crate::{ compositor::{Surface, SurfaceData}, error::GlobalError, globals::ProvidesBoundGlobal, shell::xdg::XdgShellSurface, }; use std::sync::{ atomic::{AtomicI32, AtomicU32, Ordering::Relaxed}, Arc, Weak, }; use wayland_client::{ protocol::{wl_compositor::WlCompositor, wl_surface}, Connection, Dispatch, QueueHandle, }; use wayland_protocols::xdg::shell::client::{xdg_popup, xdg_positioner, xdg_surface, xdg_wm_base}; #[derive(Debug, Clone)] pub struct Popup { inner: Arc, } impl Eq for Popup {} impl PartialEq for Popup { fn eq(&self, other: &Popup) -> bool { Arc::ptr_eq(&self.inner, &other.inner) } } #[derive(Debug)] pub struct PopupData { inner: Weak, } #[derive(Debug)] struct PopupInner { surface: XdgShellSurface, xdg_popup: xdg_popup::XdgPopup, pending_position: (AtomicI32, AtomicI32), pending_dimensions: (AtomicI32, AtomicI32), pending_token: AtomicU32, configure_state: AtomicU32, } impl Popup { /// Create a new popup. /// /// This creates the popup and sends the initial commit. You must wait for /// [`PopupHandler::configure`] to commit contents to the surface. pub fn new( parent: &xdg_surface::XdgSurface, position: &xdg_positioner::XdgPositioner, qh: &QueueHandle, compositor: &impl ProvidesBoundGlobal, wm_base: &impl ProvidesBoundGlobal, ) -> Result where D: Dispatch + Dispatch + Dispatch + PopupHandler + 'static, { let surface = Surface::new(compositor, qh)?; let popup = Self::from_surface(Some(parent), position, qh, surface, wm_base)?; popup.wl_surface().commit(); Ok(popup) } /// Create a new popup from an existing surface. /// /// If you do not specify a parent surface, you must configure the parent using an alternate /// function such as [`LayerSurface::get_popup`] prior to committing the surface, or you will /// get an `invalid_popup_parent` protocol error. /// /// [`LayerSurface::get_popup`]: wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1::get_popup pub fn from_surface( parent: Option<&xdg_surface::XdgSurface>, position: &xdg_positioner::XdgPositioner, qh: &QueueHandle, surface: impl Into, wm_base: &impl ProvidesBoundGlobal, ) -> Result where D: Dispatch + Dispatch + 'static, { let surface = surface.into(); let wm_base = wm_base.bound_global()?; // Freeze the queue during the creation of the Arc to avoid a race between events on the // new objects being processed and the Weak in the PopupData becoming usable. let freeze = qh.freeze(); let inner = Arc::new_cyclic(|weak| { let xdg_surface = wm_base.get_xdg_surface( surface.wl_surface(), qh, PopupData { inner: weak.clone() }, ); let surface = XdgShellSurface { surface, xdg_surface }; let xdg_popup = surface.xdg_surface().get_popup( parent, position, qh, PopupData { inner: weak.clone() }, ); PopupInner { surface, xdg_popup, pending_position: (AtomicI32::new(0), AtomicI32::new(0)), pending_dimensions: (AtomicI32::new(-1), AtomicI32::new(-1)), pending_token: AtomicU32::new(0), configure_state: AtomicU32::new(PopupConfigure::STATE_NEW), } }); drop(freeze); Ok(Popup { inner }) } pub fn xdg_popup(&self) -> &xdg_popup::XdgPopup { &self.inner.xdg_popup } pub fn xdg_shell_surface(&self) -> &XdgShellSurface { &self.inner.surface } pub fn xdg_surface(&self) -> &xdg_surface::XdgSurface { self.inner.surface.xdg_surface() } pub fn wl_surface(&self) -> &wl_surface::WlSurface { self.inner.surface.wl_surface() } pub fn reposition(&self, position: &xdg_positioner::XdgPositioner, token: u32) { self.xdg_popup().reposition(position, token); } } impl PopupData { /// Get a new handle to the Popup /// /// This returns `None` if the popup has been destroyed. pub fn popup(&self) -> Option { let inner = self.inner.upgrade()?; Some(Popup { inner }) } } impl Drop for PopupInner { fn drop(&mut self) { self.xdg_popup.destroy(); } } #[derive(Debug, Clone)] #[non_exhaustive] pub struct PopupConfigure { /// (x,y) relative to parent surface window geometry pub position: (i32, i32), pub width: i32, pub height: i32, pub serial: u32, pub kind: ConfigureKind, } #[derive(Debug, Clone)] #[non_exhaustive] pub enum ConfigureKind { /// Initial configure for this popup Initial, /// The configure is due to an xdg_positioner with set_reactive requested Reactive, /// The configure is due to a reposition request with this token Reposition { token: u32 }, } impl PopupConfigure { const STATE_NEW: u32 = 0; const STATE_CONFIGURED: u32 = 1; const STATE_REPOSITION_ACK: u32 = 2; } pub trait PopupHandler: Sized { /// The popup has been configured. fn configure( &mut self, conn: &Connection, qh: &QueueHandle, popup: &Popup, config: PopupConfigure, ); /// The popup was dismissed by the compositor and should be destroyed. fn done(&mut self, conn: &Connection, qh: &QueueHandle, popup: &Popup); } impl Dispatch for PopupData where D: Dispatch + PopupHandler, { fn event( data: &mut D, xdg_surface: &xdg_surface::XdgSurface, event: xdg_surface::Event, pdata: &PopupData, conn: &Connection, qh: &QueueHandle, ) { let popup = match pdata.popup() { Some(popup) => popup, None => return, }; let inner = &popup.inner; match event { xdg_surface::Event::Configure { serial } => { xdg_surface.ack_configure(serial); let x = inner.pending_position.0.load(Relaxed); let y = inner.pending_position.1.load(Relaxed); let width = inner.pending_dimensions.0.load(Relaxed); let height = inner.pending_dimensions.1.load(Relaxed); let kind = match inner.configure_state.swap(PopupConfigure::STATE_CONFIGURED, Relaxed) { PopupConfigure::STATE_NEW => ConfigureKind::Initial, PopupConfigure::STATE_CONFIGURED => ConfigureKind::Reactive, PopupConfigure::STATE_REPOSITION_ACK => { ConfigureKind::Reposition { token: inner.pending_token.load(Relaxed) } } _ => unreachable!(), }; let config = PopupConfigure { position: (x, y), width, height, serial, kind }; data.configure(conn, qh, &popup, config); } _ => unreachable!(), } } } impl Dispatch for PopupData where D: Dispatch + PopupHandler, { fn event( data: &mut D, _: &xdg_popup::XdgPopup, event: xdg_popup::Event, pdata: &PopupData, conn: &Connection, qh: &QueueHandle, ) { let popup = match pdata.popup() { Some(popup) => popup, None => return, }; let inner = &popup.inner; match event { xdg_popup::Event::Configure { x, y, width, height } => { inner.pending_position.0.store(x, Relaxed); inner.pending_position.1.store(y, Relaxed); inner.pending_dimensions.0.store(width, Relaxed); inner.pending_dimensions.1.store(height, Relaxed); } xdg_popup::Event::PopupDone => { data.done(conn, qh, &popup); } xdg_popup::Event::Repositioned { token } => { inner.pending_token.store(token, Relaxed); inner.configure_state.store(PopupConfigure::STATE_REPOSITION_ACK, Relaxed); } _ => unreachable!(), } } } #[macro_export] macro_rules! delegate_xdg_popup { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::shell::client::xdg_popup::XdgPopup: $crate::shell::xdg::popup::PopupData ] => $crate::shell::xdg::popup::PopupData); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::shell::client::xdg_surface::XdgSurface: $crate::shell::xdg::popup::PopupData ] => $crate::shell::xdg::popup::PopupData); }; } smithay-client-toolkit-0.18.0/src/shell/xdg/window/inner.rs000064400000000000000000000220211046102023000217740ustar 00000000000000use std::{ convert::{TryFrom, TryInto}, num::NonZeroU32, sync::Mutex, }; use wayland_client::{Connection, Dispatch, QueueHandle}; use wayland_protocols::{ xdg::decoration::zv1::client::{ zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1::{self, Mode}, }, xdg::shell::client::{ xdg_surface, xdg_toplevel::{self, State, WmCapabilities}, }, }; use crate::{ error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, shell::xdg::{XdgShell, XdgShellSurface}, }; use super::{ DecorationMode, Window, WindowConfigure, WindowData, WindowHandler, WindowManagerCapabilities, WindowState, }; impl Drop for WindowInner { fn drop(&mut self) { // XDG decoration says we must destroy the decoration object before the toplevel if let Some(toplevel_decoration) = self.toplevel_decoration.as_ref() { toplevel_decoration.destroy(); } // XDG Shell protocol dictates we must destroy the role object before the xdg surface. self.xdg_toplevel.destroy(); // XdgShellSurface will do it's own drop // self.xdg_surface.destroy(); } } #[derive(Debug)] pub struct WindowInner { pub xdg_surface: XdgShellSurface, pub xdg_toplevel: xdg_toplevel::XdgToplevel, pub toplevel_decoration: Option, pub pending_configure: Mutex, } impl ProvidesBoundGlobal for XdgShell { fn bound_global( &self, ) -> Result { self.xdg_decoration_manager.get().cloned() } } impl Dispatch for XdgShell where D: Dispatch + WindowHandler, { fn event( data: &mut D, xdg_surface: &xdg_surface::XdgSurface, event: xdg_surface::Event, _: &WindowData, conn: &Connection, qh: &QueueHandle, ) { if let Some(window) = Window::from_xdg_surface(xdg_surface) { match event { xdg_surface::Event::Configure { serial } => { // Acknowledge the configure per protocol requirements. xdg_surface.ack_configure(serial); let configure = { window.0.pending_configure.lock().unwrap().clone() }; WindowHandler::configure(data, conn, qh, &window, configure, serial); } _ => unreachable!(), } } } } impl Dispatch for XdgShell where D: Dispatch + WindowHandler, { fn event( data: &mut D, toplevel: &xdg_toplevel::XdgToplevel, event: xdg_toplevel::Event, _: &WindowData, conn: &Connection, qh: &QueueHandle, ) { if let Some(window) = Window::from_xdg_toplevel(toplevel) { match event { xdg_toplevel::Event::Configure { width, height, states } => { // The states are encoded as a bunch of u32 of native endian, but are encoded in an array of // bytes. let new_state = states .chunks_exact(4) .flat_map(TryInto::<[u8; 4]>::try_into) .map(u32::from_ne_bytes) .flat_map(State::try_from) .fold(WindowState::empty(), |mut acc, state| { match state { State::Maximized => acc.set(WindowState::MAXIMIZED, true), State::Fullscreen => acc.set(WindowState::FULLSCREEN, true), State::Resizing => acc.set(WindowState::RESIZING, true), State::Activated => acc.set(WindowState::ACTIVATED, true), State::TiledLeft => acc.set(WindowState::TILED_LEFT, true), State::TiledRight => acc.set(WindowState::TILED_RIGHT, true), State::TiledTop => acc.set(WindowState::TILED_TOP, true), State::TiledBottom => acc.set(WindowState::TILED_BOTTOM, true), State::Suspended => acc.set(WindowState::SUSPENDED, true), _ => (), } acc }); // XXX we do explicit convertion and sanity checking because compositor // could pass negative values which we should ignore all together. let width = u32::try_from(width).ok().and_then(NonZeroU32::new); let height = u32::try_from(height).ok().and_then(NonZeroU32::new); let pending_configure = &mut window.0.pending_configure.lock().unwrap(); pending_configure.new_size = (width, height); pending_configure.state = new_state; } xdg_toplevel::Event::Close => { data.request_close(conn, qh, &window); } xdg_toplevel::Event::ConfigureBounds { width, height } => { let pending_configure = &mut window.0.pending_configure.lock().unwrap(); if width == 0 && height == 0 { pending_configure.suggested_bounds = None; } else { pending_configure.suggested_bounds = Some((width as u32, height as u32)); } } xdg_toplevel::Event::WmCapabilities { capabilities } => { let pending_configure = &mut window.0.pending_configure.lock().unwrap(); pending_configure.capabilities = capabilities .chunks_exact(4) .flat_map(TryInto::<[u8; 4]>::try_into) .map(u32::from_ne_bytes) .flat_map(WmCapabilities::try_from) .fold(WindowManagerCapabilities::empty(), |mut acc, capability| { match capability { WmCapabilities::WindowMenu => { acc.set(WindowManagerCapabilities::WINDOW_MENU, true) } WmCapabilities::Maximize => { acc.set(WindowManagerCapabilities::MAXIMIZE, true) } WmCapabilities::Fullscreen => { acc.set(WindowManagerCapabilities::FULLSCREEN, true) } WmCapabilities::Minimize => { acc.set(WindowManagerCapabilities::MINIMIZE, true) } _ => (), } acc }); } _ => unreachable!(), } } } } // XDG decoration impl Dispatch for XdgShell where D: Dispatch + WindowHandler, { fn event( _: &mut D, _: &zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _: zxdg_decoration_manager_v1::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("zxdg_decoration_manager_v1 has no events") } } impl Dispatch for XdgShell where D: Dispatch + WindowHandler, { fn event( _: &mut D, decoration: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, event: zxdg_toplevel_decoration_v1::Event, _: &WindowData, _: &Connection, _: &QueueHandle, ) { if let Some(window) = Window::from_toplevel_decoration(decoration) { match event { zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode { wayland_client::WEnum::Value(mode) => { let mode = match mode { Mode::ClientSide => DecorationMode::Client, Mode::ServerSide => DecorationMode::Server, _ => unreachable!(), }; window.0.pending_configure.lock().unwrap().decoration_mode = mode; } wayland_client::WEnum::Unknown(unknown) => { log::error!(target: "sctk", "unknown decoration mode 0x{:x}", unknown); } }, _ => unreachable!(), } } } } smithay-client-toolkit-0.18.0/src/shell/xdg/window/mod.rs000064400000000000000000000251311046102023000214450ustar 00000000000000//! XDG shell windows. use std::{ num::NonZeroU32, sync::{Arc, Weak}, }; use crate::reexports::client::{ protocol::{wl_output, wl_seat, wl_surface}, Connection, Proxy, QueueHandle, }; use crate::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use crate::reexports::protocols::{ xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::{self, Mode}, xdg::shell::client::{xdg_surface, xdg_toplevel}, }; use crate::shell::WaylandSurface; use self::inner::WindowInner; use super::XdgSurface; pub(super) mod inner; /// Handler for toplevel operations on a [`Window`]. pub trait WindowHandler: Sized { /// Request to close a window. /// /// This request does not destroy the window. You must drop all [`Window`] handles to destroy the window. /// This request may be sent either by the compositor or by some other mechanism (such as client side decorations). fn request_close(&mut self, conn: &Connection, qh: &QueueHandle, window: &Window); /// Apply a suggested surface change. /// /// When this function is called, the compositor is requesting the window's size or state to change. /// /// Internally this function is called when the underlying `xdg_surface` is configured. Any extension /// protocols that interface with xdg-shell are able to be notified that the surface's configure sequence /// is complete by using this function. /// /// # Double buffering /// /// Configure events in Wayland are considered to be double buffered and the state of the window does not /// change until committed. fn configure( &mut self, conn: &Connection, qh: &QueueHandle, window: &Window, configure: WindowConfigure, serial: u32, ); } /// Decoration mode of a window. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DecorationMode { /// The window should draw client side decorations. Client, /// The server will draw window decorations. Server, } /// A window configure. /// /// A configure describes a compositor request to resize the window or change it's state. #[non_exhaustive] #[derive(Debug, Clone)] pub struct WindowConfigure { /// The compositor suggested new size of the window in window geometry coordinates. /// /// If this value is [`None`], you may set the size of the window as you wish. pub new_size: (Option, Option), /// Compositor suggested maximum bounds for a window. /// /// This may be used to ensure a window is not created in a way where it will not fit. /// /// If xdg-shell is version 3 or lower, this will always be [`None`]. pub suggested_bounds: Option<(u32, u32)>, /// The compositor set decoration mode of the window. /// /// This will always be [`DecorationMode::Client`] if server side decorations are not enabled or /// supported. pub decoration_mode: DecorationMode, /// The current state of the window. /// /// For more see [`WindowState`] documentation on the flag values. pub state: WindowState, /// The capabilities supported by the compositor. /// /// For more see [`WindowManagerCapabilities`] documentation on the flag values. pub capabilities: WindowManagerCapabilities, } impl WindowConfigure { /// Is [`WindowState::MAXIMIZED`] state is set. #[inline] pub fn is_maximized(&self) -> bool { self.state.contains(WindowState::MAXIMIZED) } /// Is [`WindowState::FULLSCREEN`] state is set. #[inline] pub fn is_fullscreen(&self) -> bool { self.state.contains(WindowState::FULLSCREEN) } /// Is [`WindowState::RESIZING`] state is set. #[inline] pub fn is_resizing(&self) -> bool { self.state.contains(WindowState::RESIZING) } /// Is [`WindowState::TILED`] state is set. #[inline] pub fn is_tiled(&self) -> bool { self.state.contains(WindowState::TILED) } /// Is [`WindowState::ACTIVATED`] state is set. #[inline] pub fn is_activated(&self) -> bool { self.state.contains(WindowState::ACTIVATED) } /// Is [`WindowState::TILED_LEFT`] state is set. #[inline] pub fn is_tiled_left(&self) -> bool { self.state.contains(WindowState::TILED_LEFT) } /// Is [`WindowState::TILED_RIGHT`] state is set. #[inline] pub fn is_tiled_right(&self) -> bool { self.state.contains(WindowState::TILED_RIGHT) } /// Is [`WindowState::TILED_TOP`] state is set. #[inline] pub fn is_tiled_top(&self) -> bool { self.state.contains(WindowState::TILED_TOP) } /// Is [`WindowState::TILED_BOTTOM`] state is set. #[inline] pub fn is_tiled_bottom(&self) -> bool { self.state.contains(WindowState::TILED_BOTTOM) } } /// Decorations a window is created with. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WindowDecorations { /// The window should use the decoration mode the server asks for. /// /// The server may ask the client to render with or without client side decorations. If server side /// decorations are not available, client side decorations are drawn instead. ServerDefault, /// The window should request server side decorations. /// /// The server may ignore this request and ask the client to render with client side decorations. If /// server side decorations are not available, client side decorations are drawn instead. RequestServer, /// The window should request client side decorations. /// /// The server may ignore this request and render server side decorations. If server side decorations are /// not available, client side decorations are drawn. RequestClient, /// The window should always draw it's own client side decorations. ClientOnly, /// The window should use server side decorations or draw any client side decorations. None, } #[derive(Debug, Clone)] pub struct Window(pub(super) Arc); impl Window { pub fn from_xdg_toplevel(toplevel: &xdg_toplevel::XdgToplevel) -> Option { toplevel.data::().and_then(|data| data.0.upgrade()).map(Window) } pub fn from_xdg_surface(surface: &xdg_surface::XdgSurface) -> Option { surface.data::().and_then(|data| data.0.upgrade()).map(Window) } pub fn from_toplevel_decoration( decoration: &zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1, ) -> Option { decoration.data::().and_then(|data| data.0.upgrade()).map(Window) } pub fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, position: (i32, i32)) { self.xdg_toplevel().show_window_menu(seat, serial, position.0, position.1); } pub fn set_title(&self, title: impl Into) { self.xdg_toplevel().set_title(title.into()); } pub fn set_app_id(&self, app_id: impl Into) { self.xdg_toplevel().set_app_id(app_id.into()); } pub fn set_parent(&self, parent: Option<&Window>) { self.xdg_toplevel().set_parent(parent.map(Window::xdg_toplevel)); } pub fn set_maximized(&self) { self.xdg_toplevel().set_maximized() } pub fn unset_maximized(&self) { self.xdg_toplevel().unset_maximized() } pub fn set_minimized(&self) { self.xdg_toplevel().set_minimized() } pub fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) { self.xdg_toplevel().set_fullscreen(output) } pub fn unset_fullscreen(&self) { self.xdg_toplevel().unset_fullscreen() } /// Requests the window should use the specified decoration mode. /// /// A mode of [`None`] indicates that the window does not care what type of decorations are used. /// /// The compositor will respond with a [`configure`](WindowHandler::configure). The configure will /// indicate whether the window's decoration mode has changed. /// /// # Configure loops /// /// You should avoid sending multiple decoration mode requests to ensure you do not enter a configure loop. pub fn request_decoration_mode(&self, mode: Option) { if let Some(toplevel_decoration) = &self.0.toplevel_decoration { match mode { Some(DecorationMode::Client) => toplevel_decoration.set_mode(Mode::ClientSide), Some(DecorationMode::Server) => toplevel_decoration.set_mode(Mode::ServerSide), None => toplevel_decoration.unset_mode(), } } } pub fn move_(&self, seat: &wl_seat::WlSeat, serial: u32) { self.xdg_toplevel()._move(seat, serial) } pub fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge) { self.xdg_toplevel().resize(seat, serial, edges) } // Double buffered window state pub fn set_min_size(&self, min_size: Option<(u32, u32)>) { let min_size = min_size.unwrap_or_default(); self.xdg_toplevel().set_min_size(min_size.0 as i32, min_size.1 as i32); } /// # Protocol errors /// /// The maximum size of the window may not be smaller than the minimum size. pub fn set_max_size(&self, max_size: Option<(u32, u32)>) { let max_size = max_size.unwrap_or_default(); self.xdg_toplevel().set_max_size(max_size.0 as i32, max_size.1 as i32); } // Other /// Returns the underlying xdg toplevel wrapped by this window. pub fn xdg_toplevel(&self) -> &xdg_toplevel::XdgToplevel { &self.0.xdg_toplevel } } impl WaylandSurface for Window { fn wl_surface(&self) -> &wl_surface::WlSurface { self.0.xdg_surface.wl_surface() } } impl XdgSurface for Window { fn xdg_surface(&self) -> &xdg_surface::XdgSurface { self.0.xdg_surface.xdg_surface() } } impl PartialEq for Window { fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } #[derive(Debug, Clone)] pub struct WindowData(pub(crate) Weak); #[macro_export] macro_rules! delegate_xdg_window { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::shell::client::xdg_surface::XdgSurface: $crate::shell::xdg::window::WindowData ] => $crate::shell::xdg::XdgShell); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::protocols::xdg::shell::client::xdg_toplevel::XdgToplevel: $crate::shell::xdg::window::WindowData ] => $crate::shell::xdg::XdgShell); }; } smithay-client-toolkit-0.18.0/src/shm/mod.rs000064400000000000000000000072651046102023000170440ustar 00000000000000pub mod multi; pub mod raw; pub mod slot; use std::io; use wayland_client::{ globals::{BindError, GlobalList}, protocol::wl_shm, Connection, Dispatch, QueueHandle, WEnum, }; use crate::{ error::GlobalError, globals::{GlobalData, ProvidesBoundGlobal}, }; pub trait ShmHandler { fn shm_state(&mut self) -> &mut Shm; } #[derive(Debug)] pub struct Shm { wl_shm: wl_shm::WlShm, formats: Vec, } impl From for Shm { fn from(wl_shm: wl_shm::WlShm) -> Self { Self { wl_shm, formats: Vec::new() } } } impl Shm { pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where State: Dispatch + ShmHandler + 'static, { let wl_shm = globals.bind(qh, 1..=1, GlobalData)?; // Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats. Ok(Shm { wl_shm, formats: Vec::with_capacity(2) }) } pub fn wl_shm(&self) -> &wl_shm::WlShm { &self.wl_shm } /// Returns the formats supported in memory pools. pub fn formats(&self) -> &[wl_shm::Format] { &self.formats[..] } } impl ProvidesBoundGlobal for Shm { fn bound_global(&self) -> Result { Ok(self.wl_shm.clone()) } } /// An error that may occur when creating a pool. #[derive(Debug, thiserror::Error)] pub enum CreatePoolError { /// The wl_shm global is not bound. #[error(transparent)] Global(#[from] GlobalError), /// Error while allocating the shared memory. #[error(transparent)] Create(#[from] io::Error), } /// Delegates the handling of [`wl_shm`] to some [`Shm`]. /// /// This macro requires two things, the type that will delegate to [`Shm`] and a closure specifying how /// to obtain the state object. /// /// ``` /// use smithay_client_toolkit::shm::{ShmHandler, Shm}; /// use smithay_client_toolkit::delegate_shm; /// /// struct ExampleApp { /// /// The state object that will be our delegate. /// shm: Shm, /// } /// /// // Use the macro to delegate wl_shm to Shm. /// delegate_shm!(ExampleApp); /// /// // You must implement the ShmHandler trait to provide a way to access the Shm from your data type. /// impl ShmHandler for ExampleApp { /// fn shm_state(&mut self) -> &mut Shm { /// &mut self.shm /// } /// } #[macro_export] macro_rules! delegate_shm { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_shm::WlShm: $crate::globals::GlobalData ] => $crate::shm::Shm ); }; } impl Dispatch for Shm where D: Dispatch + ShmHandler, { fn event( state: &mut D, _proxy: &wl_shm::WlShm, event: wl_shm::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { match event { wl_shm::Event::Format { format } => { match format { WEnum::Value(format) => { state.shm_state().formats.push(format); log::debug!(target: "sctk", "supported wl_shm format {:?}", format); } // Ignore formats we don't know about. WEnum::Unknown(raw) => { log::debug!(target: "sctk", "Unknown supported wl_shm format {:x}", raw); } }; } _ => unreachable!(), } } } smithay-client-toolkit-0.18.0/src/shm/multi.rs000064400000000000000000000333471046102023000174170ustar 00000000000000//! A pool implementation which automatically manage buffers. //! //! This pool is built on the [`RawPool`]. //! //! The [`MultiPool`] takes a key which is used to identify buffers and tries to return the buffer associated to the key //! if possible. If no buffer in the pool is associated to the key, it will create a new one. //! //! # Example //! //! ```rust //! use smithay_client_toolkit::reexports::client::{ //! QueueHandle, //! protocol::wl_surface::WlSurface, //! protocol::wl_shm::Format, //! }; //! use smithay_client_toolkit::shm::multi::MultiPool; //! //! struct WlFoo { //! // The surface we'll draw on and the index of buffer associated to it //! surface: (WlSurface, usize), //! pool: MultiPool<(WlSurface, usize)> //! } //! //! impl WlFoo { //! fn draw(&mut self, qh: &QueueHandle) { //! let surface = &self.surface.0; //! // We'll increment "i" until the pool can create a new buffer //! // if there's no buffer associated with our surface and "i" or if //! // a buffer with the obuffer associated with our surface and "i" is free for use. //! // //! // There's no limit to the amount of buffers we can allocate to our surface but since //! // shm buffers are released fairly fast, it's unlikely we'll need more than double buffering. //! for i in 0..2 { //! self.surface.1 = i; //! if let Ok((offset, buffer, slice)) = self.pool.create_buffer( //! 100, //! 100 * 4, //! 100, //! &self.surface, //! Format::Argb8888, //! ) { //! /* //! insert drawing code here //! */ //! surface.attach(Some(buffer), 0, 0); //! surface.commit(); //! // We exit the function after the draw. //! return; //! } //! } //! /* //! If there's no buffer available we can for example request a frame callback //! and trigger a redraw when it fires. //! (not shown in this example) //! */ //! } //! } //! //! fn draw(slice: &mut [u8]) { //! todo!() //! } //! //! ``` //! use std::borrow::Borrow; use std::io; use std::os::unix::io::OwnedFd; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; use wayland_client::{ protocol::{wl_buffer, wl_shm}, Proxy, }; use crate::globals::ProvidesBoundGlobal; use super::raw::RawPool; use super::CreatePoolError; #[derive(Debug, thiserror::Error)] pub enum PoolError { #[error("buffer is currently used")] InUse, #[error("buffer is overlapping another")] Overlap, #[error("buffer could not be found")] NotFound, } /// This pool manages buffers associated with keys. /// Only one buffer can be attributed to a given key. #[derive(Debug)] pub struct MultiPool { buffer_list: Vec>, pub(crate) inner: RawPool, } #[derive(Debug, thiserror::Error)] pub struct BufferSlot { free: Arc, size: usize, used: usize, offset: usize, buffer: Option, key: K, } impl Drop for BufferSlot { fn drop(&mut self) { self.destroy().ok(); } } impl BufferSlot { pub fn destroy(&self) -> Result<(), PoolError> { self.buffer.as_ref().ok_or(PoolError::NotFound).and_then(|buffer| { self.free.load(Ordering::Relaxed).then(|| buffer.destroy()).ok_or(PoolError::InUse) }) } } impl MultiPool { pub fn new(shm: &impl ProvidesBoundGlobal) -> Result { Ok(Self { inner: RawPool::new(4096, shm)?, buffer_list: Vec::new() }) } /// Resizes the memory pool, notifying the server the pool has changed in size. /// /// The wl_shm protocol only allows the pool to be made bigger. If the new size is smaller than the /// current size of the pool, this function will do nothing. pub fn resize(&mut self, size: usize) -> io::Result<()> { self.inner.resize(size) } /// Removes the buffer with the given key from the pool and rearranges the others. pub fn remove(&mut self, key: &Q) -> Option> where Q: PartialEq, K: std::borrow::Borrow, { self.buffer_list .iter() .enumerate() .find(|(_, slot)| slot.key.borrow().eq(key)) .map(|(i, _)| i) .map(|i| self.buffer_list.remove(i)) } /// Insert a buffer into the pool. /// /// The parameters are: /// /// - `width`: the width of this buffer (in pixels) /// - `height`: the height of this buffer (in pixels) /// - `stride`: distance (in bytes) between the beginning of a row and the next one /// - `key`: a borrowed form of the stored key type /// - `format`: the encoding format of the pixels. pub fn insert( &mut self, width: i32, stride: i32, height: i32, key: &Q, format: wl_shm::Format, ) -> Result where K: Borrow, Q: PartialEq + ToOwned, { let mut offset = 0; let mut found_key = false; let size = (stride * height) as usize; let mut index = Err(PoolError::NotFound); for (i, buf_slot) in self.buffer_list.iter_mut().enumerate() { if buf_slot.key.borrow().eq(key) { found_key = true; if buf_slot.free.load(Ordering::Relaxed) { // Destroys the buffer if it's resized if size != buf_slot.used { if let Some(buffer) = buf_slot.buffer.take() { buffer.destroy(); } } // Increases the size of the Buffer if it's too small and add 5% padding. // It is possible this buffer overlaps the following but the else if // statement prevents this buffer from being returned if that's the case. buf_slot.size = buf_slot.size.max(size + size / 20); index = Ok(i); } else { index = Err(PoolError::InUse); } // If a buffer is resized, it is likely that the followings might overlap } else if offset > buf_slot.offset { // When the buffer is free, it's safe to shift it because we know the compositor won't try to read it. if buf_slot.free.load(Ordering::Relaxed) { if offset != buf_slot.offset { if let Some(buffer) = buf_slot.buffer.take() { buffer.destroy(); } } buf_slot.offset = offset; } else { // If one of the overlapping buffers is busy, then no buffer can be returned because it could result in a data race. index = Err(PoolError::InUse); } } else if found_key { break; } let size = (buf_slot.size + 63) & !63; offset += size; } if !found_key { if let Err(err) = index { return self .dyn_resize(offset, width, stride, height, key.to_owned(), format) .map(|_| self.buffer_list.len() - 1) .ok_or(err); } } index } /// Retreives the buffer associated with the given key. /// /// The parameters are: /// /// - `width`: the width of this buffer (in pixels) /// - `height`: the height of this buffer (in pixels) /// - `stride`: distance (in bytes) between the beginning of a row and the next one /// - `key`: a borrowed form of the stored key type /// - `format`: the encoding format of the pixels. pub fn get( &mut self, width: i32, stride: i32, height: i32, key: &Q, format: wl_shm::Format, ) -> Option<(usize, &wl_buffer::WlBuffer, &mut [u8])> where Q: PartialEq, K: std::borrow::Borrow, { let len = self.inner.len(); let size = (stride * height) as usize; let buf_slot = self.buffer_list.iter_mut().find(|buf_slot| buf_slot.key.borrow().eq(key))?; if buf_slot.size >= size { return None; } buf_slot.used = size; let offset = buf_slot.offset; if buf_slot.buffer.is_none() { if offset + size > len { self.inner.resize(offset + size + size / 20).ok()?; } let free = Arc::new(AtomicBool::new(true)); let data = BufferObjectData { free: free.clone() }; let buffer = self.inner.create_buffer_raw( offset as i32, width, height, stride, format, Arc::new(data), ); buf_slot.free = free; buf_slot.buffer = Some(buffer); } let buf = buf_slot.buffer.as_ref()?; buf_slot.free.store(false, Ordering::Relaxed); Some((offset, buf, &mut self.inner.mmap()[offset..][..size])) } /// Returns the buffer associated with the given key and its offset (usize) in the mempool. /// /// The parameters are: /// /// - `width`: the width of this buffer (in pixels) /// - `height`: the height of this buffer (in pixels) /// - `stride`: distance (in bytes) between the beginning of a row and the next one /// - `key`: a borrowed form of the stored key type /// - `format`: the encoding format of the pixels. /// /// The offset can be used to determine whether or not a buffer was moved in the mempool /// and by consequence if it should be damaged partially or fully. pub fn create_buffer( &mut self, width: i32, stride: i32, height: i32, key: &Q, format: wl_shm::Format, ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> where K: Borrow, Q: PartialEq + ToOwned, { let index = self.insert(width, stride, height, key, format)?; self.get_at(index, width, stride, height, format) } /// Retreives the buffer at the given index. fn get_at( &mut self, index: usize, width: i32, stride: i32, height: i32, format: wl_shm::Format, ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> { let len = self.inner.len(); let size = (stride * height) as usize; let buf_slot = self.buffer_list.get_mut(index).ok_or(PoolError::NotFound)?; if buf_slot.size > size { return Err(PoolError::Overlap); } buf_slot.used = size; let offset = buf_slot.offset; if buf_slot.buffer.is_none() { if offset + size > len { self.inner.resize(offset + size + size / 20).map_err(|_| PoolError::Overlap)?; } let free = Arc::new(AtomicBool::new(true)); let data = BufferObjectData { free: free.clone() }; let buffer = self.inner.create_buffer_raw( offset as i32, width, height, stride, format, Arc::new(data), ); buf_slot.free = free; buf_slot.buffer = Some(buffer); } buf_slot.free.store(false, Ordering::Relaxed); let buf = buf_slot.buffer.as_ref().unwrap(); Ok((offset, buf, &mut self.inner.mmap()[offset..][..size])) } /// Calcule the offet and size of a buffer based on its stride. fn offset(&self, mut offset: i32, stride: i32, height: i32) -> (usize, usize) { // bytes per pixel let size = stride * height; // 5% padding. offset += offset / 20; offset = (offset + 63) & !63; (offset as usize, size as usize) } #[allow(clippy::too_many_arguments)] /// Resizes the pool and appends a new buffer. fn dyn_resize( &mut self, offset: usize, width: i32, stride: i32, height: i32, key: K, format: wl_shm::Format, ) -> Option<()> { let (offset, size) = self.offset(offset as i32, stride, height); if self.inner.len() < offset + size { self.resize(offset + size + size / 20).ok()?; } let free = Arc::new(AtomicBool::new(true)); let data = BufferObjectData { free: free.clone() }; let buffer = self.inner.create_buffer_raw( offset as i32, width, height, stride, format, Arc::new(data), ); self.buffer_list.push(BufferSlot { offset, used: 0, free, buffer: Some(buffer), size, key, }); Some(()) } } struct BufferObjectData { free: Arc, } impl wayland_client::backend::ObjectData for BufferObjectData { fn event( self: Arc, _backend: &wayland_backend::client::Backend, msg: wayland_backend::protocol::Message, ) -> Option> { debug_assert!(wayland_client::backend::protocol::same_interface( msg.sender_id.interface(), wl_buffer::WlBuffer::interface() )); debug_assert!(msg.opcode == 0); // wl_buffer only has a single event: wl_buffer.release self.free.store(true, Ordering::Relaxed); None } fn destroyed(&self, _: wayland_backend::client::ObjectId) {} } smithay-client-toolkit-0.18.0/src/shm/raw.rs000064400000000000000000000200311046102023000170400ustar 00000000000000//! A raw shared memory pool handler. //! //! This is intended as a safe building block for higher level shared memory pool abstractions and is not //! encouraged for most library users. use rustix::{ io::Errno, shm::{Mode, ShmOFlags}, }; use std::{ fs::File, io, os::unix::prelude::{AsFd, OwnedFd}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; use memmap2::MmapMut; use wayland_client::{ backend::ObjectData, protocol::{wl_buffer, wl_shm, wl_shm_pool}, Dispatch, Proxy, QueueHandle, WEnum, }; use crate::globals::ProvidesBoundGlobal; use super::CreatePoolError; /// A raw handler for file backed shared memory pools. /// /// This type of pool will create the SHM memory pool and provide a way to resize the pool. /// /// This pool does not release buffers. If you need this, use one of the higher level pools. #[derive(Debug)] pub struct RawPool { pool: wl_shm_pool::WlShmPool, len: usize, mem_file: File, mmap: MmapMut, } impl RawPool { pub fn new( len: usize, shm: &impl ProvidesBoundGlobal, ) -> Result { let shm = shm.bound_global()?; let shm_fd = RawPool::create_shm_fd()?; let mem_file = File::from(shm_fd); mem_file.set_len(len as u64)?; let pool = shm .send_constructor( wl_shm::Request::CreatePool { fd: mem_file.as_fd(), size: len as i32 }, Arc::new(ShmPoolData), ) .unwrap_or_else(|_| Proxy::inert(shm.backend().clone())); let mmap = unsafe { MmapMut::map_mut(&mem_file)? }; Ok(RawPool { pool, len, mem_file, mmap }) } /// Resizes the memory pool, notifying the server the pool has changed in size. /// /// The wl_shm protocol only allows the pool to be made bigger. If the new size is smaller than the /// current size of the pool, this function will do nothing. pub fn resize(&mut self, size: usize) -> io::Result<()> { if size > self.len { self.len = size; self.mem_file.set_len(size as u64)?; self.pool.resize(size as i32); self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?; } Ok(()) } /// Returns a reference to the underlying shared memory file using the memmap2 crate. pub fn mmap(&mut self) -> &mut MmapMut { &mut self.mmap } /// Returns the size of the mempool #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.len } /// Create a new buffer to this pool. /// /// ## Parameters /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts. /// - `width` and `height`: the width and height of the buffer in pixels. /// - `stride`: distance (in bytes) between the beginning of a row and the next one. /// - `format`: the encoding format of the pixels. /// /// The encoding format of the pixels must be supported by the compositor or else a protocol error is /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats). /// /// Note this function only creates the wl_buffer object, you will need to write to the pixels using the /// [`io::Write`] implementation or [`RawPool::mmap`]. #[allow(clippy::too_many_arguments)] pub fn create_buffer( &mut self, offset: i32, width: i32, height: i32, stride: i32, format: wl_shm::Format, udata: U, qh: &QueueHandle, ) -> wl_buffer::WlBuffer where D: Dispatch + 'static, U: Send + Sync + 'static, { self.pool.create_buffer(offset, width, height, stride, format, qh, udata) } /// Create a new buffer to this pool. /// /// This is identical to [Self::create_buffer], but allows using a custom [ObjectData] /// implementation instead of relying on the [Dispatch] interface. #[allow(clippy::too_many_arguments)] pub fn create_buffer_raw( &mut self, offset: i32, width: i32, height: i32, stride: i32, format: wl_shm::Format, data: Arc, ) -> wl_buffer::WlBuffer { self.pool .send_constructor( wl_shm_pool::Request::CreateBuffer { offset, width, height, stride, format: WEnum::Value(format), }, data, ) .unwrap_or_else(|_| Proxy::inert(self.pool.backend().clone())) } /// Returns the pool object used to communicate with the server. pub fn pool(&self) -> &wl_shm_pool::WlShmPool { &self.pool } } impl io::Write for RawPool { fn write(&mut self, buf: &[u8]) -> io::Result { io::Write::write(&mut self.mem_file, buf) } fn flush(&mut self) -> io::Result<()> { io::Write::flush(&mut self.mem_file) } } impl io::Seek for RawPool { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { io::Seek::seek(&mut self.mem_file, pos) } } impl RawPool { fn create_shm_fd() -> io::Result { #[cfg(target_os = "linux")] { match RawPool::create_memfd() { Ok(fd) => return Ok(fd), // Not supported, use fallback. Err(Errno::NOSYS) => (), Err(err) => return Err(Into::::into(err)), }; } let time = SystemTime::now(); let mut mem_file_handle = format!( "/smithay-client-toolkit-{}", time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() ); loop { let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR; let mode = Mode::RUSR | Mode::WUSR; match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) { Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) { Ok(_) => return Ok(fd), Err(errno) => { return Err(errno.into()); } }, Err(Errno::EXIST) => { // Change the handle if we happen to be duplicate. let time = SystemTime::now(); mem_file_handle = format!( "/smithay-client-toolkit-{}", time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() ); continue; } Err(Errno::INTR) => continue, Err(err) => return Err(err.into()), } } } #[cfg(target_os = "linux")] fn create_memfd() -> rustix::io::Result { use std::ffi::CStr; use rustix::fs::{MemfdFlags, SealFlags}; loop { let name = CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap(); let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC; match rustix::fs::memfd_create(name, flags) { Ok(fd) => { // We only need to seal for the purposes of optimization, ignore the errors. let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL); return Ok(fd); } Err(Errno::INTR) => continue, Err(err) => return Err(err), } } } } impl Drop for RawPool { fn drop(&mut self) { self.pool.destroy(); } } #[derive(Debug)] struct ShmPoolData; impl ObjectData for ShmPoolData { fn event( self: Arc, _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message, ) -> Option> { unreachable!("wl_shm_pool has no events") } fn destroyed(&self, _: wayland_client::backend::ObjectId) {} } smithay-client-toolkit-0.18.0/src/shm/slot.rs000064400000000000000000000454451046102023000172500ustar 00000000000000//! A pool implementation based on buffer slots use std::io; use std::{ os::unix::io::{AsRawFd, OwnedFd}, sync::{ atomic::{AtomicU8, AtomicUsize, Ordering}, Arc, Mutex, Weak, }, }; use wayland_client::{ protocol::{wl_buffer, wl_shm, wl_surface}, Proxy, }; use crate::{globals::ProvidesBoundGlobal, shm::raw::RawPool, shm::CreatePoolError}; #[derive(Debug, thiserror::Error)] pub enum CreateBufferError { /// Slot creation error. #[error(transparent)] Io(#[from] io::Error), /// Pool mismatch. #[error("Incorrect pool for slot")] PoolMismatch, /// Slot size mismatch #[error("Requested buffer size is too large for slot")] SlotTooSmall, } #[derive(Debug, thiserror::Error)] pub enum ActivateSlotError { /// Buffer was already active #[error("Buffer was already active")] AlreadyActive, } #[derive(Debug)] pub struct SlotPool { pub(crate) inner: RawPool, free_list: Arc>>, } #[derive(Debug)] struct FreelistEntry { offset: usize, len: usize, } /// A chunk of memory allocated from a [SlotPool] /// /// Retaining this object is only required if you wish to resize or change the buffer's format /// without changing the contents of the backing memory. #[derive(Debug)] pub struct Slot { inner: Arc, } #[derive(Debug)] struct SlotInner { free_list: Weak>>, offset: usize, len: usize, active_buffers: AtomicUsize, /// Count of all "real" references to this slot. This includes all Slot objects and any /// BufferData object that is not in the DEAD state. When this reaches zero, the memory for /// this slot will return to the free_list. It is not possible for it to reach zero and have a /// Slot or Buffer referring to it. all_refs: AtomicUsize, } /// A wrapper around a [`wl_buffer::WlBuffer`] which has been allocated via a [SlotPool]. /// /// When this object is dropped, the buffer will be destroyed immediately if it is not active, or /// upon the server's release if it is. #[derive(Debug)] pub struct Buffer { buffer: wl_buffer::WlBuffer, height: i32, stride: i32, slot: Slot, } /// ObjectData for the WlBuffer #[derive(Debug)] struct BufferData { inner: Arc, state: AtomicU8, } // These constants define the value of BufferData::state, since AtomicEnum does not exist. impl BufferData { /// Buffer is counted in active_buffers list; will return to INACTIVE on Release. const ACTIVE: u8 = 0; /// Buffer is not counted in active_buffers list, but also has not been destroyed. const INACTIVE: u8 = 1; /// Buffer is counted in active_buffers list; will move to DEAD on Release const DESTROY_ON_RELEASE: u8 = 2; /// Buffer has been destroyed const DEAD: u8 = 3; /// Value that is ORed on buffer release to transition to the next state const RELEASE_SET: u8 = 1; /// Value that is ORed on buffer destroy to transition to the next state const DESTROY_SET: u8 = 2; /// Call after successfully transitioning the state to DEAD fn record_death(&self) { drop(Slot { inner: self.inner.clone() }) } } impl SlotPool { pub fn new( len: usize, shm: &impl ProvidesBoundGlobal, ) -> Result { let inner = RawPool::new(len, shm)?; let free_list = Arc::new(Mutex::new(vec![FreelistEntry { offset: 0, len: inner.len() }])); Ok(SlotPool { inner, free_list }) } /// Create a new buffer in a new slot. /// /// This returns the buffer and the canvas. The parameters are: /// /// - `width`: the width of this buffer (in pixels) /// - `height`: the height of this buffer (in pixels) /// - `stride`: distance (in bytes) between the beginning of a row and the next one /// - `format`: the encoding format of the pixels. Using a format that was not /// advertised to the `wl_shm` global by the server is a protocol error and will /// terminate your connection. /// /// The [Slot] for this buffer will have exactly the size required for the data. It can be /// accessed via [Buffer::slot] to create additional buffers that point to the same data. This /// is required if you wish to change formats, buffer dimensions, or attach a canvas to /// multiple surfaces. /// /// For more control over sizing, use [Self::new_slot] and [Self::create_buffer_in]. pub fn create_buffer( &mut self, width: i32, height: i32, stride: i32, format: wl_shm::Format, ) -> Result<(Buffer, &mut [u8]), CreateBufferError> { let len = (height as usize) * (stride as usize); let slot = self.new_slot(len)?; let buffer = self.create_buffer_in(&slot, width, height, stride, format)?; let canvas = self.raw_data_mut(&slot); Ok((buffer, canvas)) } /// Get the bytes corresponding to a given slot or buffer if drawing to the slot is permitted. /// /// Returns `None` if there are active buffers in the slot or if the slot does not correspond /// to this pool. pub fn canvas(&mut self, key: &impl CanvasKey) -> Option<&mut [u8]> { key.canvas(self) } /// Returns the size, in bytes, of this pool. #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.inner.len() } /// Resizes the memory pool, notifying the server the pool has changed in size. /// /// This is an optimization; the pool automatically resizes when you allocate new slots. pub fn resize(&mut self, size: usize) -> io::Result<()> { let old_len = self.inner.len(); self.inner.resize(size)?; let new_len = self.inner.len(); if old_len == new_len { return Ok(()); } // add the new memory to the freelist let mut free = self.free_list.lock().unwrap(); if let Some(FreelistEntry { offset, len }) = free.last_mut() { if *offset + *len == old_len { *len += new_len - old_len; return Ok(()); } } free.push(FreelistEntry { offset: old_len, len: new_len - old_len }); Ok(()) } fn alloc(&mut self, size: usize) -> io::Result { let mut free = self.free_list.lock().unwrap(); for FreelistEntry { offset, len } in free.iter_mut() { if *len >= size { let rv = *offset; *len -= size; *offset += size; return Ok(rv); } } let mut rv = self.inner.len(); let mut pop_tail = false; if let Some(FreelistEntry { offset, len }) = free.last() { if offset + len == self.inner.len() { rv -= len; pop_tail = true; } } // resize like Vec::reserve, always at least doubling let target = std::cmp::max(rv + size, self.inner.len() * 2); self.inner.resize(target)?; // adjust the end of the freelist here if pop_tail { free.pop(); } if target > rv + size { free.push(FreelistEntry { offset: rv + size, len: target - rv - size }); } Ok(rv) } fn free(free_list: &Mutex>, mut offset: usize, mut len: usize) { let mut free = free_list.lock().unwrap(); let mut nf = Vec::with_capacity(free.len() + 1); for &FreelistEntry { offset: ioff, len: ilen } in free.iter() { if ioff + ilen == offset { offset = ioff; len += ilen; continue; } if ioff == offset + len { len += ilen; continue; } if ioff > offset + len && len != 0 { nf.push(FreelistEntry { offset, len }); len = 0; } if ilen != 0 { nf.push(FreelistEntry { offset: ioff, len: ilen }); } } if len != 0 { nf.push(FreelistEntry { offset, len }); } *free = nf; } /// Create a new slot with the given size in bytes. pub fn new_slot(&mut self, mut len: usize) -> io::Result { len = (len + 63) & !63; let offset = self.alloc(len)?; Ok(Slot { inner: Arc::new(SlotInner { free_list: Arc::downgrade(&self.free_list), offset, len, active_buffers: AtomicUsize::new(0), all_refs: AtomicUsize::new(1), }), }) } /// Get the bytes corresponding to a given slot. /// /// Note: prefer using [Self::canvas], which will prevent drawing to a buffer that has not been /// released by the server. /// /// Returns an empty buffer if the slot does not belong to this pool. pub fn raw_data_mut(&mut self, slot: &Slot) -> &mut [u8] { if slot.inner.free_list.as_ptr() == Arc::as_ptr(&self.free_list) { &mut self.inner.mmap()[slot.inner.offset..][..slot.inner.len] } else { &mut [] } } /// Create a new buffer corresponding to a slot. /// /// The parameters are: /// /// - `width`: the width of this buffer (in pixels) /// - `height`: the height of this buffer (in pixels) /// - `stride`: distance (in bytes) between the beginning of a row and the next one /// - `format`: the encoding format of the pixels. Using a format that was not /// advertised to the `wl_shm` global by the server is a protocol error and will /// terminate your connection pub fn create_buffer_in( &mut self, slot: &Slot, width: i32, height: i32, stride: i32, format: wl_shm::Format, ) -> Result { let offset = slot.inner.offset as i32; let len = (height as usize) * (stride as usize); if len > slot.inner.len { return Err(CreateBufferError::SlotTooSmall); } if slot.inner.free_list.as_ptr() != Arc::as_ptr(&self.free_list) { return Err(CreateBufferError::PoolMismatch); } let slot = slot.clone(); // take a ref for the BufferData, which will be destroyed by BufferData::record_death slot.inner.all_refs.fetch_add(1, Ordering::Relaxed); let data = Arc::new(BufferData { inner: slot.inner.clone(), state: AtomicU8::new(BufferData::INACTIVE), }); let buffer = self.inner.create_buffer_raw(offset, width, height, stride, format, data); Ok(Buffer { buffer, height, stride, slot }) } } impl Clone for Slot { fn clone(&self) -> Self { let inner = self.inner.clone(); inner.all_refs.fetch_add(1, Ordering::Relaxed); Slot { inner } } } impl Drop for Slot { fn drop(&mut self) { if self.inner.all_refs.fetch_sub(1, Ordering::Relaxed) == 1 { if let Some(free_list) = self.inner.free_list.upgrade() { SlotPool::free(&free_list, self.inner.offset, self.inner.len); } } } } impl Drop for SlotInner { fn drop(&mut self) { debug_assert_eq!(*self.all_refs.get_mut(), 0); } } /// A helper trait for [SlotPool::canvas]. pub trait CanvasKey { fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]>; } impl Slot { /// Return true if there are buffers referencing this slot whose contents are being accessed /// by the server. pub fn has_active_buffers(&self) -> bool { self.inner.active_buffers.load(Ordering::Relaxed) != 0 } /// Returns the size, in bytes, of this slot. #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.inner.len } /// Get the bytes corresponding to a given slot if drawing to the slot is permitted. /// /// Returns `None` if there are active buffers in the slot or if the slot does not correspond /// to this pool. pub fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { if self.has_active_buffers() { return None; } if self.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) { Some(&mut pool.inner.mmap()[self.inner.offset..][..self.inner.len]) } else { None } } } impl CanvasKey for Slot { fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { self.canvas(pool) } } impl Buffer { /// Attach a buffer to a surface. /// /// This marks the slot as active until the server releases the buffer, which will happen /// automatically assuming the surface is committed without attaching a different buffer. /// /// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that /// could be attached to a surface in a multi-threaded client, make this call while you have /// exclusive access to the corresponding [`SlotPool`]. pub fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> { self.activate()?; surface.attach(Some(&self.buffer), 0, 0); Ok(()) } /// Get the inner buffer. pub fn wl_buffer(&self) -> &wl_buffer::WlBuffer { &self.buffer } pub fn height(&self) -> i32 { self.height } pub fn stride(&self) -> i32 { self.stride } fn data(&self) -> Option<&BufferData> { self.buffer.object_data()?.downcast_ref() } /// Get the bytes corresponding to this buffer if drawing is permitted. /// /// This may be smaller than the canvas associated with the slot. pub fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { let len = (self.height as usize) * (self.stride as usize); if self.slot.inner.active_buffers.load(Ordering::Relaxed) != 0 { return None; } if self.slot.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) { Some(&mut pool.inner.mmap()[self.slot.inner.offset..][..len]) } else { None } } /// Get the slot corresponding to this buffer. pub fn slot(&self) -> Slot { self.slot.clone() } /// Manually mark a buffer as active. /// /// An active buffer prevents drawing on its slot until a Release event is received or until /// manually deactivated. pub fn activate(&self) -> Result<(), ActivateSlotError> { let data = self.data().expect("UserData type mismatch"); // This bitwise AND will transition INACTIVE -> ACTIVE, or do nothing if the buffer was // already ACTIVE. No other ordering is required, as the server will not send a Release // until we send our attach after returning Ok. match data.state.fetch_and(!BufferData::RELEASE_SET, Ordering::Relaxed) { BufferData::INACTIVE => { data.inner.active_buffers.fetch_add(1, Ordering::Relaxed); Ok(()) } BufferData::ACTIVE => Err(ActivateSlotError::AlreadyActive), _ => unreachable!("Invalid state in BufferData"), } } /// Manually mark a buffer as inactive. /// /// This should be used when the buffer was manually marked as active or when a buffer was /// attached to a surface but not committed. Calling this function on a buffer that was /// committed to a surface risks making the surface contents undefined. pub fn deactivate(&self) -> Result<(), ActivateSlotError> { let data = self.data().expect("UserData type mismatch"); // Same operation as the Release event, but we know the Buffer was not dropped. match data.state.fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) { BufferData::ACTIVE => { data.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); Ok(()) } BufferData::INACTIVE => Err(ActivateSlotError::AlreadyActive), _ => unreachable!("Invalid state in BufferData"), } } } impl CanvasKey for Buffer { fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> { self.canvas(pool) } } impl Drop for Buffer { fn drop(&mut self) { if let Some(data) = self.data() { match data.state.fetch_or(BufferData::DESTROY_SET, Ordering::Relaxed) { BufferData::ACTIVE => { // server is using the buffer, let ObjectData handle the destroy } BufferData::INACTIVE => { data.record_death(); self.buffer.destroy(); } _ => unreachable!("Invalid state in BufferData"), } } } } impl wayland_client::backend::ObjectData for BufferData { fn event( self: Arc, handle: &wayland_client::backend::Backend, msg: wayland_backend::protocol::Message, ) -> Option> { debug_assert!(wayland_client::backend::protocol::same_interface( msg.sender_id.interface(), wl_buffer::WlBuffer::interface() )); debug_assert!(msg.opcode == 0); match self.state.fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) { BufferData::ACTIVE => { self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); } BufferData::INACTIVE => { // possible spurious release, or someone called deactivate incorrectly log::debug!("Unexpected WlBuffer::Release on an inactive buffer"); } BufferData::DESTROY_ON_RELEASE => { self.record_death(); self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); // The Destroy message is identical to Release message (no args, same ID), so just reply handle .send_request(msg.map_fd(|x| x.as_raw_fd()), None, None) .expect("Unexpected invalid ID"); } BufferData::DEAD => { // no-op, this object is already unusable } _ => unreachable!("Invalid state in BufferData"), } None } fn destroyed(&self, _: wayland_backend::client::ObjectId) {} } impl Drop for BufferData { fn drop(&mut self) { let state = *self.state.get_mut(); if state == BufferData::ACTIVE || state == BufferData::DESTROY_ON_RELEASE { // Release the active-buffer count self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed); } if state != BufferData::DEAD { // nobody has ever transitioned state to DEAD, so we are responsible for freeing the // extra reference self.record_death(); } } } smithay-client-toolkit-0.18.0/src/subcompositor.rs000064400000000000000000000103511046102023000203740ustar 00000000000000use crate::reexports::client::globals::{BindError, GlobalList}; use crate::reexports::client::protocol::wl_compositor::WlCompositor; use crate::reexports::client::protocol::wl_subcompositor::WlSubcompositor; use crate::reexports::client::protocol::wl_subsurface::WlSubsurface; use crate::reexports::client::protocol::wl_surface::WlSurface; use crate::reexports::client::{Connection, Dispatch, Proxy, QueueHandle}; use crate::compositor::SurfaceData; use crate::globals::GlobalData; #[derive(Debug)] pub struct SubcompositorState { compositor: WlCompositor, subcompositor: WlSubcompositor, } impl SubcompositorState { pub fn bind( compositor: WlCompositor, globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result where State: Dispatch + 'static, { let subcompositor = globals.bind(queue_handle, 1..=1, GlobalData)?; Ok(SubcompositorState { compositor, subcompositor }) } pub fn create_subsurface( &self, parent: WlSurface, queue_handle: &QueueHandle, ) -> (WlSubsurface, WlSurface) where State: Dispatch + Dispatch + 'static, { let surface_data = SurfaceData::new(Some(parent.clone()), 1); let surface = self.compositor.create_surface(queue_handle, surface_data); let subsurface_data = SubsurfaceData::new(surface.clone()); let subsurface = self.subcompositor.get_subsurface(&surface, &parent, queue_handle, subsurface_data); (subsurface, surface) } } impl Dispatch for SubcompositorState where D: Dispatch, { fn event( _: &mut D, _: &WlSubsurface, _: ::Event, _: &SubsurfaceData, _: &Connection, _: &QueueHandle, ) { unreachable!("wl_subsurface has no events") } } impl Dispatch for SubcompositorState where D: Dispatch, { fn event( _: &mut D, _: &WlSubcompositor, _: ::Event, _: &GlobalData, _: &Connection, _: &QueueHandle, ) { unreachable!("wl_subcompositor has no events") } } /// The data assoctiated with the subsurface. #[derive(Debug)] pub struct SubsurfaceData { /// The surface used when creating this subsurface. surface: WlSurface, } impl SubsurfaceData { pub(crate) fn new(surface: WlSurface) -> Self { Self { surface } } /// Get the surface used when creating the given subsurface. pub fn surface(&self) -> &WlSurface { &self.surface } } #[macro_export] macro_rules! delegate_subcompositor { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_subcompositor::WlSubcompositor: $crate::globals::GlobalData ] => $crate::subcompositor::SubcompositorState ); $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_subsurface::WlSubsurface: $crate::subcompositor::SubsurfaceData ] => $crate::subcompositor::SubcompositorState ); }; ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, subsurface: [$($subsurface: ty),*$(,)?]) => { $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_subcompositor::WlSubcompositor: $crate::globals::GlobalData ] => $crate::subcompositor::SubcompositorState ); $( $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $crate::reexports::client::protocol::wl_subsurface::WlSubsurface: $subsurface ] => $crate::subcompositor::SubcompositorState ); )* }; }