├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── anvil ├── Cargo.toml ├── README.md ├── resources │ ├── cursor.rgba │ └── numbers.png └── src │ ├── cursor.rs │ ├── drawing.rs │ ├── focus.rs │ ├── input_handler.rs │ ├── lib.rs │ ├── main.rs │ ├── render.rs │ ├── shell │ ├── element.rs │ ├── grabs.rs │ ├── mod.rs │ ├── ssd.rs │ ├── x11.rs │ └── xdg.rs │ ├── state.rs │ ├── udev.rs │ ├── winit.rs │ └── x11.rs ├── benches ├── benchmark.rs └── geometry.rs ├── build.rs ├── clippy.toml ├── compile_wlcs.sh ├── doc_index.html ├── examples ├── buffer_test.rs ├── compositor.rs ├── minimal.rs ├── seat.rs └── vulkan.rs ├── smallvil ├── Cargo.toml └── src │ ├── grabs │ ├── mod.rs │ ├── move_grab.rs │ └── resize_grab.rs │ ├── handlers │ ├── compositor.rs │ ├── mod.rs │ └── xdg_shell.rs │ ├── input.rs │ ├── main.rs │ ├── state.rs │ └── winit.rs ├── smithay-drm-extras ├── Cargo.toml ├── LICENSE ├── README.md ├── examples │ └── simple.rs └── src │ ├── display_info.rs │ ├── docs │ └── doctest_helpers.rs │ ├── drm_scanner.rs │ ├── drm_scanner │ ├── connector_scanner.rs │ └── crtc_mapper.rs │ └── lib.rs ├── src ├── backend │ ├── allocator │ │ ├── dmabuf.rs │ │ ├── dumb.rs │ │ ├── format.rs │ │ ├── gbm.rs │ │ ├── mod.rs │ │ ├── swapchain.rs │ │ └── vulkan │ │ │ ├── format.rs │ │ │ └── mod.rs │ ├── drm │ │ ├── compositor │ │ │ ├── elements.rs │ │ │ ├── frame_result.rs │ │ │ └── mod.rs │ │ ├── device │ │ │ ├── atomic.rs │ │ │ ├── fd.rs │ │ │ ├── legacy.rs │ │ │ └── mod.rs │ │ ├── dumb.rs │ │ ├── error.rs │ │ ├── exporter │ │ │ ├── dumb.rs │ │ │ ├── gbm.rs │ │ │ └── mod.rs │ │ ├── gbm.rs │ │ ├── mod.rs │ │ ├── output.rs │ │ └── surface │ │ │ ├── atomic.rs │ │ │ ├── gbm.rs │ │ │ ├── legacy.rs │ │ │ └── mod.rs │ ├── egl │ │ ├── context.rs │ │ ├── device.rs │ │ ├── display.rs │ │ ├── error.rs │ │ ├── fence.rs │ │ ├── ffi.rs │ │ ├── mod.rs │ │ ├── native.rs │ │ └── surface.rs │ ├── input │ │ ├── mod.rs │ │ └── tablet.rs │ ├── libinput │ │ ├── mod.rs │ │ └── tablet.rs │ ├── mod.rs │ ├── renderer │ │ ├── color.rs │ │ ├── damage │ │ │ ├── mod.rs │ │ │ └── shaper.rs │ │ ├── element │ │ │ ├── memory.rs │ │ │ ├── mod.rs │ │ │ ├── solid.rs │ │ │ ├── surface.rs │ │ │ ├── tests.rs │ │ │ ├── texture.rs │ │ │ └── utils │ │ │ │ ├── elements.rs │ │ │ │ ├── mod.rs │ │ │ │ └── wayland.rs │ │ ├── gles │ │ │ ├── element.rs │ │ │ ├── error.rs │ │ │ ├── format.rs │ │ │ ├── mod.rs │ │ │ ├── shaders │ │ │ │ ├── implicit │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── solid.frag │ │ │ │ │ ├── solid.vert │ │ │ │ │ ├── texture.frag │ │ │ │ │ └── texture.vert │ │ │ │ └── mod.rs │ │ │ ├── texture.rs │ │ │ ├── uniform.rs │ │ │ └── version.rs │ │ ├── glow.rs │ │ ├── mod.rs │ │ ├── multigpu │ │ │ ├── gbm.rs │ │ │ └── mod.rs │ │ ├── pixman │ │ │ ├── error.rs │ │ │ └── mod.rs │ │ ├── sync │ │ │ ├── egl.rs │ │ │ └── mod.rs │ │ ├── test.rs │ │ └── utils │ │ │ ├── mod.rs │ │ │ └── wayland.rs │ ├── session │ │ ├── libseat.rs │ │ └── mod.rs │ ├── udev.rs │ ├── vulkan │ │ ├── inner.rs │ │ ├── mod.rs │ │ ├── phd.rs │ │ └── version.rs │ ├── winit │ │ ├── input.rs │ │ └── mod.rs │ └── x11 │ │ ├── buffer.rs │ │ ├── error.rs │ │ ├── extension.rs │ │ ├── input.rs │ │ ├── mod.rs │ │ ├── surface.rs │ │ └── window_inner.rs ├── desktop │ ├── mod.rs │ ├── space │ │ ├── element │ │ │ ├── mod.rs │ │ │ └── wayland.rs │ │ ├── mod.rs │ │ ├── output.rs │ │ ├── utils.rs │ │ └── wayland │ │ │ ├── layer.rs │ │ │ ├── mod.rs │ │ │ ├── window.rs │ │ │ └── x11.rs │ └── wayland │ │ ├── layer.rs │ │ ├── popup │ │ ├── grab.rs │ │ ├── manager.rs │ │ └── mod.rs │ │ ├── utils.rs │ │ └── window.rs ├── input │ ├── keyboard │ │ ├── keymap_file.rs │ │ ├── mod.rs │ │ ├── modifiers_state.rs │ │ └── xkb_config.rs │ ├── mod.rs │ ├── pointer │ │ ├── cursor_image.rs │ │ ├── grab.rs │ │ └── mod.rs │ └── touch │ │ ├── grab.rs │ │ └── mod.rs ├── lib.rs ├── output.rs ├── reexports.rs ├── utils │ ├── alive_tracker.rs │ ├── clock.rs │ ├── fd.rs │ ├── geometry.rs │ ├── hook.rs │ ├── ids.rs │ ├── iter.rs │ ├── mod.rs │ ├── sealed_file.rs │ ├── serial.rs │ ├── signaling.rs │ ├── user_data.rs │ └── x11rb.rs ├── wayland │ ├── alpha_modifier │ │ ├── dispatch.rs │ │ └── mod.rs │ ├── buffer │ │ └── mod.rs │ ├── commit_timing │ │ └── mod.rs │ ├── compositor │ │ ├── cache.rs │ │ ├── handlers.rs │ │ ├── mod.rs │ │ ├── transaction.rs │ │ └── tree.rs │ ├── content_type │ │ ├── dispatch.rs │ │ └── mod.rs │ ├── cursor_shape.rs │ ├── dmabuf │ │ ├── dispatch.rs │ │ └── mod.rs │ ├── drm_lease │ │ └── mod.rs │ ├── drm_syncobj │ │ ├── mod.rs │ │ └── sync_point.rs │ ├── fifo │ │ └── mod.rs │ ├── foreign_toplevel_list │ │ └── mod.rs │ ├── fractional_scale │ │ └── mod.rs │ ├── idle_inhibit │ │ ├── inhibitor.rs │ │ └── mod.rs │ ├── idle_notify │ │ └── mod.rs │ ├── input_method │ │ ├── input_method_handle.rs │ │ ├── input_method_keyboard_grab.rs │ │ ├── input_method_popup_surface.rs │ │ └── mod.rs │ ├── keyboard_shortcuts_inhibit │ │ ├── dispatch.rs │ │ └── mod.rs │ ├── mod.rs │ ├── output │ │ ├── handlers.rs │ │ ├── mod.rs │ │ └── xdg.rs │ ├── pointer_constraints.rs │ ├── pointer_gestures.rs │ ├── presentation │ │ └── mod.rs │ ├── relative_pointer.rs │ ├── seat │ │ ├── keyboard.rs │ │ ├── mod.rs │ │ ├── pointer.rs │ │ └── touch.rs │ ├── security_context │ │ ├── listener_source.rs │ │ └── mod.rs │ ├── selection │ │ ├── data_device │ │ │ ├── device.rs │ │ │ ├── dnd_grab.rs │ │ │ ├── mod.rs │ │ │ ├── server_dnd_grab.rs │ │ │ └── source.rs │ │ ├── device.rs │ │ ├── ext_data_control │ │ │ ├── device.rs │ │ │ ├── mod.rs │ │ │ └── source.rs │ │ ├── mod.rs │ │ ├── offer.rs │ │ ├── primary_selection │ │ │ ├── device.rs │ │ │ ├── mod.rs │ │ │ └── source.rs │ │ ├── seat_data.rs │ │ ├── source.rs │ │ └── wlr_data_control │ │ │ ├── device.rs │ │ │ ├── mod.rs │ │ │ └── source.rs │ ├── session_lock │ │ ├── lock.rs │ │ ├── mod.rs │ │ └── surface.rs │ ├── shell │ │ ├── kde │ │ │ ├── decoration.rs │ │ │ ├── handlers.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── wlr_layer │ │ │ ├── handlers.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ │ └── xdg │ │ │ ├── decoration.rs │ │ │ ├── dialog.rs │ │ │ ├── handlers.rs │ │ │ ├── handlers │ │ │ ├── positioner.rs │ │ │ ├── surface.rs │ │ │ ├── surface │ │ │ │ ├── popup.rs │ │ │ │ └── toplevel.rs │ │ │ └── wm_base.rs │ │ │ └── mod.rs │ ├── shm │ │ ├── handlers.rs │ │ ├── mod.rs │ │ └── pool.rs │ ├── single_pixel_buffer │ │ ├── handlers.rs │ │ └── mod.rs │ ├── socket.rs │ ├── tablet_manager │ │ ├── mod.rs │ │ ├── tablet.rs │ │ ├── tablet_seat.rs │ │ └── tablet_tool.rs │ ├── text_input │ │ ├── mod.rs │ │ └── text_input_handle.rs │ ├── viewporter │ │ └── mod.rs │ ├── virtual_keyboard │ │ ├── mod.rs │ │ └── virtual_keyboard_handle.rs │ ├── xdg_activation │ │ ├── dispatch.rs │ │ └── mod.rs │ ├── xdg_foreign │ │ ├── handlers.rs │ │ └── mod.rs │ ├── xdg_system_bell.rs │ ├── xdg_toplevel_icon.rs │ ├── xdg_toplevel_tag.rs │ ├── xwayland_keyboard_grab.rs │ └── xwayland_shell.rs └── xwayland │ ├── mod.rs │ ├── x11_sockets.rs │ ├── xserver.rs │ └── xwm │ ├── mod.rs │ ├── settings.rs │ └── surface.rs ├── test_clients ├── Cargo.toml └── src │ ├── bin │ └── test_xdg_map_unmap.rs │ └── lib.rs ├── test_gbm_bo_create_with_modifiers2.c ├── test_gbm_bo_get_fd_for_plane.c └── wlcs_anvil ├── Cargo.toml └── src ├── lib.rs └── main_loop.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | .vscode 5 | .vagga 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 110 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Smithay is open to contributions from anyone. Here are a few tips to get started if you want to participate. 4 | 5 | ## Coordination 6 | 7 | Most discussion about features and their implementations takes place on github. 8 | If you have questions, suggestions, ideas, you can open an issue to discuss it, or add your message in an already existing issue 9 | if it fits its scope. 10 | 11 | If you want a more realtime discussion I (@vberger) have a Matrix room dedicated to Smithay and 12 | my other wayland crates: [#smithay:matrix.org](https://matrix.to/#/#smithay:matrix.org). If you don't want to 13 | use matrix, this room is also bridged to libera.chat IRC on #smithay. 14 | 15 | ## Scope 16 | 17 | Smithay attempts to be as generic and un-opinionated as possible. As such, if you have an idea of a feature that would be usefull 18 | for your compositor project and would like it to be integrated in Smithay, please consider whether it is in its scope: 19 | 20 | - If this is a very generic feature that probably many different projects would find useful, it can be integrated in Smithay 21 | - If it is a rather specific feature, but can be framed as a special case of a more general feature, this general feature is 22 | likely worth adding to Smithay 23 | - If this feature is really specific to your use-case, it is out of scope for Smithay 24 | 25 | ## Structure 26 | 27 | Smithay aims to be a modular hierarchical library: 28 | 29 | - Functionalities should be split into independent modules as much as possible 30 | - There can be dependencies in functionalities 31 | - Even if most people would directly use a high-level functionality, the lower level abstractions it is built on should 32 | still be exposed independently if possible 33 | 34 | The goal is for Smithay to be a "use what you want" library, and features that are not used should have no impact on the 35 | application built with Smithay. 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Victor Berger and Victoria Brekenfeld 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Smithay 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/smithay.svg)](https://crates.io/crates/smithay) 6 | [![docs.rs](https://docs.rs/smithay/badge.svg)](https://docs.rs/smithay) 7 | [![Build Status](https://github.com/Smithay/smithay/workflows/Continuous%20Integration/badge.svg)](https://github.com/Smithay/smithay/actions) 8 | [![Join the chat on matrix at #smithay:matrix.org](https://img.shields.io/badge/%5Bm%5D-%23smithay%3Amatrix.org-blue.svg)](https://matrix.to/#/#smithay:matrix.org) 9 | ![Join the chat via bridge on #smithay on libera.chat](https://img.shields.io/badge/IRC-%23Smithay-blue.svg) 10 | 11 | A smithy for rusty wayland compositors 12 | 13 | ## Goals 14 | 15 | Smithay aims to provide building blocks to create wayland compositors in Rust. While not 16 | being a full-blown compositor, it'll provide objects and interfaces implementing common 17 | functionalities that pretty much any compositor will need, in a generic fashion. 18 | 19 | It supports the [core Wayland protocols](https://gitlab.freedesktop.org/wayland/wayland), the official [protocol extensions](https://gitlab.freedesktop.org/wayland/wayland-protocols), and *some* external extensions, such as those made by and for [wlroots](https://gitlab.freedesktop.org/wlroots/wlr-protocols) and [KDE](https://invent.kde.org/libraries/plasma-wayland-protocols) 20 | 21 | 22 | Also: 23 | 24 | - **Documented:** Smithay strives to maintain a clear and detailed documentation of its API and its 25 | functionalities. Compiled documentations are available on [docs.rs](https://docs.rs/smithay) for released 26 | versions, and [here](https://smithay.github.io/smithay) for the master branch. 27 | - **Safety:** Smithay will target to be safe to use, because Rust. 28 | - **Modularity:** Smithay is not a framework, and will not be constraining. If there is a 29 | part you don't want to use, you should not be forced to use it. 30 | - **High-level:** You should be able to not have to worry about gory low-level stuff (but 31 | Smithay won't stop you if you really want to dive into it). 32 | 33 | 34 | ## Anvil 35 | 36 | Smithay as a compositor library has its own sample compositor: anvil. 37 | 38 | To get informations about it and how you can run it visit [anvil README](https://github.com/Smithay/smithay/blob/master/anvil/README.md) 39 | 40 | ## Other compositors that use Smithay 41 | 42 | - [Cosmic](https://github.com/pop-os/cosmic-epoch): Next generation Cosmic desktop environment 43 | - [Catacomb](https://github.com/catacombing/catacomb): A Wayland Mobile Compositor 44 | - [MagmaWM](https://github.com/MagmaWM/MagmaWM): A versatile and customizable Wayland Compositor 45 | - [Niri](https://github.com/YaLTeR/niri): A scrollable-tiling Wayland compositor 46 | - [Strata](https://github.com/StrataWM/strata): A cutting-edge, robust and sleek Wayland compositor 47 | - [Pinnacle](https://github.com/Ottatop/pinnacle): A WIP Wayland compositor, inspired by AwesomeWM 48 | - [Sudbury](https://gitlab.freedesktop.org/bwidawsk/sudbury): Compositor designed for ChromeOS 49 | - [wprs](https://github.com/wayland-transpositor/wprs): Like [xpra](https://en.wikipedia.org/wiki/Xpra), but for Wayland, and written in 50 | Rust. 51 | 52 | ## System Dependencies 53 | 54 | (This list can depend on features you enable) 55 | 56 | - `libwayland` 57 | - `libxkbcommon` 58 | - `libudev` 59 | - `libinput` 60 | - `libgbm` 61 | - [`libseat`](https://git.sr.ht/~kennylevinsen/seatd) 62 | - `xwayland` 63 | 64 | ## Contact us 65 | 66 | If you have questions or want to discuss the project with us, our main chatroom is on Matrix: [`#smithay:matrix.org`](https://matrix.to/#/#smithay:matrix.org). 67 | -------------------------------------------------------------------------------- /anvil/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Victor Berger ", "Drakulix (Victoria Brekenfeld)"] 3 | edition = "2021" 4 | license = "MIT" 5 | name = "anvil" 6 | publish = false 7 | version = "0.0.1" 8 | 9 | [dependencies] 10 | bitflags = "2.2.1" 11 | fps_ticker = {version = "1.0.0", optional = true} 12 | image = {version = "0.25.1", default-features = false, optional = true, features = ["png"]} 13 | rand = "0.8" 14 | tracing = { version = "0.1.37", features = ["max_level_trace", "release_max_level_debug"] } 15 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 16 | thiserror = "1" 17 | xcursor = {version = "0.3.3", optional = true} 18 | xkbcommon = "0.8.0" 19 | renderdoc = {version = "0.11.0", optional = true} 20 | smithay-drm-extras = {path = "../smithay-drm-extras", optional = true} 21 | puffin_http = { version = "0.13", optional = true } 22 | profiling = { version = "1.0" } 23 | 24 | [dependencies.smithay] 25 | default-features = false 26 | features = ["desktop", "wayland_frontend"] 27 | path = ".." 28 | 29 | [dependencies.x11rb] 30 | default-features = false 31 | features = ["composite"] 32 | optional = true 33 | version = "0.13.0" 34 | 35 | [build-dependencies] 36 | gl_generator = "0.14" 37 | 38 | [features] 39 | debug = ["fps_ticker", "image/png", "renderdoc"] 40 | default = ["egl", "winit", "x11", "udev", "xwayland"] 41 | egl = ["smithay/use_system_lib", "smithay/backend_egl"] 42 | test_all_features = ["default", "debug"] 43 | udev = [ 44 | "smithay-drm-extras", 45 | "smithay/backend_libinput", 46 | "smithay/backend_udev", 47 | "smithay/backend_drm", 48 | "smithay/backend_gbm", 49 | "smithay/backend_vulkan", 50 | "smithay/backend_egl", 51 | "smithay/backend_session_libseat", 52 | "image", 53 | "smithay/renderer_gl", 54 | "smithay/renderer_pixman", 55 | "smithay/renderer_multi", 56 | "xcursor", 57 | ] 58 | winit = ["smithay/backend_winit", "smithay/backend_drm"] 59 | x11 = ["smithay/backend_x11", "x11rb", "smithay/renderer_gl", "smithay/backend_vulkan"] 60 | xwayland = ["smithay/xwayland", "x11rb", "smithay/x11rb_event_source", "xcursor"] 61 | profile-with-puffin = ["profiling/profile-with-puffin", "puffin_http"] 62 | profile-with-tracy = ["profiling/profile-with-tracy"] 63 | profile-with-tracy-mem = ["profile-with-tracy"] 64 | renderer_sync = [] 65 | -------------------------------------------------------------------------------- /anvil/README.md: -------------------------------------------------------------------------------- 1 | # Anvil 2 | 3 | A compositor used as a testing ground for new smithay features. 4 | For a simple example compositor consider reading [smallvil](https://github.com/Smithay/smithay/tree/master/smallvil) 5 | 6 | ## Dependencies 7 | 8 | You'll need to install the following dependencies (note, that those package 9 | names may vary depending on your OS and linux distribution): 10 | 11 | - `libwayland` 12 | - `libxkbcommon` 13 | 14 | #### These are needed for the "Udev/DRM backend" 15 | 16 | - `libudev` 17 | - `libinput` 18 | - `libgbm` 19 | - [`libseat`](https://git.sr.ht/~kennylevinsen/seatd) 20 | 21 | If you want to enable X11 support (to run X11 applications within anvil), 22 | then you'll need to install the following packages as well: 23 | - `xwayland` 24 | 25 | ## Build and run 26 | 27 | You can run it with cargo after having cloned this repository: 28 | 29 | ``` 30 | cd anvil; 31 | 32 | cargo run -- --{backend} 33 | ``` 34 | 35 | The currently available backends are: 36 | 37 | - `--x11`: start anvil as an X11 client. This allows you to run the compositor inside an X11 session or any compositor supporting XWayland. Should be preferred over the winit backend where possible. 38 | - `--winit`: start anvil as a [Winit](https://github.com/tomaka/winit) application. This allows you to run it 39 | inside of an other X11 or Wayland session. 40 | - `--tty-udev`: start anvil in a tty with udev support. This is the "traditional" launch of a Wayland 41 | compositor. Note that this requires you to start anvil as root if your system does not have logind 42 | available. 43 | 44 | ### Supported Environment Variables 45 | 46 | | Variable | Example | Backends | 47 | |-------------------------------|-----------------|-----------| 48 | | ANVIL_DRM_DEVICE | /dev/dri/card0 | tty-udev | 49 | | ANVIL_DISABLE_10BIT | any | tty-udev | 50 | | ANVIL_DISABLE_DIRECT_SCANOUT | any | tty-udev | 51 | | ANVIL_NO_VULKAN | 1,true,yes,y | x11 | 52 | | SMITHAY_USE_LEGACY | 1,true,yes,y | tty-udev | 53 | | SMITHAY_VK_VERSION | 1.3 | | 54 | -------------------------------------------------------------------------------- /anvil/resources/cursor.rgba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/smithay/776ba424423584400e76317e688b160546e68ca7/anvil/resources/cursor.rgba -------------------------------------------------------------------------------- /anvil/resources/numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smithay/smithay/776ba424423584400e76317e688b160546e68ca7/anvil/resources/numbers.png -------------------------------------------------------------------------------- /anvil/src/cursor.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Read, time::Duration}; 2 | 3 | use tracing::warn; 4 | use xcursor::{ 5 | parser::{parse_xcursor, Image}, 6 | CursorTheme, 7 | }; 8 | 9 | static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba"); 10 | 11 | pub struct Cursor { 12 | icons: Vec, 13 | size: u32, 14 | } 15 | 16 | impl Cursor { 17 | pub fn load() -> Cursor { 18 | let name = std::env::var("XCURSOR_THEME") 19 | .ok() 20 | .unwrap_or_else(|| "default".into()); 21 | let size = std::env::var("XCURSOR_SIZE") 22 | .ok() 23 | .and_then(|s| s.parse().ok()) 24 | .unwrap_or(24); 25 | 26 | let theme = CursorTheme::load(&name); 27 | let icons = load_icon(&theme) 28 | .map_err(|err| warn!("Unable to load xcursor: {}, using fallback cursor", err)) 29 | .unwrap_or_else(|_| { 30 | vec![Image { 31 | size: 32, 32 | width: 64, 33 | height: 64, 34 | xhot: 1, 35 | yhot: 1, 36 | delay: 1, 37 | pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), 38 | pixels_argb: vec![], //unused 39 | }] 40 | }); 41 | 42 | Cursor { icons, size } 43 | } 44 | 45 | pub fn get_image(&self, scale: u32, time: Duration) -> Image { 46 | let size = self.size * scale; 47 | frame(time.as_millis() as u32, size, &self.icons) 48 | } 49 | } 50 | 51 | fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { 52 | // Follow the nominal size of the cursor to choose the nearest 53 | let nearest_image = images 54 | .iter() 55 | .min_by_key(|image| (size as i32 - image.size as i32).abs()) 56 | .unwrap(); 57 | 58 | images 59 | .iter() 60 | .filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height) 61 | } 62 | 63 | fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { 64 | let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); 65 | if total == 0 { 66 | return nearest_images(size, images).next().unwrap().clone(); 67 | } 68 | millis %= total; 69 | 70 | for img in nearest_images(size, images) { 71 | if millis < img.delay { 72 | return img.clone(); 73 | } 74 | millis -= img.delay; 75 | } 76 | 77 | unreachable!() 78 | } 79 | 80 | #[derive(thiserror::Error, Debug)] 81 | enum Error { 82 | #[error("Theme has no default cursor")] 83 | NoDefaultCursor, 84 | #[error("Error opening xcursor file: {0}")] 85 | File(#[from] std::io::Error), 86 | #[error("Failed to parse XCursor file")] 87 | Parse, 88 | } 89 | 90 | fn load_icon(theme: &CursorTheme) -> Result, Error> { 91 | let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?; 92 | let mut cursor_file = std::fs::File::open(icon_path)?; 93 | let mut cursor_data = Vec::new(); 94 | cursor_file.read_to_end(&mut cursor_data)?; 95 | parse_xcursor(&cursor_data).ok_or(Error::Parse) 96 | } 97 | -------------------------------------------------------------------------------- /anvil/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | // If no backend is enabled, a large portion of the codebase is unused. 3 | // So silence this useless warning for the CI. 4 | #![cfg_attr( 5 | not(any(feature = "winit", feature = "x11", feature = "udev")), 6 | allow(dead_code, unused_imports) 7 | )] 8 | 9 | #[cfg(any(feature = "udev", feature = "xwayland"))] 10 | pub mod cursor; 11 | pub mod drawing; 12 | pub mod focus; 13 | pub mod input_handler; 14 | pub mod render; 15 | pub mod shell; 16 | pub mod state; 17 | #[cfg(feature = "udev")] 18 | pub mod udev; 19 | #[cfg(feature = "winit")] 20 | pub mod winit; 21 | #[cfg(feature = "x11")] 22 | pub mod x11; 23 | 24 | pub use state::{AnvilState, ClientState}; 25 | -------------------------------------------------------------------------------- /anvil/src/main.rs: -------------------------------------------------------------------------------- 1 | static POSSIBLE_BACKENDS: &[&str] = &[ 2 | #[cfg(feature = "winit")] 3 | "--winit : Run anvil as a X11 or Wayland client using winit.", 4 | #[cfg(feature = "udev")] 5 | "--tty-udev : Run anvil as a tty udev client (requires root if without logind).", 6 | #[cfg(feature = "x11")] 7 | "--x11 : Run anvil as an X11 client.", 8 | ]; 9 | 10 | #[cfg(feature = "profile-with-tracy-mem")] 11 | #[global_allocator] 12 | static GLOBAL: profiling::tracy_client::ProfiledAllocator = 13 | profiling::tracy_client::ProfiledAllocator::new(std::alloc::System, 10); 14 | 15 | fn main() { 16 | if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { 17 | tracing_subscriber::fmt() 18 | .compact() 19 | .with_env_filter(env_filter) 20 | .init(); 21 | } else { 22 | tracing_subscriber::fmt().compact().init(); 23 | } 24 | 25 | #[cfg(feature = "profile-with-tracy")] 26 | profiling::tracy_client::Client::start(); 27 | 28 | profiling::register_thread!("Main Thread"); 29 | 30 | #[cfg(feature = "profile-with-puffin")] 31 | let _server = puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap(); 32 | #[cfg(feature = "profile-with-puffin")] 33 | profiling::puffin::set_scopes_on(true); 34 | 35 | let arg = ::std::env::args().nth(1); 36 | match arg.as_ref().map(|s| &s[..]) { 37 | #[cfg(feature = "winit")] 38 | Some("--winit") => { 39 | tracing::info!("Starting anvil with winit backend"); 40 | anvil::winit::run_winit(); 41 | } 42 | #[cfg(feature = "udev")] 43 | Some("--tty-udev") => { 44 | tracing::info!("Starting anvil on a tty using udev"); 45 | anvil::udev::run_udev(); 46 | } 47 | #[cfg(feature = "x11")] 48 | Some("--x11") => { 49 | tracing::info!("Starting anvil with x11 backend"); 50 | anvil::x11::run_x11(); 51 | } 52 | Some(other) => { 53 | tracing::error!("Unknown backend: {}", other); 54 | } 55 | None => { 56 | #[allow(clippy::disallowed_macros)] 57 | { 58 | println!("USAGE: anvil --backend"); 59 | println!(); 60 | println!("Possible backends are:"); 61 | for b in POSSIBLE_BACKENDS { 62 | println!("\t{}", b); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use smithay::utils::user_data::UserDataMap; 3 | 4 | fn criterion_benchmark(c: &mut Criterion) { 5 | c.bench_function("UserDataMap::get", |b| { 6 | let udata_map = UserDataMap::new(); 7 | udata_map.insert_if_missing(|| 17i32); 8 | b.iter(|| udata_map.get::()) 9 | }); 10 | c.bench_function("UserDataMap::get threadsafe", |b| { 11 | let udata_map = UserDataMap::new(); 12 | udata_map.insert_if_missing_threadsafe(|| 17i32); 13 | b.iter(|| udata_map.get::()) 14 | }); 15 | } 16 | 17 | criterion_group!(benches, criterion_benchmark); 18 | criterion_main!(benches); 19 | -------------------------------------------------------------------------------- /benches/geometry.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use rand::Rng; 3 | use smithay::utils::{Physical, Rectangle, Size}; 4 | 5 | fn element_visible_size(test_element: Rectangle, opaque_regions: &[Rectangle]) { 6 | let mut workhouse = Vec::with_capacity(2048 * 4); 7 | workhouse.push(test_element); 8 | workhouse = Rectangle::subtract_rects_many_in_place(workhouse, opaque_regions.iter().copied()); 9 | workhouse 10 | .iter() 11 | .fold(0usize, |acc, item| acc + (item.size.w * item.size.h) as usize); 12 | } 13 | 14 | fn criterion_benchmark(c: &mut Criterion) { 15 | let stage: Size = Size::from((800, 600)); 16 | let element_size: Size = Size::from((200, 100)); 17 | let max_x = stage.w - element_size.w; 18 | let max_y = stage.h - element_size.h; 19 | 20 | let mut rand = rand::thread_rng(); 21 | let x = rand.gen_range(0..max_x); 22 | let y = rand.gen_range(0..max_y); 23 | let test_element = Rectangle::new((x, y).into(), element_size); 24 | 25 | let x_min = (test_element.loc.x - element_size.w) + 1; 26 | let x_max = (test_element.loc.x + element_size.w) - 1; 27 | let y_min = (test_element.loc.y - element_size.h) + 1; 28 | let y_max = (test_element.loc.y + element_size.h) - 1; 29 | // let x_min = 0; 30 | // let x_max = stage.w - element_size.w; 31 | // let y_min = 0; 32 | // let y_max = stage.h - element_size.h; 33 | 34 | let opaque_regions = (0..2048) 35 | .map(|_| { 36 | let x = rand.gen_range(x_min..=x_max); 37 | let y = rand.gen_range(y_min..=y_max); 38 | Rectangle::new((x, y).into(), element_size) 39 | }) 40 | .collect::>(); 41 | 42 | c.bench_function("element_visible_size", |b| { 43 | b.iter(|| { 44 | element_visible_size(test_element, &opaque_regions); 45 | }); 46 | }); 47 | } 48 | 49 | criterion_group!(benches, criterion_benchmark); 50 | criterion_main!(benches); 51 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.80.1" 2 | type-complexity-threshold = 400 3 | 4 | disallowed-macros = [ 5 | { path = "std::print", reason = "We use tracing for logging" }, 6 | { path = "std::println", reason = "We use tracing for logging" }, 7 | { path = "std::eprint", reason = "We use tracing for logging" }, 8 | { path = "std::eprintln", reason = "We use tracing for logging" }, 9 | ] 10 | -------------------------------------------------------------------------------- /compile_wlcs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | WLCS_SHA=12234affdc0a4cc104fbaf8a502efc5f822b973b 4 | 5 | if [ -f "./wlcs/wlcs" ] && [ "$(cd wlcs; git rev-parse HEAD)" = "${WLCS_SHA}" ] ; then 6 | echo "Using cached WLCS." 7 | else 8 | echo "Compiling WLCS." 9 | git clone https://github.com/MirServer/wlcs.git 10 | cd wlcs || exit 11 | # checkout a specific revision 12 | git reset --hard "${WLCS_SHA}" 13 | cmake -DWLCS_BUILD_ASAN=False -DWLCS_BUILD_TSAN=False -DWLCS_BUILD_UBSAN=False -DCMAKE_EXPORT_COMPILE_COMMANDS=1 . 14 | make 15 | fi 16 | -------------------------------------------------------------------------------- /doc_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/compositor.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use smithay::delegate_compositor; 4 | use smithay::reexports::wayland_server::Display; 5 | 6 | use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState}; 7 | 8 | use wayland_server::backend::{ClientData, ClientId, DisconnectReason}; 9 | use wayland_server::protocol::wl_surface::WlSurface; 10 | use wayland_server::{Client, ListeningSocket}; 11 | 12 | struct App { 13 | compositor_state: CompositorState, 14 | } 15 | 16 | impl CompositorHandler for App { 17 | fn compositor_state(&mut self) -> &mut CompositorState { 18 | &mut self.compositor_state 19 | } 20 | 21 | fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { 22 | &client.get_data::().unwrap().compositor_state 23 | } 24 | 25 | fn commit(&mut self, surface: &WlSurface) { 26 | dbg!("Commit", surface); 27 | } 28 | } 29 | 30 | fn main() -> Result<(), Box> { 31 | let mut display: Display = Display::new()?; 32 | let dh = display.handle(); 33 | 34 | let compositor_state = CompositorState::new::(&dh); 35 | 36 | let mut state = App { compositor_state }; 37 | 38 | let listener = ListeningSocket::bind("wayland-5").unwrap(); 39 | 40 | let mut clients = Vec::new(); 41 | 42 | loop { 43 | if let Some(stream) = listener.accept().unwrap() { 44 | println!("Got a client: {:?}", stream); 45 | 46 | let client = display 47 | .handle() 48 | .insert_client(stream, Arc::new(ClientState::default())) 49 | .unwrap(); 50 | clients.push(client); 51 | } 52 | 53 | display.dispatch_clients(&mut state)?; 54 | display.flush_clients()?; 55 | } 56 | } 57 | 58 | #[derive(Default)] 59 | struct ClientState { 60 | compositor_state: CompositorClientState, 61 | } 62 | impl ClientData for ClientState { 63 | fn initialized(&self, _client_id: ClientId) { 64 | println!("initialized"); 65 | } 66 | 67 | fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) { 68 | println!("disconnected"); 69 | } 70 | } 71 | 72 | impl AsMut for App { 73 | fn as_mut(&mut self) -> &mut CompositorState { 74 | &mut self.compositor_state 75 | } 76 | } 77 | 78 | delegate_compositor!(App); 79 | -------------------------------------------------------------------------------- /examples/seat.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use smithay::input::{keyboard::FilterResult, Seat, SeatHandler, SeatState}; 4 | use smithay::reexports::wayland_server::{ 5 | backend::{ClientData, ClientId, DisconnectReason}, 6 | protocol::wl_surface::WlSurface, 7 | Display, ListeningSocket, 8 | }; 9 | use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState}; 10 | use smithay::{delegate_compositor, delegate_seat}; 11 | 12 | struct App { 13 | compositor_state: CompositorState, 14 | seat_state: SeatState, 15 | seat: Seat, 16 | } 17 | 18 | impl SeatHandler for App { 19 | type KeyboardFocus = WlSurface; 20 | type PointerFocus = WlSurface; 21 | type TouchFocus = WlSurface; 22 | 23 | fn seat_state(&mut self) -> &mut SeatState { 24 | &mut self.seat_state 25 | } 26 | 27 | fn focus_changed(&mut self, _seat: &Seat, _focused: Option<&WlSurface>) {} 28 | fn cursor_image(&mut self, _seat: &Seat, _image: smithay::input::pointer::CursorImageStatus) {} 29 | } 30 | 31 | fn main() -> Result<(), Box> { 32 | let mut display: Display = Display::new()?; 33 | let dh = display.handle(); 34 | 35 | let compositor_state = CompositorState::new::(&dh); 36 | let mut seat_state = SeatState::new(); 37 | let seat = seat_state.new_wl_seat(&dh, "Example"); 38 | 39 | let mut state = App { 40 | compositor_state, 41 | seat_state, 42 | seat, 43 | }; 44 | 45 | let keyboard = state.seat.add_keyboard(Default::default(), 25, 600)?; 46 | 47 | let listener = ListeningSocket::bind("wayland-5").unwrap(); 48 | 49 | let mut clients = Vec::new(); 50 | 51 | loop { 52 | if let Some(stream) = listener.accept().unwrap() { 53 | println!("Got a client: {:?}", stream); 54 | 55 | let client = display 56 | .handle() 57 | .insert_client(stream, Arc::new(ClientState(CompositorClientState::default()))) 58 | .unwrap(); 59 | clients.push(client); 60 | } 61 | 62 | keyboard.input( 63 | &mut state, 64 | smithay::backend::input::Keycode::from(9u32), 65 | smithay::backend::input::KeyState::Pressed, 66 | 0.into(), 67 | 0, 68 | |_, _, _| { 69 | if false { 70 | FilterResult::Intercept(0) 71 | } else { 72 | FilterResult::Forward 73 | } 74 | }, 75 | ); 76 | 77 | keyboard.set_focus(&mut state, Option::::None, 0.into()); 78 | 79 | display.dispatch_clients(&mut state)?; 80 | display.flush_clients()?; 81 | } 82 | } 83 | 84 | struct ClientState(CompositorClientState); 85 | impl ClientData for ClientState { 86 | fn initialized(&self, _client_id: ClientId) { 87 | println!("initialized"); 88 | } 89 | 90 | fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) { 91 | println!("disconnected"); 92 | } 93 | } 94 | 95 | impl CompositorHandler for App { 96 | fn compositor_state(&mut self) -> &mut CompositorState { 97 | &mut self.compositor_state 98 | } 99 | 100 | fn client_compositor_state<'a>(&self, client: &'a wayland_server::Client) -> &'a CompositorClientState { 101 | &client.get_data::().unwrap().0 102 | } 103 | 104 | fn commit(&mut self, _surface: &WlSurface) {} 105 | } 106 | 107 | delegate_compositor!(App); 108 | delegate_seat!(App); 109 | -------------------------------------------------------------------------------- /examples/vulkan.rs: -------------------------------------------------------------------------------- 1 | use drm_fourcc::{DrmFourcc, DrmModifier}; 2 | use smithay::backend::{ 3 | allocator::{ 4 | dmabuf::AsDmabuf, 5 | vulkan::{ImageUsageFlags, VulkanAllocator}, 6 | Allocator, Buffer, 7 | }, 8 | vulkan::{version::Version, Instance, PhysicalDevice}, 9 | }; 10 | 11 | fn main() { 12 | if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { 13 | tracing_subscriber::fmt().with_env_filter(env_filter).init(); 14 | } else { 15 | tracing_subscriber::fmt().init(); 16 | } 17 | 18 | println!( 19 | "Available instance extensions: {:?}", 20 | Instance::enumerate_extensions().unwrap().collect::>() 21 | ); 22 | println!(); 23 | 24 | let instance = Instance::new(Version::VERSION_1_3, None).unwrap(); 25 | 26 | for (idx, phy) in PhysicalDevice::enumerate(&instance).unwrap().enumerate() { 27 | println!( 28 | "Device #{}: {} v{}, {:?}", 29 | idx, 30 | phy.name(), 31 | phy.api_version(), 32 | phy.driver() 33 | ); 34 | } 35 | 36 | let physical_device = PhysicalDevice::enumerate(&instance) 37 | .unwrap() 38 | .next() 39 | .expect("No physical devices"); 40 | 41 | // The allocator should create buffers that are suitable as render targets. 42 | let mut allocator = VulkanAllocator::new(&physical_device, ImageUsageFlags::COLOR_ATTACHMENT).unwrap(); 43 | 44 | let image = allocator 45 | .create_buffer(100, 200, DrmFourcc::Argb8888, &[DrmModifier::Linear]) 46 | .expect("create"); 47 | 48 | assert_eq!(image.width(), 100); 49 | assert_eq!(image.height(), 200); 50 | 51 | let image_dmabuf = image.export().expect("Export dmabuf"); 52 | 53 | drop(image); 54 | 55 | let _image2 = allocator 56 | .create_buffer(200, 200, DrmFourcc::Argb8888, &[DrmModifier::Linear]) 57 | .expect("create"); 58 | 59 | drop(allocator); 60 | drop(image_dmabuf); 61 | } 62 | -------------------------------------------------------------------------------- /smallvil/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smallvil" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 8 | bitflags = "2.2.1" 9 | 10 | [dependencies.smithay] 11 | path = "../" 12 | default-features = false 13 | features = [ 14 | "backend_winit", 15 | "wayland_frontend", 16 | "desktop", 17 | ] 18 | -------------------------------------------------------------------------------- /smallvil/src/grabs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod move_grab; 2 | pub use move_grab::MoveSurfaceGrab; 3 | 4 | pub mod resize_grab; 5 | pub use resize_grab::ResizeSurfaceGrab; 6 | -------------------------------------------------------------------------------- /smallvil/src/handlers/compositor.rs: -------------------------------------------------------------------------------- 1 | use crate::{grabs::resize_grab, state::ClientState, Smallvil}; 2 | use smithay::{ 3 | backend::renderer::utils::on_commit_buffer_handler, 4 | delegate_compositor, delegate_shm, 5 | reexports::wayland_server::{ 6 | protocol::{wl_buffer, wl_surface::WlSurface}, 7 | Client, 8 | }, 9 | wayland::{ 10 | buffer::BufferHandler, 11 | compositor::{ 12 | get_parent, is_sync_subsurface, CompositorClientState, CompositorHandler, CompositorState, 13 | }, 14 | shm::{ShmHandler, ShmState}, 15 | }, 16 | }; 17 | 18 | use super::xdg_shell; 19 | 20 | impl CompositorHandler for Smallvil { 21 | fn compositor_state(&mut self) -> &mut CompositorState { 22 | &mut self.compositor_state 23 | } 24 | 25 | fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { 26 | &client.get_data::().unwrap().compositor_state 27 | } 28 | 29 | fn commit(&mut self, surface: &WlSurface) { 30 | on_commit_buffer_handler::(surface); 31 | if !is_sync_subsurface(surface) { 32 | let mut root = surface.clone(); 33 | while let Some(parent) = get_parent(&root) { 34 | root = parent; 35 | } 36 | if let Some(window) = self 37 | .space 38 | .elements() 39 | .find(|w| w.toplevel().unwrap().wl_surface() == &root) 40 | { 41 | window.on_commit(); 42 | } 43 | }; 44 | 45 | xdg_shell::handle_commit(&mut self.popups, &self.space, surface); 46 | resize_grab::handle_commit(&mut self.space, surface); 47 | } 48 | } 49 | 50 | impl BufferHandler for Smallvil { 51 | fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} 52 | } 53 | 54 | impl ShmHandler for Smallvil { 55 | fn shm_state(&self) -> &ShmState { 56 | &self.shm_state 57 | } 58 | } 59 | 60 | delegate_compositor!(Smallvil); 61 | delegate_shm!(Smallvil); 62 | -------------------------------------------------------------------------------- /smallvil/src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | mod compositor; 2 | mod xdg_shell; 3 | 4 | use crate::Smallvil; 5 | 6 | // 7 | // Wl Seat 8 | // 9 | 10 | use smithay::input::{Seat, SeatHandler, SeatState}; 11 | use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; 12 | use smithay::reexports::wayland_server::Resource; 13 | use smithay::wayland::output::OutputHandler; 14 | use smithay::wayland::selection::data_device::{ 15 | set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, 16 | }; 17 | use smithay::wayland::selection::SelectionHandler; 18 | use smithay::{delegate_data_device, delegate_output, delegate_seat}; 19 | 20 | impl SeatHandler for Smallvil { 21 | type KeyboardFocus = WlSurface; 22 | type PointerFocus = WlSurface; 23 | type TouchFocus = WlSurface; 24 | 25 | fn seat_state(&mut self) -> &mut SeatState { 26 | &mut self.seat_state 27 | } 28 | 29 | fn cursor_image(&mut self, _seat: &Seat, _image: smithay::input::pointer::CursorImageStatus) {} 30 | 31 | fn focus_changed(&mut self, seat: &Seat, focused: Option<&WlSurface>) { 32 | let dh = &self.display_handle; 33 | let client = focused.and_then(|s| dh.get_client(s.id()).ok()); 34 | set_data_device_focus(dh, seat, client); 35 | } 36 | } 37 | 38 | delegate_seat!(Smallvil); 39 | 40 | // 41 | // Wl Data Device 42 | // 43 | 44 | impl SelectionHandler for Smallvil { 45 | type SelectionUserData = (); 46 | } 47 | 48 | impl DataDeviceHandler for Smallvil { 49 | fn data_device_state(&self) -> &DataDeviceState { 50 | &self.data_device_state 51 | } 52 | } 53 | 54 | impl ClientDndGrabHandler for Smallvil {} 55 | impl ServerDndGrabHandler for Smallvil {} 56 | 57 | delegate_data_device!(Smallvil); 58 | 59 | // 60 | // Wl Output & Xdg Output 61 | // 62 | 63 | impl OutputHandler for Smallvil {} 64 | delegate_output!(Smallvil); 65 | -------------------------------------------------------------------------------- /smallvil/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(irrefutable_let_patterns)] 2 | 3 | mod handlers; 4 | 5 | mod grabs; 6 | mod input; 7 | mod state; 8 | mod winit; 9 | 10 | use smithay::reexports::{ 11 | calloop::EventLoop, 12 | wayland_server::{Display, DisplayHandle}, 13 | }; 14 | pub use state::Smallvil; 15 | 16 | pub struct CalloopData { 17 | state: Smallvil, 18 | display_handle: DisplayHandle, 19 | } 20 | 21 | fn main() -> Result<(), Box> { 22 | if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { 23 | tracing_subscriber::fmt().with_env_filter(env_filter).init(); 24 | } else { 25 | tracing_subscriber::fmt().init(); 26 | } 27 | 28 | let mut event_loop: EventLoop = EventLoop::try_new()?; 29 | 30 | let display: Display = Display::new()?; 31 | let display_handle = display.handle(); 32 | let state = Smallvil::new(&mut event_loop, display); 33 | 34 | let mut data = CalloopData { 35 | state, 36 | display_handle, 37 | }; 38 | 39 | crate::winit::init_winit(&mut event_loop, &mut data)?; 40 | 41 | let mut args = std::env::args().skip(1); 42 | let flag = args.next(); 43 | let arg = args.next(); 44 | 45 | match (flag.as_deref(), arg) { 46 | (Some("-c") | Some("--command"), Some(command)) => { 47 | std::process::Command::new(command).spawn().ok(); 48 | } 49 | _ => { 50 | std::process::Command::new("weston-terminal").spawn().ok(); 51 | } 52 | } 53 | 54 | event_loop.run(None, &mut data, move |_| { 55 | // Smallvil is running 56 | })?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /smallvil/src/winit.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use smithay::{ 4 | backend::{ 5 | renderer::{ 6 | damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, gles::GlesRenderer, 7 | }, 8 | winit::{self, WinitEvent}, 9 | }, 10 | output::{Mode, Output, PhysicalProperties, Subpixel}, 11 | reexports::calloop::EventLoop, 12 | utils::{Rectangle, Transform}, 13 | }; 14 | 15 | use crate::{CalloopData, Smallvil}; 16 | 17 | pub fn init_winit( 18 | event_loop: &mut EventLoop, 19 | data: &mut CalloopData, 20 | ) -> Result<(), Box> { 21 | let display_handle = &mut data.display_handle; 22 | let state = &mut data.state; 23 | 24 | let (mut backend, winit) = winit::init()?; 25 | 26 | let mode = Mode { 27 | size: backend.window_size(), 28 | refresh: 60_000, 29 | }; 30 | 31 | let output = Output::new( 32 | "winit".to_string(), 33 | PhysicalProperties { 34 | size: (0, 0).into(), 35 | subpixel: Subpixel::Unknown, 36 | make: "Smithay".into(), 37 | model: "Winit".into(), 38 | }, 39 | ); 40 | let _global = output.create_global::(display_handle); 41 | output.change_current_state(Some(mode), Some(Transform::Flipped180), None, Some((0, 0).into())); 42 | output.set_preferred(mode); 43 | 44 | state.space.map_output(&output, (0, 0)); 45 | 46 | let mut damage_tracker = OutputDamageTracker::from_output(&output); 47 | 48 | std::env::set_var("WAYLAND_DISPLAY", &state.socket_name); 49 | 50 | event_loop.handle().insert_source(winit, move |event, _, data| { 51 | let display = &mut data.display_handle; 52 | let state = &mut data.state; 53 | 54 | match event { 55 | WinitEvent::Resized { size, .. } => { 56 | output.change_current_state( 57 | Some(Mode { 58 | size, 59 | refresh: 60_000, 60 | }), 61 | None, 62 | None, 63 | None, 64 | ); 65 | } 66 | WinitEvent::Input(event) => state.process_input_event(event), 67 | WinitEvent::Redraw => { 68 | let size = backend.window_size(); 69 | let damage = Rectangle::from_size(size); 70 | 71 | { 72 | let (renderer, mut framebuffer) = backend.bind().unwrap(); 73 | smithay::desktop::space::render_output::< 74 | _, 75 | WaylandSurfaceRenderElement, 76 | _, 77 | _, 78 | >( 79 | &output, 80 | renderer, 81 | &mut framebuffer, 82 | 1.0, 83 | 0, 84 | [&state.space], 85 | &[], 86 | &mut damage_tracker, 87 | [0.1, 0.1, 0.1, 1.0], 88 | ) 89 | .unwrap(); 90 | } 91 | backend.submit(Some(&[damage])).unwrap(); 92 | 93 | state.space.elements().for_each(|window| { 94 | window.send_frame( 95 | &output, 96 | state.start_time.elapsed(), 97 | Some(Duration::ZERO), 98 | |_, _| Some(output.clone()), 99 | ) 100 | }); 101 | 102 | state.space.refresh(); 103 | state.popups.cleanup(); 104 | let _ = display.flush_clients(); 105 | 106 | // Ask for redraw to schedule new frame. 107 | backend.window().request_redraw(); 108 | } 109 | WinitEvent::CloseRequested => { 110 | state.loop_signal.stop(); 111 | } 112 | _ => (), 113 | }; 114 | })?; 115 | 116 | Ok(()) 117 | } 118 | -------------------------------------------------------------------------------- /smithay-drm-extras/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smithay-drm-extras" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Bartłomiej Maryńczak "] 7 | 8 | [dependencies] 9 | libdisplay-info = { version = "0.2.1", optional = true } 10 | drm = { version = "0.14.0" } 11 | 12 | [features] 13 | default = ["display-info"] 14 | display-info = ["libdisplay-info"] 15 | 16 | [dev-dependencies.smithay] 17 | path = "../" 18 | -------------------------------------------------------------------------------- /smithay-drm-extras/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Bartłomiej Maryńczak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /smithay-drm-extras/README.md: -------------------------------------------------------------------------------- 1 | # Smithay DRM Extras 2 | 3 | This crate contains some extra abstractions and helpers over DRM 4 | 5 | - `display_info` module is responsible for extraction of information from DRM connectors (`model` and `manufacturer`) 6 | - `drm_scanner` module contains helpers for detecting connector connected and disconnected events as well as mapping crtc to them. 7 | - `ConnectorScanner` is responsible for tracking connected/disconnected events. 8 | - `CrtcMapper` trait and `SimpleCrtcMapper` are meant for mapping crtc to connector. 9 | - `DrmScanner` combines two above into single abstraction. If it does not fit your needs you can always drop down to using `ConnectoScanner` alone. -------------------------------------------------------------------------------- /smithay-drm-extras/src/display_info.rs: -------------------------------------------------------------------------------- 1 | //! # EDID - Extended Display Identification Data 2 | //! 3 | //! This module is meant to help with extraction of EDID data from connectors 4 | //! 5 | //! ```no_run 6 | //! # mod helpers { include!("./docs/doctest_helpers.rs"); }; 7 | //! # let drm_device: helpers::FakeDevice = todo!(); 8 | //! # let connector = todo!(); 9 | //! use smithay_drm_extras::display_info; 10 | //! 11 | //! let info = display_info::for_connector(&drm_device, connector).unwrap(); 12 | //! 13 | //! println!("Monitor name: {}", info.model()); 14 | //! println!("Manufacturer name: {}", info.make()); 15 | //! ``` 16 | 17 | use drm::control::{connector, Device as ControlDevice}; 18 | use libdisplay_info::info::Info; 19 | 20 | /// Try to read the [`Info`] from the connector EDID property 21 | pub fn for_connector(device: &impl ControlDevice, connector: connector::Handle) -> Option { 22 | let props = device.get_properties(connector).ok()?; 23 | 24 | let (info, value) = props 25 | .into_iter() 26 | .filter_map(|(handle, value)| { 27 | let info = device.get_property(handle).ok()?; 28 | 29 | Some((info, value)) 30 | }) 31 | .find(|(info, _)| info.name().to_str() == Ok("EDID"))?; 32 | 33 | let blob = info.value_type().convert_value(value).as_blob()?; 34 | let data = device.get_property_blob(blob).ok()?; 35 | 36 | Info::parse_edid(&data).ok() 37 | } 38 | -------------------------------------------------------------------------------- /smithay-drm-extras/src/docs/doctest_helpers.rs: -------------------------------------------------------------------------------- 1 | pub struct FakeDevice; 2 | impl std::os::unix::prelude::AsFd for FakeDevice { 3 | fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> { 4 | unimplemented!() 5 | } 6 | } 7 | impl drm::Device for FakeDevice {} 8 | impl drm::control::Device for FakeDevice {} 9 | -------------------------------------------------------------------------------- /smithay-drm-extras/src/drm_scanner/crtc_mapper.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use drm::control::{connector, crtc, Device as ControlDevice}; 4 | 5 | /// CRTC Mapper trait 6 | /// 7 | /// It exists to allow custom mappers in [`super::DrmScanner`]. 8 | /// 9 | /// It is responsible for mapping CRTCs to connectors. 10 | /// For each connector it has to pick suitable CRTC. 11 | pub trait CrtcMapper { 12 | /// Request mapping of CRTCs to supplied connectors 13 | /// 14 | /// Usually called in response to udev device changed event, 15 | /// or on device init. 16 | fn map<'a>( 17 | &mut self, 18 | drm: &impl ControlDevice, 19 | connectors: impl Iterator + Clone, 20 | ); 21 | 22 | /// Query CRTC mapped to supplied connector 23 | fn crtc_for_connector(&self, connector: &connector::Handle) -> Option; 24 | } 25 | 26 | /// Simple CRTC Mapper 27 | /// 28 | /// This is basic mapper that simply chooses one CRTC for every connector. 29 | /// 30 | /// It is also capable of recovering mappings that were used by display manger or tty 31 | /// before the compositor was started up. 32 | #[derive(Debug, Default)] 33 | pub struct SimpleCrtcMapper { 34 | crtcs: HashMap, 35 | } 36 | 37 | impl SimpleCrtcMapper { 38 | /// Create new [`SimpleCrtcMapper`] 39 | pub fn new() -> Self { 40 | Self::default() 41 | } 42 | 43 | fn is_taken(&self, crtc: &crtc::Handle) -> bool { 44 | self.crtcs.values().any(|v| v == crtc) 45 | } 46 | 47 | fn is_available(&self, crtc: &crtc::Handle) -> bool { 48 | !self.is_taken(crtc) 49 | } 50 | 51 | fn restored_for_connector( 52 | &self, 53 | drm: &impl ControlDevice, 54 | connector: &connector::Info, 55 | ) -> Option { 56 | let encoder = connector.current_encoder()?; 57 | let encoder = drm.get_encoder(encoder).ok()?; 58 | let crtc = encoder.crtc()?; 59 | 60 | self.is_available(&crtc).then_some(crtc) 61 | } 62 | 63 | fn pick_next_avalible_for_connector( 64 | &self, 65 | drm: &impl ControlDevice, 66 | connector: &connector::Info, 67 | ) -> Option { 68 | let res_handles = drm.resource_handles().ok()?; 69 | 70 | connector 71 | .encoders() 72 | .iter() 73 | .flat_map(|encoder_handle| drm.get_encoder(*encoder_handle)) 74 | .find_map(|encoder_info| { 75 | res_handles 76 | .filter_crtcs(encoder_info.possible_crtcs()) 77 | .into_iter() 78 | .find(|crtc| self.is_available(crtc)) 79 | }) 80 | } 81 | } 82 | 83 | impl super::CrtcMapper for SimpleCrtcMapper { 84 | fn map<'a>( 85 | &mut self, 86 | drm: &impl ControlDevice, 87 | connectors: impl Iterator + Clone, 88 | ) { 89 | for connector in connectors 90 | .clone() 91 | .filter(|conn| conn.state() != connector::State::Connected) 92 | { 93 | self.crtcs.remove(&connector.handle()); 94 | } 95 | 96 | let mut needs_crtc: Vec<&connector::Info> = connectors 97 | .filter(|conn| conn.state() == connector::State::Connected) 98 | .filter(|conn| !self.crtcs.contains_key(&conn.handle())) 99 | .collect(); 100 | 101 | needs_crtc.retain(|connector| { 102 | if let Some(crtc) = self.restored_for_connector(drm, connector) { 103 | self.crtcs.insert(connector.handle(), crtc); 104 | 105 | // This connector no longer needs crtc so let's remove it 106 | false 107 | } else { 108 | true 109 | } 110 | }); 111 | 112 | for connector in needs_crtc { 113 | if let Some(crtc) = self.pick_next_avalible_for_connector(drm, connector) { 114 | self.crtcs.insert(connector.handle(), crtc); 115 | } 116 | } 117 | } 118 | 119 | fn crtc_for_connector(&self, connector: &connector::Handle) -> Option { 120 | self.crtcs.get(connector).copied() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /smithay-drm-extras/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Smithay DRM Extras 2 | //! 3 | //! This crate contains some extra abstractions and helpers over DRM 4 | //! 5 | //! - [`display_info`] is responsible for extraction of information from DRM connectors 6 | //! - [`drm_scanner`] is responsible for detecting connector connected and 7 | //! disconnected events, as well as mapping CRTC to them. 8 | //! 9 | //! ### Features 10 | //! - `display_info` - If enabled `display_info` functionality is enabled through `libdisplay-info` integration 11 | 12 | #![warn(missing_docs, missing_debug_implementations)] 13 | 14 | #[cfg(feature = "display-info")] 15 | pub mod display_info; 16 | pub mod drm_scanner; 17 | -------------------------------------------------------------------------------- /src/backend/allocator/dumb.rs: -------------------------------------------------------------------------------- 1 | //! Module for [DumbBuffer](https://docs.kernel.org/gpu/drm-kms.html#dumb-buffer-objects) buffers 2 | 3 | use std::fmt; 4 | use std::io; 5 | 6 | use drm::buffer::Buffer as DrmBuffer; 7 | use drm::control::{dumbbuffer::DumbBuffer as Handle, Device as ControlDevice}; 8 | use tracing::instrument; 9 | 10 | use super::dmabuf::{AsDmabuf, Dmabuf, DmabufFlags}; 11 | use super::{format::get_bpp, Allocator, Buffer, Format, Fourcc, Modifier}; 12 | use crate::backend::drm::DrmDeviceFd; 13 | use crate::backend::drm::DrmNode; 14 | use crate::utils::{Buffer as BufferCoords, Size}; 15 | 16 | /// Wrapper around raw DumbBuffer handles. 17 | pub struct DumbBuffer { 18 | fd: DrmDeviceFd, 19 | handle: Handle, 20 | format: Format, 21 | } 22 | 23 | impl fmt::Debug for DumbBuffer { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | f.debug_struct("DumbBuffer") 26 | .field("handle", &self.handle) 27 | .field("format", &self.format) 28 | .finish() 29 | } 30 | } 31 | 32 | /// Light wrapper around an [`DrmDeviceFd`] to implement the [`Allocator`]-trait 33 | #[derive(Debug)] 34 | pub struct DumbAllocator { 35 | fd: DrmDeviceFd, 36 | } 37 | 38 | impl DumbAllocator { 39 | /// Create a new [`DumbAllocator`] from a [`DrmDeviceFd`]. 40 | pub fn new(fd: DrmDeviceFd) -> Self { 41 | DumbAllocator { fd } 42 | } 43 | } 44 | 45 | impl Allocator for DumbAllocator { 46 | type Buffer = DumbBuffer; 47 | type Error = io::Error; 48 | 49 | #[instrument(level = "trace", err)] 50 | #[profiling::function] 51 | fn create_buffer( 52 | &mut self, 53 | width: u32, 54 | height: u32, 55 | fourcc: Fourcc, 56 | modifiers: &[Modifier], 57 | ) -> Result { 58 | // dumb buffers are always linear 59 | if modifiers 60 | .iter() 61 | .all(|&x| x != Modifier::Invalid && x != Modifier::Linear) 62 | { 63 | return Err(rustix::io::Errno::INVAL.into()); 64 | } 65 | 66 | let handle = self.fd.create_dumb_buffer( 67 | (width, height), 68 | fourcc, 69 | get_bpp(fourcc).ok_or(rustix::io::Errno::INVAL)? as u32, 70 | )?; 71 | 72 | Ok(DumbBuffer { 73 | fd: self.fd.clone(), 74 | handle, 75 | format: Format { 76 | code: fourcc, 77 | modifier: Modifier::Linear, 78 | }, 79 | }) 80 | } 81 | } 82 | 83 | impl Buffer for DumbBuffer { 84 | fn size(&self) -> Size { 85 | let (w, h) = self.handle.size(); 86 | (w as i32, h as i32).into() 87 | } 88 | 89 | fn format(&self) -> Format { 90 | self.format 91 | } 92 | } 93 | 94 | impl DumbBuffer { 95 | /// Raw handle to the underlying buffer. 96 | /// 97 | /// Note: This handle will become invalid, once the `DumbBuffer` wrapper is dropped 98 | /// or the device used to create is closed. Do not copy this handle and assume it keeps being valid. 99 | pub fn handle(&self) -> &Handle { 100 | &self.handle 101 | } 102 | } 103 | 104 | impl AsDmabuf for DumbBuffer { 105 | type Error = io::Error; 106 | 107 | #[profiling::function] 108 | fn export(&self) -> Result { 109 | let fd = self 110 | .fd 111 | .buffer_to_prime_fd(self.handle.handle(), drm::CLOEXEC | drm::RDWR)?; 112 | let mut builder = Dmabuf::builder( 113 | self.size(), 114 | self.format.code, 115 | self.format.modifier, 116 | DmabufFlags::empty(), 117 | ); 118 | builder.add_plane(fd, 0, 0, self.handle.pitch()); 119 | if let Ok(node) = DrmNode::from_file(&self.fd) { 120 | builder.set_node(node); 121 | } 122 | builder.build().ok_or(rustix::io::Errno::INVAL.into()) 123 | } 124 | } 125 | 126 | impl Drop for DumbBuffer { 127 | fn drop(&mut self) { 128 | let _ = self.fd.destroy_dumb_buffer(self.handle); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/backend/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Buffer allocation and management. 2 | //! 3 | //! Collection of common traits and implementations around 4 | //! buffer creation and handling from various sources. 5 | //! 6 | //! Allocators provided: 7 | //! - Dumb Buffers through [`crate::backend::drm::DrmDevice`] 8 | //! - Gbm Buffers through [`::gbm::Device`] 9 | //! 10 | //! Buffer types supported: 11 | //! - [DumbBuffers](dumb::DumbBuffer) 12 | //! - [GbmBuffers](::gbm::BufferObject) 13 | //! - [DmaBufs](dmabuf::Dmabuf) 14 | //! 15 | //! Helpers: 16 | //! - [`Swapchain`] to help with buffer management for framebuffers 17 | 18 | pub mod dmabuf; 19 | #[cfg(feature = "backend_drm")] 20 | pub mod dumb; 21 | pub mod format; 22 | #[cfg(feature = "backend_gbm")] 23 | pub mod gbm; 24 | #[cfg(feature = "backend_vulkan")] 25 | pub mod vulkan; 26 | 27 | mod swapchain; 28 | use std::{ 29 | cell::RefCell, 30 | rc::Rc, 31 | sync::{Arc, Mutex}, 32 | }; 33 | 34 | use crate::utils::{Buffer as BufferCoords, Size}; 35 | pub use swapchain::{Slot, Swapchain}; 36 | 37 | pub use drm_fourcc::{ 38 | DrmFormat as Format, DrmFourcc as Fourcc, DrmModifier as Modifier, DrmVendor as Vendor, 39 | UnrecognizedFourcc, UnrecognizedVendor, 40 | }; 41 | 42 | /// Common trait describing common properties of most types of buffers. 43 | pub trait Buffer { 44 | /// Width of the two-dimensional buffer 45 | fn width(&self) -> u32 { 46 | self.size().w as u32 47 | } 48 | /// Height of the two-dimensional buffer 49 | fn height(&self) -> u32 { 50 | self.size().h as u32 51 | } 52 | /// Size of the two-dimensional buffer 53 | fn size(&self) -> Size; 54 | /// Pixel format of the buffer 55 | fn format(&self) -> Format; 56 | } 57 | 58 | /// Interface to create Buffers 59 | pub trait Allocator { 60 | /// Buffer type produced by this allocator 61 | type Buffer: Buffer; 62 | /// Error type thrown if allocations fail 63 | type Error: std::error::Error; 64 | 65 | /// Try to create a buffer with the given dimensions and pixel format 66 | fn create_buffer( 67 | &mut self, 68 | width: u32, 69 | height: u32, 70 | fourcc: Fourcc, 71 | modifiers: &[Modifier], 72 | ) -> Result; 73 | } 74 | 75 | // General implementations for interior mutability. 76 | 77 | impl Allocator for Arc> { 78 | type Buffer = A::Buffer; 79 | type Error = A::Error; 80 | 81 | fn create_buffer( 82 | &mut self, 83 | width: u32, 84 | height: u32, 85 | fourcc: Fourcc, 86 | modifiers: &[Modifier], 87 | ) -> Result { 88 | let mut guard = self.lock().unwrap(); 89 | guard.create_buffer(width, height, fourcc, modifiers) 90 | } 91 | } 92 | 93 | impl Allocator for Rc> { 94 | type Buffer = A::Buffer; 95 | type Error = A::Error; 96 | 97 | fn create_buffer( 98 | &mut self, 99 | width: u32, 100 | height: u32, 101 | fourcc: Fourcc, 102 | modifiers: &[Modifier], 103 | ) -> Result { 104 | self.borrow_mut().create_buffer(width, height, fourcc, modifiers) 105 | } 106 | } 107 | 108 | impl Allocator for Box + 'static> { 109 | type Buffer = B; 110 | type Error = E; 111 | 112 | fn create_buffer( 113 | &mut self, 114 | width: u32, 115 | height: u32, 116 | fourcc: Fourcc, 117 | modifiers: &[Modifier], 118 | ) -> Result { 119 | (**self).create_buffer(width, height, fourcc, modifiers) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/backend/allocator/vulkan/format.rs: -------------------------------------------------------------------------------- 1 | //! Format conversions between Vulkan and DRM formats. 2 | 3 | /// Macro to generate format conversions between Vulkan and FourCC format codes. 4 | /// 5 | /// Any entry in this table may have attributes associated with a conversion. This is needed for `PACK` Vulkan 6 | /// formats which may only have an alternative given a specific host endian. 7 | /// 8 | /// See the module documentation for usage details. 9 | macro_rules! vk_format_table { 10 | ( 11 | $( 12 | // This meta specifier is used for format conversions for PACK formats. 13 | $(#[$conv_meta:meta])* 14 | $fourcc: ident => $vk: ident 15 | ),* $(,)? 16 | ) => { 17 | /// Converts a FourCC format code to a Vulkan format code. 18 | /// 19 | /// This will return [`None`] if the format is not known. 20 | /// 21 | /// These format conversions will return all known FourCC and Vulkan format conversions. However a 22 | /// Vulkan implementation may not support some Vulkan format. One notable example of this are the 23 | /// formats introduced in `VK_EXT_4444_formats`. The corresponding FourCC codes will return the 24 | /// formats from `VK_EXT_4444_formats`, but the caller is responsible for testing that a Vulkan device 25 | /// supports these formats. 26 | pub const fn get_vk_format(fourcc: $crate::backend::allocator::Fourcc) -> Option { 27 | // FIXME: Use reexport for ash::vk::Format 28 | match fourcc { 29 | $( 30 | $(#[$conv_meta])* 31 | $crate::backend::allocator::Fourcc::$fourcc => Some(ash::vk::Format::$vk), 32 | )* 33 | 34 | _ => None, 35 | } 36 | } 37 | 38 | /// Returns all the known format conversions. 39 | /// 40 | /// The list contains FourCC format codes that may be converted using [`get_vk_format`]. 41 | pub const fn known_formats() -> &'static [$crate::backend::allocator::Fourcc] { 42 | &[ 43 | $( 44 | $crate::backend::allocator::Fourcc::$fourcc 45 | ),* 46 | ] 47 | } 48 | }; 49 | } 50 | 51 | // FIXME: SRGB format is not always correct. 52 | // 53 | // Vulkan classifies formats by both channel sizes and colorspace. FourCC format codes do not classify formats 54 | // based on colorspace. 55 | // 56 | // To implement this correctly, it is likely that parsing vulkan.xml and classifying families of colorspaces 57 | // would be needed since there are a lot of formats. 58 | // 59 | // Many of these conversions come from wsi_common_wayland.c in Mesa 60 | vk_format_table! { 61 | Argb8888 => B8G8R8A8_SRGB, 62 | Xrgb8888 => B8G8R8A8_SRGB, 63 | 64 | Abgr8888 => R8G8B8A8_SRGB, 65 | Xbgr8888 => R8G8B8A8_SRGB, 66 | 67 | // PACK32 formats are equivalent to u32 instead of [u8; 4] and thus depend their layout depends the host 68 | // endian. 69 | #[cfg(target_endian = "little")] 70 | Rgba8888 => A8B8G8R8_SRGB_PACK32, 71 | #[cfg(target_endian = "little")] 72 | Rgbx8888 => A8B8G8R8_SRGB_PACK32, 73 | 74 | #[cfg(target_endian = "little")] 75 | Argb2101010 => A2R10G10B10_UNORM_PACK32, 76 | #[cfg(target_endian = "little")] 77 | Xrgb2101010 => A2R10G10B10_UNORM_PACK32, 78 | 79 | #[cfg(target_endian = "little")] 80 | Abgr2101010 => A2B10G10R10_UNORM_PACK32, 81 | #[cfg(target_endian = "little")] 82 | Xbgr2101010 => A2B10G10R10_UNORM_PACK32, 83 | } 84 | -------------------------------------------------------------------------------- /src/backend/drm/device/fd.rs: -------------------------------------------------------------------------------- 1 | use drm::{control::Device as ControlDevice, Device as BasicDevice}; 2 | use std::{ 3 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}, 4 | sync::{Arc, Weak}, 5 | }; 6 | use tracing::{error, info, warn}; 7 | 8 | use crate::utils::{DevPath, DeviceFd}; 9 | 10 | #[derive(Debug)] 11 | struct InternalDrmDeviceFd { 12 | fd: DeviceFd, 13 | privileged: bool, 14 | } 15 | 16 | impl PartialEq for InternalDrmDeviceFd { 17 | fn eq(&self, other: &Self) -> bool { 18 | self.fd == other.fd 19 | } 20 | } 21 | 22 | impl Drop for InternalDrmDeviceFd { 23 | fn drop(&mut self) { 24 | info!("Dropping device: {:?}", self.fd.dev_path()); 25 | if self.privileged { 26 | if let Err(err) = self.release_master_lock() { 27 | error!("Failed to drop drm master state. Error: {}", err); 28 | } 29 | } 30 | } 31 | } 32 | 33 | impl AsFd for InternalDrmDeviceFd { 34 | fn as_fd(&self) -> BorrowedFd<'_> { 35 | self.fd.as_fd() 36 | } 37 | } 38 | impl BasicDevice for InternalDrmDeviceFd {} 39 | impl ControlDevice for InternalDrmDeviceFd {} 40 | 41 | /// Ref-counted file descriptor of an open drm device 42 | #[derive(Debug, Clone, PartialEq)] 43 | pub struct DrmDeviceFd(Arc); 44 | 45 | impl AsFd for DrmDeviceFd { 46 | fn as_fd(&self) -> BorrowedFd<'_> { 47 | self.0.fd.as_fd() 48 | } 49 | } 50 | 51 | // TODO: drop impl once not needed anymore by smithay or dependencies 52 | impl AsRawFd for DrmDeviceFd { 53 | fn as_raw_fd(&self) -> RawFd { 54 | self.0.fd.as_raw_fd() 55 | } 56 | } 57 | 58 | impl DrmDeviceFd { 59 | /// Create a new `DrmDeviceFd`. 60 | /// 61 | /// This function will try to acquire the master lock for the underlying drm device 62 | /// and release the lock on drop again. 63 | /// For that reason you should never create multiple `DrmDeviceFd` out of the same 64 | /// `DeviceFd`, but instead clone the `DrmDeviceFd`. 65 | /// 66 | /// Failing to do so might fail to acquire set lock and release it early, 67 | /// which can cause some drm ioctls to fail later. 68 | pub fn new(fd: DeviceFd) -> DrmDeviceFd { 69 | let mut dev = InternalDrmDeviceFd { 70 | fd, 71 | privileged: false, 72 | }; 73 | 74 | // We want to modeset, so we better be the master, if we run via a tty session. 75 | // This is only needed on older kernels. Newer kernels grant this permission, 76 | // if no other process is already the *master*. So we skip over this error. 77 | if dev.acquire_master_lock().is_err() { 78 | warn!("Unable to become drm master, assuming unprivileged mode"); 79 | } else { 80 | dev.privileged = true; 81 | } 82 | 83 | DrmDeviceFd(Arc::new(dev)) 84 | } 85 | 86 | pub(in crate::backend::drm) fn is_privileged(&self) -> bool { 87 | self.0.privileged 88 | } 89 | 90 | /// Returns the underlying `DeviceFd` 91 | pub fn device_fd(&self) -> DeviceFd { 92 | self.0.fd.clone() 93 | } 94 | 95 | /// Returns the `dev_t` of the underlying device 96 | pub fn dev_id(&self) -> rustix::io::Result { 97 | Ok(rustix::fs::fstat(&self.0.fd)?.st_rdev) 98 | } 99 | 100 | /// Returns a weak reference to the underlying device 101 | pub fn downgrade(&self) -> WeakDrmDeviceFd { 102 | WeakDrmDeviceFd(Arc::downgrade(&self.0)) 103 | } 104 | } 105 | 106 | impl BasicDevice for DrmDeviceFd {} 107 | impl ControlDevice for DrmDeviceFd {} 108 | 109 | /// Weak variant of [`DrmDeviceFd`] 110 | #[derive(Debug, Clone, Default)] 111 | pub struct WeakDrmDeviceFd(Weak); 112 | 113 | impl WeakDrmDeviceFd { 114 | /// Construct an empty Weak reference, that will never upgrade successfully 115 | pub fn new() -> Self { 116 | WeakDrmDeviceFd(Weak::new()) 117 | } 118 | 119 | /// Try to upgrade to a strong reference 120 | pub fn upgrade(&self) -> Option { 121 | self.0.upgrade().map(DrmDeviceFd) 122 | } 123 | } 124 | 125 | impl PartialEq for WeakDrmDeviceFd { 126 | fn eq(&self, other: &Self) -> bool { 127 | Weak::ptr_eq(&self.0, &other.0) 128 | } 129 | } 130 | 131 | impl PartialEq for WeakDrmDeviceFd { 132 | fn eq(&self, other: &DrmDeviceFd) -> bool { 133 | Weak::upgrade(&self.0).is_some_and(|arc| Arc::ptr_eq(&arc, &other.0)) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/backend/drm/exporter/dumb.rs: -------------------------------------------------------------------------------- 1 | //! Implementation for ExportFramebuffer and related utilizies for dumb buffers 2 | 3 | use thiserror::Error; 4 | 5 | use super::{ExportBuffer, ExportFramebuffer}; 6 | use crate::backend::{ 7 | allocator::dumb::DumbBuffer, 8 | drm::{ 9 | dumb::{framebuffer_from_dumb_buffer, DumbFramebuffer}, 10 | error::AccessError, 11 | DrmDeviceFd, 12 | }, 13 | }; 14 | 15 | /// Possible errors for attaching a [`framebuffer::Handle`](::drm::control::framebuffer::Handle) 16 | #[derive(Error, Debug)] 17 | pub enum Error { 18 | /// The buffer is not supported 19 | #[error("Unsupported buffer supplied")] 20 | Unsupported, 21 | /// Failed to add a framebuffer for the dumb buffer 22 | #[error("failed to add a framebuffer for the dumb buffer")] 23 | Drm(AccessError), 24 | } 25 | 26 | impl ExportFramebuffer for DrmDeviceFd { 27 | type Framebuffer = DumbFramebuffer; 28 | type Error = Error; 29 | 30 | #[profiling::function] 31 | fn add_framebuffer( 32 | &self, 33 | _drm: &DrmDeviceFd, 34 | buffer: ExportBuffer<'_, DumbBuffer>, 35 | use_opaque: bool, 36 | ) -> Result, Self::Error> { 37 | match buffer { 38 | #[cfg(feature = "wayland_frontend")] 39 | ExportBuffer::Wayland(_) => return Err(Error::Unsupported), 40 | ExportBuffer::Allocator(buffer) => framebuffer_from_dumb_buffer(self, buffer, use_opaque) 41 | .map_err(Error::Drm) 42 | .map(Some), 43 | } 44 | } 45 | 46 | #[inline] 47 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, DumbBuffer>) -> bool { 48 | match buffer { 49 | #[cfg(feature = "wayland_frontend")] 50 | ExportBuffer::Wayland(_) => false, 51 | ExportBuffer::Allocator(_) => true, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/backend/drm/exporter/gbm.rs: -------------------------------------------------------------------------------- 1 | //! Implementation for ExportFramebuffer and related utilizes for gbm types 2 | 3 | use std::os::unix::io::AsFd; 4 | 5 | use drm::node::DrmNode; 6 | 7 | use super::{ExportBuffer, ExportFramebuffer}; 8 | #[cfg(feature = "wayland_frontend")] 9 | use crate::backend::drm::gbm::framebuffer_from_wayland_buffer; 10 | use crate::backend::{ 11 | allocator::{ 12 | dmabuf::AsDmabuf, 13 | gbm::{GbmBuffer, GbmConvertError}, 14 | }, 15 | drm::{ 16 | gbm::{framebuffer_from_bo, framebuffer_from_dmabuf, Error as GbmError, GbmFramebuffer}, 17 | DrmDeviceFd, 18 | }, 19 | }; 20 | 21 | /// Error for [`GbmFramebufferExporter`] 22 | #[derive(Debug, thiserror::Error)] 23 | pub enum Error { 24 | /// Exporting the [`GbmBuffer`] as a [`Dmabuf`](crate::backend::allocator::dmabuf::Dmabuf) failed 25 | #[error(transparent)] 26 | Dmabuf(#[from] GbmConvertError), 27 | /// Exporting the [`GbmBuffer`] failed 28 | #[error(transparent)] 29 | Gbm(#[from] GbmError), 30 | } 31 | 32 | /// Export framebuffers based on [`gbm::Device`] 33 | #[derive(Debug, Clone)] 34 | pub struct GbmFramebufferExporter { 35 | gbm: gbm::Device, 36 | drm_node: Option, 37 | } 38 | 39 | impl GbmFramebufferExporter { 40 | /// Initialize a new framebuffer exporter 41 | pub fn new(gbm: gbm::Device) -> Self { 42 | let drm_node = DrmNode::from_file(gbm.as_fd()).ok(); 43 | Self { gbm, drm_node } 44 | } 45 | } 46 | 47 | impl ExportFramebuffer for GbmFramebufferExporter { 48 | type Framebuffer = GbmFramebuffer; 49 | type Error = Error; 50 | 51 | #[profiling::function] 52 | fn add_framebuffer( 53 | &self, 54 | drm: &DrmDeviceFd, 55 | buffer: ExportBuffer<'_, GbmBuffer>, 56 | use_opaque: bool, 57 | ) -> Result, Self::Error> { 58 | let framebuffer = match buffer { 59 | #[cfg(feature = "wayland_frontend")] 60 | ExportBuffer::Wayland(wl_buffer) => { 61 | framebuffer_from_wayland_buffer(drm, &self.gbm, wl_buffer, use_opaque)? 62 | } 63 | ExportBuffer::Allocator(buffer) => { 64 | let foreign = self.drm_node.is_none() 65 | || buffer.device_node().is_none() 66 | || self.drm_node != buffer.device_node(); 67 | if foreign { 68 | tracing::debug!("importing foreign buffer"); 69 | let dmabuf = buffer.export()?; 70 | framebuffer_from_dmabuf(drm, &self.gbm, &dmabuf, use_opaque, true).map(Some)? 71 | } else { 72 | framebuffer_from_bo(drm, buffer, use_opaque) 73 | .map_err(GbmError::Drm) 74 | .map(Some)? 75 | } 76 | } 77 | }; 78 | Ok(framebuffer) 79 | } 80 | 81 | #[inline] 82 | #[cfg(feature = "wayland_frontend")] 83 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool { 84 | match buffer { 85 | #[cfg(not(all(feature = "backend_egl", feature = "use_system_lib")))] 86 | ExportBuffer::Wayland(buffer) => matches!( 87 | crate::backend::renderer::buffer_type(buffer), 88 | Some(crate::backend::renderer::BufferType::Dma) 89 | ), 90 | #[cfg(all(feature = "backend_egl", feature = "use_system_lib"))] 91 | ExportBuffer::Wayland(buffer) => matches!( 92 | crate::backend::renderer::buffer_type(buffer), 93 | Some(crate::backend::renderer::BufferType::Dma) 94 | | Some(crate::backend::renderer::BufferType::Egl) 95 | ), 96 | ExportBuffer::Allocator(_) => true, 97 | } 98 | } 99 | 100 | #[inline] 101 | #[cfg(not(feature = "wayland_frontend"))] 102 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool { 103 | match buffer { 104 | ExportBuffer::Allocator(_) => true, 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/backend/drm/exporter/mod.rs: -------------------------------------------------------------------------------- 1 | //! Trait and data structures to describe types, that can be exported to a drm framebuffer 2 | 3 | use std::{ 4 | cell::RefCell, 5 | rc::Rc, 6 | sync::{Arc, Mutex}, 7 | }; 8 | 9 | #[cfg(feature = "wayland_frontend")] 10 | use wayland_server::protocol::wl_buffer::WlBuffer; 11 | 12 | use crate::backend::{allocator::Buffer, renderer::element::UnderlyingStorage}; 13 | 14 | use super::{DrmDeviceFd, Framebuffer}; 15 | 16 | #[cfg(feature = "backend_drm")] 17 | pub mod dumb; 18 | #[cfg(feature = "backend_gbm")] 19 | pub mod gbm; 20 | 21 | /// Possible buffers to export as a framebuffer using [`ExportFramebuffer`] 22 | #[derive(Debug)] 23 | pub enum ExportBuffer<'a, B: Buffer> { 24 | /// A wayland buffer 25 | #[cfg(feature = "wayland_frontend")] 26 | Wayland(&'a WlBuffer), 27 | /// A [`Allocator`][crate::backend::allocator::Allocator] buffer 28 | Allocator(&'a B), 29 | } 30 | 31 | impl<'a, B: Buffer> ExportBuffer<'a, B> { 32 | /// Create the export buffer from an [`UnderlyingStorage`] 33 | #[inline] 34 | pub fn from_underlying_storage(storage: &'a UnderlyingStorage<'_>) -> Option { 35 | match storage { 36 | #[cfg(feature = "wayland_frontend")] 37 | UnderlyingStorage::Wayland(buffer) => Some(Self::Wayland(buffer)), 38 | UnderlyingStorage::Memory { .. } => None, 39 | } 40 | } 41 | } 42 | 43 | /// Export a [`ExportBuffer`] as a framebuffer 44 | pub trait ExportFramebuffer 45 | where 46 | B: Buffer, 47 | { 48 | /// Type of the framebuffer 49 | type Framebuffer: Framebuffer; 50 | 51 | /// Type of the error 52 | type Error: std::error::Error; 53 | 54 | /// Add a framebuffer for the specified buffer 55 | fn add_framebuffer( 56 | &self, 57 | drm: &DrmDeviceFd, 58 | buffer: ExportBuffer<'_, B>, 59 | use_opaque: bool, 60 | ) -> Result, Self::Error>; 61 | 62 | /// Test if the provided buffer is eligible for adding a framebuffer 63 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool; 64 | } 65 | 66 | impl ExportFramebuffer for Arc> 67 | where 68 | F: ExportFramebuffer, 69 | B: Buffer, 70 | { 71 | type Framebuffer = >::Framebuffer; 72 | type Error = >::Error; 73 | 74 | #[inline] 75 | fn add_framebuffer( 76 | &self, 77 | drm: &DrmDeviceFd, 78 | buffer: ExportBuffer<'_, B>, 79 | use_opaque: bool, 80 | ) -> Result, Self::Error> { 81 | let guard = self.lock().unwrap(); 82 | guard.add_framebuffer(drm, buffer, use_opaque) 83 | } 84 | 85 | #[inline] 86 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { 87 | let guard = self.lock().unwrap(); 88 | guard.can_add_framebuffer(buffer) 89 | } 90 | } 91 | 92 | impl ExportFramebuffer for Rc> 93 | where 94 | F: ExportFramebuffer, 95 | B: Buffer, 96 | { 97 | type Framebuffer = >::Framebuffer; 98 | type Error = >::Error; 99 | 100 | #[inline] 101 | fn add_framebuffer( 102 | &self, 103 | drm: &DrmDeviceFd, 104 | buffer: ExportBuffer<'_, B>, 105 | use_opaque: bool, 106 | ) -> Result, Self::Error> { 107 | self.borrow().add_framebuffer(drm, buffer, use_opaque) 108 | } 109 | 110 | #[inline] 111 | fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool { 112 | self.borrow().can_add_framebuffer(buffer) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/backend/renderer/color.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Mul; 2 | 3 | /// A four-component color representing pre-multiplied RGBA color values 4 | #[derive(Debug, Copy, Clone, Default, PartialEq)] 5 | pub struct Color32F([f32; 4]); 6 | 7 | impl Color32F { 8 | /// Initialize a new [`Color32F`] 9 | #[inline] 10 | pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { 11 | Self([r, g, b, a]) 12 | } 13 | } 14 | 15 | impl Color32F { 16 | /// Transparent color 17 | pub const TRANSPARENT: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.0); 18 | 19 | /// Solid black color 20 | pub const BLACK: Color32F = Color32F::new(0f32, 0f32, 0f32, 1f32); 21 | } 22 | 23 | impl Color32F { 24 | /// Red color component 25 | #[inline] 26 | pub fn r(&self) -> f32 { 27 | self.0[0] 28 | } 29 | 30 | /// Green color component 31 | #[inline] 32 | pub fn g(&self) -> f32 { 33 | self.0[1] 34 | } 35 | 36 | /// Blue color component 37 | #[inline] 38 | pub fn b(&self) -> f32 { 39 | self.0[2] 40 | } 41 | 42 | /// Alpha color component 43 | #[inline] 44 | pub fn a(&self) -> f32 { 45 | self.0[3] 46 | } 47 | 48 | /// Color components 49 | #[inline] 50 | pub fn components(self) -> [f32; 4] { 51 | self.0 52 | } 53 | } 54 | 55 | impl Color32F { 56 | /// Test if the color represents a opaque color 57 | #[inline] 58 | pub fn is_opaque(&self) -> bool { 59 | self.a() == 1f32 60 | } 61 | } 62 | 63 | impl From<[f32; 4]> for Color32F { 64 | #[inline] 65 | fn from(value: [f32; 4]) -> Self { 66 | Self(value) 67 | } 68 | } 69 | 70 | impl Mul for Color32F { 71 | type Output = Color32F; 72 | 73 | #[inline] 74 | fn mul(self, rhs: f32) -> Self::Output { 75 | Self::new(self.r() * rhs, self.g() * rhs, self.b() * rhs, self.a() * rhs) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/backend/renderer/element/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities and helpers around the `Element` trait. 2 | 3 | mod elements; 4 | #[cfg(feature = "wayland_frontend")] 5 | mod wayland; 6 | 7 | pub use elements::*; 8 | #[cfg(feature = "wayland_frontend")] 9 | pub use wayland::*; 10 | -------------------------------------------------------------------------------- /src/backend/renderer/element/utils/wayland.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backend::renderer::element::{Id, RenderElementPresentationState, RenderElementStates, RenderingReason}, 3 | wayland::dmabuf::DmabufFeedback, 4 | }; 5 | 6 | /// Select a [`DmabufFeedback`] based on the [`RenderElementPresentationState`] for a single [`Element`](crate::backend::renderer::element::Element) 7 | /// 8 | /// Returns the provided scan-out feedback if the element has been successfully assigned for scan-out or 9 | /// was selected for scan-out but failed the scan-out test. 10 | /// Otherwise the provided default feedback is returned. 11 | pub fn select_dmabuf_feedback<'a>( 12 | element: impl Into, 13 | render_element_states: &RenderElementStates, 14 | default_feedback: &'a DmabufFeedback, 15 | scanout_feedback: &'a DmabufFeedback, 16 | ) -> &'a DmabufFeedback { 17 | let id = element.into(); 18 | 19 | let Some(state) = render_element_states.element_render_state(id) else { 20 | return default_feedback; 21 | }; 22 | 23 | match state.presentation_state { 24 | RenderElementPresentationState::Rendering { reason } => match reason { 25 | Some(RenderingReason::FormatUnsupported) | Some(RenderingReason::ScanoutFailed) => { 26 | scanout_feedback 27 | } 28 | None => default_feedback, 29 | }, 30 | RenderElementPresentationState::ZeroCopy => scanout_feedback, 31 | RenderElementPresentationState::Skipped => default_feedback, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/backend/renderer/gles/format.rs: -------------------------------------------------------------------------------- 1 | //! GL color format conversion helpers 2 | 3 | use super::ffi::{self, types::GLenum}; 4 | use crate::backend::allocator::{ 5 | format::{get_transparent, has_alpha}, 6 | Fourcc, 7 | }; 8 | 9 | /// Returns (internal_format, read_format, type) 10 | pub const fn fourcc_to_gl_formats(value: Fourcc) -> Option<(GLenum, GLenum, GLenum)> { 11 | let Some(value) = (if has_alpha(value) { 12 | Some(value) 13 | } else { 14 | get_transparent(value) 15 | }) else { 16 | return None; // ? not allowed in const fn 17 | }; 18 | 19 | match value { 20 | Fourcc::Abgr8888 => Some((ffi::RGBA8, ffi::RGBA, ffi::UNSIGNED_BYTE)), 21 | Fourcc::Argb8888 => Some((ffi::BGRA_EXT, ffi::BGRA_EXT, ffi::UNSIGNED_BYTE)), 22 | Fourcc::Abgr2101010 => Some((ffi::RGB10_A2, ffi::RGBA, ffi::UNSIGNED_INT_2_10_10_10_REV)), 23 | Fourcc::Abgr16161616f => Some((ffi::RGBA16F, ffi::RGBA, ffi::HALF_FLOAT)), 24 | _ => None, 25 | } 26 | } 27 | 28 | /// Returns the fourcc for a given internal format 29 | pub const fn gl_internal_format_to_fourcc(format: GLenum) -> Option { 30 | match format { 31 | ffi::RGBA | ffi::RGBA8 => Some(Fourcc::Abgr8888), 32 | ffi::BGRA_EXT => Some(Fourcc::Argb8888), 33 | ffi::RGB8 => Some(Fourcc::Bgr888), 34 | ffi::RGB10_A2 => Some(Fourcc::Abgr2101010), 35 | ffi::RGBA16F => Some(Fourcc::Abgr16161616f), 36 | _ => None, 37 | } 38 | } 39 | 40 | /// Returns the fourcc for a given read format and type 41 | pub const fn gl_read_format_to_fourcc(format: GLenum, type_: GLenum) -> Option { 42 | match (format, type_) { 43 | (ffi::RGBA, ffi::UNSIGNED_BYTE) => Some(Fourcc::Abgr8888), 44 | (ffi::BGRA_EXT, ffi::UNSIGNED_BYTE) => Some(Fourcc::Argb8888), 45 | (ffi::RGB, ffi::UNSIGNED_BYTE) => Some(Fourcc::Bgr888), 46 | (ffi::RGBA, ffi::UNSIGNED_INT_2_10_10_10_REV) => Some(Fourcc::Abgr2101010), 47 | (ffi::RGBA, ffi::HALF_FLOAT) => Some(Fourcc::Abgr16161616f), 48 | _ => None, 49 | } 50 | } 51 | 52 | /// Returns a recommended read format and type for a given internal format 53 | pub const fn gl_read_for_internal(format: GLenum) -> Option<(GLenum, GLenum)> { 54 | match format { 55 | ffi::RGBA | ffi::RGBA8 => Some((ffi::RGBA, ffi::UNSIGNED_BYTE)), 56 | ffi::BGRA_EXT => Some((ffi::BGRA_EXT, ffi::UNSIGNED_BYTE)), 57 | ffi::RGB8 => Some((ffi::RGB, ffi::UNSIGNED_BYTE)), 58 | ffi::RGB10_A2 => Some((ffi::RGBA, ffi::UNSIGNED_INT_2_10_10_10_REV)), 59 | ffi::RGBA16F => Some((ffi::RGBA, ffi::HALF_FLOAT)), 60 | _ => None, 61 | } 62 | } 63 | 64 | /// Returns the bits per pixel for a given read format and type 65 | pub const fn gl_bpp(format: GLenum, type_: GLenum) -> Option { 66 | match (format, type_) { 67 | (ffi::RGB, ffi::UNSIGNED_BYTE) => Some(24), 68 | (ffi::RGBA, ffi::UNSIGNED_BYTE) 69 | | (ffi::BGRA_EXT, ffi::UNSIGNED_BYTE) 70 | | (ffi::RGBA, ffi::UNSIGNED_INT_2_10_10_10_REV) => Some(32), 71 | (ffi::RGBA, ffi::HALF_FLOAT) => Some(64), 72 | _ => None, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/backend/renderer/gles/shaders/implicit/solid.frag: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | precision mediump float; 4 | uniform vec4 color; 5 | 6 | void main() { 7 | gl_FragColor = color; 8 | } -------------------------------------------------------------------------------- /src/backend/renderer/gles/shaders/implicit/solid.vert: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | uniform mat3 matrix; 4 | attribute vec2 vert; 5 | attribute vec4 position; 6 | 7 | mat2 scale(vec2 scale_vec){ 8 | return mat2( 9 | scale_vec.x, 0.0, 10 | 0.0, scale_vec.y 11 | ); 12 | } 13 | 14 | void main() { 15 | vec2 transform_translation = position.xy; 16 | vec2 transform_scale = position.zw; 17 | vec3 position = vec3(vert * scale(transform_scale) + transform_translation, 1.0); 18 | gl_Position = vec4(matrix * position, 1.0); 19 | } -------------------------------------------------------------------------------- /src/backend/renderer/gles/shaders/implicit/texture.frag: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | //_DEFINES_ 4 | 5 | #if defined(EXTERNAL) 6 | #extension GL_OES_EGL_image_external : require 7 | #endif 8 | 9 | precision mediump float; 10 | #if defined(EXTERNAL) 11 | uniform samplerExternalOES tex; 12 | #else 13 | uniform sampler2D tex; 14 | #endif 15 | 16 | uniform float alpha; 17 | varying vec2 v_coords; 18 | 19 | #if defined(DEBUG_FLAGS) 20 | uniform float tint; 21 | #endif 22 | 23 | void main() { 24 | vec4 color = texture2D(tex, v_coords); 25 | 26 | #if defined(NO_ALPHA) 27 | color = vec4(color.rgb, 1.0) * alpha; 28 | #else 29 | color = color * alpha; 30 | #endif 31 | 32 | #if defined(DEBUG_FLAGS) 33 | if (tint == 1.0) 34 | color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8; 35 | #endif 36 | 37 | gl_FragColor = color; 38 | } -------------------------------------------------------------------------------- /src/backend/renderer/gles/shaders/implicit/texture.vert: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | uniform mat3 matrix; 4 | uniform mat3 tex_matrix; 5 | 6 | attribute vec2 vert; 7 | attribute vec4 vert_position; 8 | 9 | varying vec2 v_coords; 10 | 11 | mat2 scale(vec2 scale_vec){ 12 | return mat2( 13 | scale_vec.x, 0.0, 14 | 0.0, scale_vec.y 15 | ); 16 | } 17 | 18 | void main() { 19 | vec2 vert_transform_translation = vert_position.xy; 20 | vec2 vert_transform_scale = vert_position.zw; 21 | vec3 position = vec3(vert * scale(vert_transform_scale) + vert_transform_translation, 1.0); 22 | v_coords = (tex_matrix * position).xy; 23 | gl_Position = vec4(matrix * position, 1.0); 24 | } -------------------------------------------------------------------------------- /src/backend/renderer/gles/version.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | os::raw::c_char, 4 | }; 5 | 6 | use super::ffi::{self, Gles2}; 7 | 8 | pub const GLES_3_0: GlVersion = GlVersion::new(3, 0); 9 | pub const GLES_2_0: GlVersion = GlVersion::new(2, 0); 10 | 11 | #[derive(Debug, PartialEq, Clone, Copy)] 12 | pub struct GlVersion { 13 | pub major: i32, 14 | pub minor: i32, 15 | } 16 | 17 | impl GlVersion { 18 | pub const fn new(major: i32, minor: i32) -> Self { 19 | GlVersion { major, minor } 20 | } 21 | } 22 | 23 | impl Eq for GlVersion {} 24 | 25 | impl Ord for GlVersion { 26 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 27 | match self.major.cmp(&other.major) { 28 | std::cmp::Ordering::Equal => self.minor.cmp(&other.minor), 29 | ord => ord, 30 | } 31 | } 32 | } 33 | 34 | impl PartialOrd for GlVersion { 35 | fn partial_cmp(&self, other: &Self) -> Option { 36 | Some(self.cmp(other)) 37 | } 38 | } 39 | 40 | #[derive(Debug, thiserror::Error)] 41 | #[error("Invalid version string {0:?}")] 42 | pub struct GlVersionParseError(CString); 43 | 44 | impl TryFrom<&CStr> for GlVersion { 45 | type Error = GlVersionParseError; 46 | 47 | fn try_from(value: &CStr) -> Result { 48 | let mut bytes = value.to_bytes(); 49 | 50 | let prefix = b"OpenGL ES "; 51 | if bytes.starts_with(prefix) { 52 | // Strip the prefix 53 | bytes = &bytes[prefix.len()..]; 54 | } 55 | 56 | let ascii_to_int = |ch: u8| (ch - b'0') as u32; 57 | 58 | let mut iter = bytes.iter(); 59 | 60 | let mut major: Option = None; 61 | let mut minor: Option = None; 62 | 63 | for v in &mut iter { 64 | if v.is_ascii_digit() { 65 | major = Some(major.unwrap_or(0) * 10 + ascii_to_int(*v)); 66 | } else if *v == b'.' { 67 | break; 68 | } else { 69 | // Neither digit nor '.', so let's assume invalid string 70 | return Err(GlVersionParseError(value.to_owned())); 71 | } 72 | } 73 | 74 | for v in iter { 75 | if v.is_ascii_digit() { 76 | minor = Some(minor.unwrap_or(0) * 10 + ascii_to_int(*v)); 77 | } else { 78 | break; 79 | } 80 | } 81 | 82 | major 83 | .zip(minor) 84 | .map(|(major, minor)| GlVersion::new(major as i32, minor as i32)) 85 | .ok_or_else(|| GlVersionParseError(value.to_owned())) 86 | } 87 | } 88 | 89 | impl TryFrom<&Gles2> for GlVersion { 90 | type Error = GlVersionParseError; 91 | 92 | fn try_from(value: &Gles2) -> Result { 93 | let version = unsafe { CStr::from_ptr(value.GetString(ffi::VERSION) as *const c_char) }; 94 | GlVersion::try_from(version) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::GlVersion; 101 | use std::{convert::TryFrom, ffi::CStr, os::raw::c_char}; 102 | 103 | #[test] 104 | fn test_parse_1234_4321() { 105 | let gl_version = b"1234.4321 Mesa 20.3.5\0"; 106 | let gl_version_str = unsafe { CStr::from_ptr(gl_version.as_ptr() as *const c_char) }; 107 | assert_eq!( 108 | GlVersion::try_from(gl_version_str).unwrap(), 109 | GlVersion::new(1234, 4321) 110 | ) 111 | } 112 | 113 | #[test] 114 | fn test_parse_mesa_3_2() { 115 | let gl_version = b"OpenGL ES 3.2 Mesa 20.3.5\0"; 116 | let gl_version_str = unsafe { CStr::from_ptr(gl_version.as_ptr() as *const c_char) }; 117 | assert_eq!(GlVersion::try_from(gl_version_str).unwrap(), GlVersion::new(3, 2)) 118 | } 119 | 120 | #[test] 121 | fn test_3_2_greater_3_0() { 122 | assert!(GlVersion::new(3, 2) > GlVersion::new(3, 0)) 123 | } 124 | 125 | #[test] 126 | fn test_3_0_greater_or_equal_3_0() { 127 | assert!(GlVersion::new(3, 0) >= GlVersion::new(3, 0)) 128 | } 129 | 130 | #[test] 131 | fn test_3_0_less_or_equal_3_0() { 132 | assert!(GlVersion::new(3, 0) <= GlVersion::new(3, 0)) 133 | } 134 | 135 | #[test] 136 | fn test_3_0_eq_3_0() { 137 | assert!(GlVersion::new(3, 0) == GlVersion::new(3, 0)) 138 | } 139 | 140 | #[test] 141 | fn test_2_0_less_3_0() { 142 | assert!(GlVersion::new(2, 0) < GlVersion::new(3, 0)) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/backend/renderer/pixman/error.rs: -------------------------------------------------------------------------------- 1 | use drm_fourcc::{DrmFourcc, DrmModifier}; 2 | use thiserror::Error; 3 | 4 | use crate::backend::{ 5 | allocator::dmabuf::{DmabufMappingFailed, DmabufSyncFailed}, 6 | SwapBuffersError, 7 | }; 8 | 9 | #[cfg(feature = "wayland_frontend")] 10 | use wayland_server::protocol::wl_shm; 11 | 12 | /// Error returned during rendering using pixman 13 | #[derive(Debug, Error)] 14 | pub enum PixmanError { 15 | /// The given buffer has an unsupported number of planes 16 | #[error("Unsupported number of planes")] 17 | UnsupportedNumberOfPlanes, 18 | /// The given buffer has an unsupported pixel format 19 | #[error("Unsupported pixel format: {0:?}")] 20 | UnsupportedPixelFormat(DrmFourcc), 21 | /// The given buffer has an unsupported modifier 22 | #[error("Unsupported modifier: {0:?}")] 23 | UnsupportedModifier(DrmModifier), 24 | /// The given wl buffer has an unsupported pixel format 25 | #[error("Unsupported wl_shm format: {0:?}")] 26 | #[cfg(feature = "wayland_frontend")] 27 | UnsupportedWlPixelFormat(wl_shm::Format), 28 | /// The given buffer is incomplete 29 | #[error("Incomplete buffer {expected} < {actual}")] 30 | IncompleteBuffer { 31 | /// Expected len of the buffer 32 | expected: usize, 33 | /// Actual len of the buffer 34 | actual: usize, 35 | }, 36 | /// The given buffer was not accessible 37 | #[error("Error accessing the buffer ({0:?})")] 38 | #[cfg(feature = "wayland_frontend")] 39 | BufferAccessError(#[from] crate::wayland::shm::BufferAccessError), 40 | /// Failed to import the given buffer 41 | #[error("Import failed")] 42 | ImportFailed, 43 | /// The underlying buffer has been destroyed 44 | #[error("The underlying buffer has been destroyed")] 45 | BufferDestroyed, 46 | /// Mapping the buffer failed 47 | #[error("Mapping the buffer failed: {0}")] 48 | Map(#[from] DmabufMappingFailed), 49 | /// Synchronizing access to the buffer failed 50 | #[error("Synchronizing buffer failed: {0}")] 51 | Sync(#[from] DmabufSyncFailed), 52 | /// The requested operation failed 53 | #[error("The requested operation failed")] 54 | Failed(#[from] pixman::OperationFailed), 55 | /// No target is currently bound 56 | #[error("No target is currently bound")] 57 | NoTargetBound, 58 | /// The requested operation is not supported 59 | #[error("The requested operation is not supported")] 60 | Unsupported, 61 | /// Blocking for a synchronization primitive failed 62 | #[error("Blocking for a synchronization primitive got interrupted")] 63 | SyncInterrupted, 64 | } 65 | 66 | impl From for SwapBuffersError { 67 | #[inline] 68 | fn from(value: PixmanError) -> Self { 69 | match value { 70 | x @ PixmanError::SyncInterrupted => SwapBuffersError::TemporaryFailure(Box::new(x)), 71 | x => SwapBuffersError::ContextLost(Box::new(x)), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/backend/renderer/sync/egl.rs: -------------------------------------------------------------------------------- 1 | use std::{os::unix::io::OwnedFd, time::Duration}; 2 | 3 | use crate::backend::{ 4 | egl::fence::EGLFence, 5 | renderer::sync::{Fence, Interrupted}, 6 | }; 7 | 8 | impl Fence for EGLFence { 9 | fn wait(&self) -> Result<(), Interrupted> { 10 | self.client_wait(None, false).map(|_| ()).map_err(|err| { 11 | tracing::warn!(?err, "Waiting for fence was interrupted"); 12 | Interrupted 13 | }) 14 | } 15 | 16 | fn is_exportable(&self) -> bool { 17 | self.is_native() 18 | } 19 | 20 | fn export(&self) -> Option { 21 | self.export().ok() 22 | } 23 | 24 | fn is_signaled(&self) -> bool { 25 | self.client_wait(Some(Duration::ZERO), false).unwrap_or(false) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/backend/renderer/sync/mod.rs: -------------------------------------------------------------------------------- 1 | //! Helper for synchronizing rendering operations 2 | use std::{error::Error, fmt, os::unix::io::OwnedFd, sync::Arc}; 3 | 4 | use downcast_rs::{impl_downcast, Downcast}; 5 | 6 | #[cfg(feature = "backend_egl")] 7 | mod egl; 8 | 9 | /// Waiting for the fence was interrupted for an unknown reason. 10 | /// 11 | /// This does not mean that the fence is signalled or not, neither that 12 | /// any timeout was reached. Waiting should be attempted again. 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 14 | pub struct Interrupted; 15 | 16 | impl fmt::Display for Interrupted { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | f.write_str("Wait for Fence was interrupted") 19 | } 20 | } 21 | impl Error for Interrupted {} 22 | 23 | /// A fence that will be signaled in finite time 24 | pub trait Fence: std::fmt::Debug + Send + Sync + Downcast { 25 | /// Queries the state of the fence 26 | fn is_signaled(&self) -> bool; 27 | 28 | /// Blocks the current thread until the fence is signaled 29 | fn wait(&self) -> Result<(), Interrupted>; 30 | 31 | /// Returns whether this fence can be exported 32 | /// as a native fence fd 33 | fn is_exportable(&self) -> bool; 34 | 35 | /// Export this fence as a native fence fd 36 | fn export(&self) -> Option; 37 | } 38 | impl_downcast!(Fence); 39 | 40 | /// A sync point the will be signaled in finite time 41 | #[derive(Debug, Clone)] 42 | #[must_use = "this `SyncPoint` may contain a fence that should be awaited, failing to do so may result in unexpected rendering artifacts"] 43 | pub struct SyncPoint { 44 | fence: Option>, 45 | } 46 | 47 | impl Default for SyncPoint { 48 | fn default() -> Self { 49 | Self::signaled() 50 | } 51 | } 52 | 53 | impl SyncPoint { 54 | /// Create an already signaled sync point 55 | pub fn signaled() -> Self { 56 | Self { 57 | fence: Default::default(), 58 | } 59 | } 60 | 61 | /// Returns `true` if `SyncPoint` contains a [`Fence`] 62 | pub fn contains_fence(&self) -> bool { 63 | self.fence.is_some() 64 | } 65 | 66 | /// Get a reference to the underlying [`Fence`] if any 67 | /// 68 | /// Returns `None` if the sync point does not contain a fence 69 | /// or contains a different type of fence 70 | pub fn get(&self) -> Option<&F> { 71 | self.fence.as_ref().and_then(|f| f.downcast_ref()) 72 | } 73 | 74 | /// Queries the state of the sync point 75 | /// 76 | /// Will always return `true` in case the sync point does not contain a fence 77 | pub fn is_reached(&self) -> bool { 78 | self.fence.as_ref().map(|f| f.is_signaled()).unwrap_or(true) 79 | } 80 | 81 | /// Blocks the current thread until the sync point is signaled 82 | /// 83 | /// If the sync point does not contain a fence this will never block. 84 | #[profiling::function] 85 | pub fn wait(&self) -> Result<(), Interrupted> { 86 | if let Some(fence) = self.fence.as_ref() { 87 | fence.wait() 88 | } else { 89 | Ok(()) 90 | } 91 | } 92 | 93 | /// Returns whether this sync point can be exported as a native fence fd 94 | /// 95 | /// Will always return `false` in case the sync point does not contain a fence 96 | pub fn is_exportable(&self) -> bool { 97 | self.fence.as_ref().map(|f| f.is_exportable()).unwrap_or(false) 98 | } 99 | 100 | /// Export this [`SyncPoint`] as a native fence fd 101 | /// 102 | /// Will always return `None` in case the sync point does not contain a fence 103 | #[profiling::function] 104 | pub fn export(&self) -> Option { 105 | self.fence.as_ref().and_then(|f| f.export()) 106 | } 107 | } 108 | 109 | impl From for SyncPoint { 110 | #[inline] 111 | fn from(value: T) -> Self { 112 | SyncPoint { 113 | fence: Some(Arc::new(value)), 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/backend/vulkan/inner.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | fmt, 4 | }; 5 | 6 | use ash::{ext, vk}; 7 | 8 | use super::{version::Version, LoadError, LIBRARY}; 9 | 10 | pub struct InstanceInner { 11 | pub instance: ash::Instance, 12 | pub version: Version, 13 | pub debug_state: Option, 14 | pub span: tracing::Span, 15 | 16 | /// Enabled instance extensions. 17 | pub enabled_extensions: Vec<&'static CStr>, 18 | } 19 | 20 | // SAFETY: Destruction is externally synchronized (`InstanceInner` owns the 21 | // `Instance`, and is held by a single thread when `Drop` is called). 22 | unsafe impl Send for InstanceInner {} 23 | unsafe impl Sync for InstanceInner {} 24 | 25 | pub struct DebugState { 26 | pub debug_utils: ext::debug_utils::Instance, 27 | pub debug_messenger: vk::DebugUtilsMessengerEXT, 28 | pub span_ptr: *mut tracing::Span, 29 | } 30 | 31 | impl fmt::Debug for InstanceInner { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | f.debug_struct("InstanceInner") 34 | .field("instance", &self.instance.handle()) 35 | .finish_non_exhaustive() 36 | } 37 | } 38 | 39 | impl Drop for InstanceInner { 40 | fn drop(&mut self) { 41 | let span = if let Some(debug) = &self.debug_state { 42 | unsafe { 43 | debug 44 | .debug_utils 45 | .destroy_debug_utils_messenger(debug.debug_messenger, None); 46 | } 47 | Some(unsafe { Box::from_raw(debug.span_ptr) }) 48 | } else { 49 | None 50 | }; 51 | 52 | // Users of `Instance` are responsible for compliance with `VUID-vkDestroyInstance-instance-00629`. 53 | 54 | // SAFETY (Host Synchronization): InstanceInner is always stored in an Arc, therefore destruction is 55 | // synchronized (since the inner value of an Arc is always dropped on a single thread). 56 | unsafe { self.instance.destroy_instance(None) }; 57 | 58 | // Now that the instance has been destroyed, we can destroy the span. 59 | drop(span); 60 | } 61 | } 62 | 63 | impl super::Instance { 64 | pub(super) fn enumerate_layers() -> Result, LoadError> { 65 | let library = LIBRARY.as_ref().or(Err(LoadError))?; 66 | 67 | let layers = unsafe { library.enumerate_instance_layer_properties() } 68 | .or(Err(LoadError))? 69 | .into_iter() 70 | .map(|properties| { 71 | // SAFETY: Vulkan guarantees the string is null terminated. 72 | unsafe { CStr::from_ptr(&properties.layer_name as *const _) }.to_owned() 73 | }); 74 | 75 | Ok(layers) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/backend/vulkan/version.rs: -------------------------------------------------------------------------------- 1 | //! The [`Version`] type. 2 | 3 | use std::{ 4 | cmp::Ordering, 5 | fmt::{self, Formatter}, 6 | }; 7 | 8 | use ash::vk; 9 | 10 | /// A Vulkan API version. 11 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] 12 | pub struct Version { 13 | /// The variant of the Vulkan API. 14 | /// 15 | /// Generally this value will be `0` because the Vulkan specification uses variant `0`. 16 | pub variant: u32, 17 | 18 | /// The major version of the Vulkan API. 19 | pub major: u32, 20 | 21 | /// The minor version of the Vulkan API. 22 | pub minor: u32, 23 | 24 | /// The patch version of the Vulkan API. 25 | /// 26 | /// Most Vulkan API calls which take a version typically ignore the patch value. Consumers of the Vulkan API may 27 | /// typically ignore the patch value. 28 | pub patch: u32, 29 | } 30 | 31 | impl Version { 32 | /// Version 1.0 of the Vulkan API. 33 | pub const VERSION_1_0: Version = Version::from_raw(vk::API_VERSION_1_0); 34 | 35 | /// Version 1.1 of the Vulkan API. 36 | pub const VERSION_1_1: Version = Version::from_raw(vk::API_VERSION_1_1); 37 | 38 | /// Version 1.2 of the Vulkan API. 39 | pub const VERSION_1_2: Version = Version::from_raw(vk::API_VERSION_1_2); 40 | 41 | /// Version 1.3 of the Vulkan API. 42 | pub const VERSION_1_3: Version = Version::from_raw(vk::API_VERSION_1_3); 43 | 44 | /// The version of Smithay. 45 | pub const SMITHAY: Version = Version { 46 | // TODO: May be useful to place the version information in a single spot that isn't just Vulkan 47 | variant: 0, 48 | major: 0, 49 | minor: 3, 50 | patch: 0, 51 | }; 52 | 53 | /// Converts a packed version into a version struct. 54 | pub const fn from_raw(raw: u32) -> Version { 55 | Version { 56 | variant: vk::api_version_variant(raw), 57 | major: vk::api_version_major(raw), 58 | minor: vk::api_version_minor(raw), 59 | patch: vk::api_version_patch(raw), 60 | } 61 | } 62 | 63 | /// Converts a version struct into a packed version. 64 | pub const fn to_raw(self) -> u32 { 65 | vk::make_api_version(self.variant, self.major, self.minor, self.patch) 66 | } 67 | } 68 | 69 | impl fmt::Display for Version { 70 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 71 | write!( 72 | f, 73 | "{}.{}.{} variant {}", 74 | self.major, self.minor, self.patch, self.variant 75 | ) 76 | } 77 | } 78 | 79 | impl PartialOrd for Version { 80 | fn partial_cmp(&self, other: &Self) -> Option { 81 | Some(self.cmp(other)) 82 | } 83 | } 84 | 85 | impl Ord for Version { 86 | fn cmp(&self, other: &Self) -> Ordering { 87 | match self.variant.cmp(&other.variant) { 88 | Ordering::Equal => {} 89 | ord => return ord, 90 | } 91 | 92 | match self.major.cmp(&other.major) { 93 | Ordering::Equal => {} 94 | ord => return ord, 95 | } 96 | 97 | match self.minor.cmp(&other.minor) { 98 | Ordering::Equal => {} 99 | ord => return ord, 100 | } 101 | 102 | self.patch.cmp(&other.patch) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/desktop/mod.rs: -------------------------------------------------------------------------------- 1 | //! Desktop management helpers 2 | //! 3 | //! This module contains helpers to organize and interact with desktop-style shells. 4 | //! 5 | //! It is therefore a lot more opinionated than for example the [xdg-shell handler](crate::wayland::shell::xdg::XdgShellHandler) 6 | //! and tightly integrates with some protocols (e.g. xdg-shell). 7 | //! 8 | //! The usage of this module is therefor entirely optional and depending on your use-case you might also only want 9 | //! to use a limited set of the helpers provided. 10 | //! 11 | //! ## Helpers 12 | //! 13 | //! ### [`Window`] 14 | //! 15 | //! A window represents what is typically understood by the end-user as a single application window. 16 | //! 17 | //! Currently it abstracts over xdg-shell toplevels and Xwayland surfaces. 18 | //! It provides a bunch of methods to calculate and retrieve its size, manage itself, attach additional user_data 19 | //! as well as a [drawing function](`crate::backend::renderer::element::AsRenderElements::render_elements`) to ease rendering it's related surfaces. 20 | //! 21 | //! Note that a [`Window`] on it's own has no position. For that it needs to be placed inside a [`Space`]. 22 | //! 23 | //! ### [`Space`] 24 | //! 25 | //! A space represents a two-dimensional plane of undefined dimensions. 26 | //! [`Window`]s (and other types implementing [`SpaceElement`](space::SpaceElement)) and [`Output`](crate::output::Output)s can be mapped onto it. 27 | //! 28 | //! Elements get a position and stacking order through mapping. Outputs become views of a part of the [`Space`] 29 | //! and can be rendered via [`render_output`](crate::desktop::space::render_output). 30 | //! 31 | //! ### Layer Shell 32 | //! 33 | //! A [`LayerSurface`] represents a surface as provided by e.g. the layer-shell protocol. 34 | //! It provides similar helper methods as a [`Window`] does to toplevel surfaces. 35 | //! 36 | //! Each [`Output`](crate::output::Output) can be associated a [`LayerMap`] by calling [`layer_map_for_output`], 37 | //! which [`LayerSurface`]s can be mapped upon. Associated layer maps are automatically rendered by [`render_output`](crate::desktop::space::render_output), 38 | //! but a [draw function](`crate::backend::renderer::element::AsRenderElements::render_elements`) is also provided for manual layer-surface management. 39 | //! 40 | //! ### Popups 41 | //! 42 | //! Provides a [`PopupManager`], which can be used to automatically keep track of popups and their 43 | //! relations to one-another. Popups are then automatically rendered with their matching toplevel surfaces, 44 | //! when either [`crate::backend::renderer::element::AsRenderElements::render_elements`] or [`render_output`](crate::desktop::space::render_output) is called. 45 | //! 46 | //! ## Remarks 47 | //! 48 | //! Note that the desktop abstractions are concerned with easing rendering different clients and therefore need to be able 49 | //! to manage client buffers to do so. If you plan to use the provided drawing functions, you need to use 50 | //! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler). 51 | 52 | pub mod space; 53 | pub use self::space::Space; 54 | 55 | #[cfg(feature = "wayland_frontend")] 56 | pub use self::wayland::{ 57 | layer::{layer_map_for_output, LayerMap, LayerSurface}, 58 | popup::*, 59 | utils, 60 | window::*, 61 | }; 62 | #[cfg(feature = "wayland_frontend")] 63 | mod wayland { 64 | pub(crate) mod layer; 65 | pub mod popup; 66 | pub mod utils; 67 | pub mod window; 68 | } 69 | -------------------------------------------------------------------------------- /src/desktop/space/element/wayland.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backend::renderer::{ 3 | element::{surface::WaylandSurfaceRenderElement, AsRenderElements, Kind}, 4 | ImportAll, Renderer, 5 | }, 6 | utils::{IsAlive, Physical, Point, Scale}, 7 | }; 8 | use wayland_server::protocol::wl_surface::WlSurface; 9 | 10 | /// A custom surface tree 11 | #[derive(Debug)] 12 | pub struct SurfaceTree { 13 | surface: WlSurface, 14 | } 15 | 16 | impl SurfaceTree { 17 | /// Create a surface tree from a surface 18 | pub fn from_surface(surface: &WlSurface) -> Self { 19 | SurfaceTree { 20 | surface: surface.clone(), 21 | } 22 | } 23 | } 24 | 25 | impl IsAlive for SurfaceTree { 26 | #[inline] 27 | fn alive(&self) -> bool { 28 | self.surface.alive() 29 | } 30 | } 31 | 32 | impl AsRenderElements for SurfaceTree 33 | where 34 | R: Renderer + ImportAll, 35 | R::TextureId: Clone + 'static, 36 | { 37 | type RenderElement = WaylandSurfaceRenderElement; 38 | 39 | #[profiling::function] 40 | fn render_elements>>( 41 | &self, 42 | renderer: &mut R, 43 | location: Point, 44 | scale: Scale, 45 | alpha: f32, 46 | ) -> Vec { 47 | crate::backend::renderer::element::surface::render_elements_from_surface_tree( 48 | renderer, 49 | &self.surface, 50 | location, 51 | scale, 52 | alpha, 53 | Kind::Unspecified, 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/desktop/space/output.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | output::Output, 3 | utils::{Logical, Point}, 4 | }; 5 | 6 | use std::{collections::HashMap, sync::Mutex}; 7 | 8 | type OutputUserdata = Mutex>>; 9 | 10 | pub fn set_output_location(space: usize, o: &Output, new_loc: impl Into>>) { 11 | let userdata = o.user_data(); 12 | userdata.insert_if_missing_threadsafe(OutputUserdata::default); 13 | 14 | match new_loc.into() { 15 | Some(loc) => userdata 16 | .get::() 17 | .unwrap() 18 | .lock() 19 | .unwrap() 20 | .insert(space, loc), 21 | None => userdata 22 | .get::() 23 | .unwrap() 24 | .lock() 25 | .unwrap() 26 | .remove(&space), 27 | }; 28 | } 29 | 30 | pub fn output_location(space: usize, o: &Output) -> Point { 31 | let userdata = o.user_data(); 32 | userdata.insert_if_missing_threadsafe(OutputUserdata::default); 33 | *userdata 34 | .get::() 35 | .unwrap() 36 | .lock() 37 | .unwrap() 38 | .entry(space) 39 | .or_default() 40 | } 41 | -------------------------------------------------------------------------------- /src/desktop/space/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backend::renderer::{ 3 | element::{ 4 | utils::{ 5 | constrain_as_render_elements, ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, 6 | RelocateRenderElement, RescaleRenderElement, 7 | }, 8 | AsRenderElements, 9 | }, 10 | Renderer, 11 | }, 12 | utils::{Logical, Point, Rectangle, Scale}, 13 | }; 14 | 15 | use super::SpaceElement; 16 | 17 | /// Defines the reference size for the constrain behavior. 18 | #[derive(Debug, Copy, Clone)] 19 | pub enum ConstrainReference { 20 | /// Use the bounding box as the reference 21 | BoundingBox, 22 | /// Use the geometry as the reference 23 | Geometry, 24 | } 25 | 26 | /// Defines the behavior for [`constrain_space_element`] 27 | #[derive(Debug, Copy, Clone)] 28 | pub struct ConstrainBehavior { 29 | /// Defines what should be used as the reference for calculating the scale 30 | pub reference: ConstrainReference, 31 | /// Defines how the element should be scaled 32 | pub behavior: ConstrainScaleBehavior, 33 | /// Defines the alignment of the element inside of the constrain 34 | pub align: ConstrainAlign, 35 | } 36 | 37 | /// Constrain the render elements of a [`SpaceElement`] 38 | /// 39 | /// see [`constrain_as_render_elements`] 40 | #[profiling::function] 41 | pub fn constrain_space_element( 42 | renderer: &mut R, 43 | element: &E, 44 | location: impl Into>, 45 | alpha: f32, 46 | scale: impl Into>, 47 | constrain: Rectangle, 48 | behavior: ConstrainBehavior, 49 | ) -> impl Iterator 50 | where 51 | R: Renderer, 52 | E: SpaceElement + AsRenderElements, 53 | C: From< 54 | CropRenderElement< 55 | RelocateRenderElement>::RenderElement>>, 56 | >, 57 | >, 58 | { 59 | let location = location.into(); 60 | let scale = scale.into(); 61 | 62 | let scale_reference = match behavior.reference { 63 | ConstrainReference::BoundingBox => element.bbox(), 64 | ConstrainReference::Geometry => element.geometry(), 65 | }; 66 | 67 | constrain_as_render_elements( 68 | element, 69 | renderer, 70 | (location - scale_reference.loc).to_physical_precise_round(scale), 71 | alpha, 72 | constrain.to_physical_precise_round(scale), 73 | scale_reference.to_physical_precise_round(scale), 74 | behavior.behavior, 75 | behavior.align, 76 | scale, 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /src/desktop/space/wayland/layer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | backend::renderer::{ 3 | element::{ 4 | surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, 5 | AsRenderElements, Kind, 6 | }, 7 | ImportAll, Renderer, 8 | }, 9 | desktop::{LayerSurface, PopupManager}, 10 | utils::{Physical, Point, Scale}, 11 | }; 12 | 13 | impl AsRenderElements for LayerSurface 14 | where 15 | R: Renderer + ImportAll, 16 | R::TextureId: Clone + 'static, 17 | { 18 | type RenderElement = WaylandSurfaceRenderElement; 19 | 20 | #[profiling::function] 21 | fn render_elements>>( 22 | &self, 23 | renderer: &mut R, 24 | location: Point, 25 | scale: Scale, 26 | alpha: f32, 27 | ) -> Vec { 28 | let surface = self.wl_surface(); 29 | 30 | let mut render_elements: Vec = Vec::new(); 31 | let popup_render_elements = 32 | PopupManager::popups_for_surface(surface).flat_map(|(popup, popup_offset)| { 33 | let offset = (popup_offset - popup.geometry().loc) 34 | .to_f64() 35 | .to_physical(scale) 36 | .to_i32_round(); 37 | 38 | render_elements_from_surface_tree( 39 | renderer, 40 | popup.wl_surface(), 41 | location + offset, 42 | scale, 43 | alpha, 44 | Kind::Unspecified, 45 | ) 46 | }); 47 | 48 | render_elements.extend(popup_render_elements); 49 | 50 | render_elements.extend(render_elements_from_surface_tree( 51 | renderer, 52 | surface, 53 | location, 54 | scale, 55 | alpha, 56 | Kind::Unspecified, 57 | )); 58 | 59 | render_elements 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/desktop/space/wayland/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap}; 2 | 3 | use tracing::instrument; 4 | use wayland_server::protocol::wl_surface::WlSurface; 5 | 6 | use crate::{ 7 | backend::renderer::utils::RendererSurfaceStateUserData, 8 | output::{Output, WeakOutput}, 9 | utils::{Logical, Point, Rectangle}, 10 | wayland::compositor::{with_surface_tree_downward, TraversalAction}, 11 | }; 12 | 13 | mod layer; 14 | mod window; 15 | #[cfg(feature = "xwayland")] 16 | mod x11; 17 | 18 | /// Updates the output overlap for a surface tree. 19 | /// 20 | /// Surfaces in the tree will receive output enter and leave events as necessary according to their 21 | /// computed overlap. 22 | #[instrument(level = "trace", skip(output), fields(output = output.name()))] 23 | #[profiling::function] 24 | pub fn output_update(output: &Output, output_overlap: Option>, surface: &WlSurface) { 25 | with_surface_tree_downward( 26 | surface, 27 | (Point::from((0, 0)), false), 28 | |_, states, (location, parent_unmapped)| { 29 | let mut location = *location; 30 | let data = states.data_map.get::(); 31 | 32 | // If the parent is unmapped we still have to traverse 33 | // our children to send a leave events 34 | if *parent_unmapped { 35 | TraversalAction::DoChildren((location, true)) 36 | } else if let Some(surface_view) = data.and_then(|d| d.lock().unwrap().surface_view) { 37 | location += surface_view.offset; 38 | TraversalAction::DoChildren((location, false)) 39 | } else { 40 | // If we are unmapped we still have to traverse 41 | // our children to send leave events 42 | TraversalAction::DoChildren((location, true)) 43 | } 44 | }, 45 | |wl_surface, states, (location, parent_unmapped)| { 46 | let mut location = *location; 47 | 48 | if *parent_unmapped { 49 | // The parent is unmapped, just send a leave event 50 | // if we were previously mapped and exit early 51 | output.leave(wl_surface); 52 | return; 53 | } 54 | 55 | let Some(output_overlap) = output_overlap else { 56 | // There's no overlap, send a leave event. 57 | output.leave(wl_surface); 58 | return; 59 | }; 60 | 61 | let data = states.data_map.get::(); 62 | 63 | if let Some(surface_view) = data.and_then(|d| d.lock().unwrap().surface_view) { 64 | location += surface_view.offset; 65 | let surface_rectangle = Rectangle::new(location, surface_view.dst); 66 | if output_overlap.overlaps(surface_rectangle) { 67 | // We found a matching output, check if we already sent enter 68 | output.enter(wl_surface); 69 | } else { 70 | // Surface does not match output, if we sent enter earlier 71 | // we should now send leave 72 | output.leave(wl_surface); 73 | } 74 | } else { 75 | // Maybe the the surface got unmapped, send leave on output 76 | output.leave(wl_surface); 77 | } 78 | }, 79 | |_, _, _| true, 80 | ); 81 | } 82 | 83 | #[derive(Debug, Default)] 84 | struct WindowOutputState { 85 | output_overlap: HashMap>, 86 | } 87 | type WindowOutputUserData = RefCell; 88 | -------------------------------------------------------------------------------- /src/desktop/space/wayland/x11.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use wayland_server::protocol::wl_surface::WlSurface; 4 | 5 | use crate::{ 6 | backend::renderer::{ 7 | element::{ 8 | surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, 9 | Kind, 10 | }, 11 | ImportAll, Renderer, 12 | }, 13 | desktop::{space::SpaceElement, utils::under_from_surface_tree, WindowSurfaceType}, 14 | utils::{Logical, Physical, Point, Rectangle, Scale}, 15 | wayland::seat::WaylandFocus, 16 | xwayland::X11Surface, 17 | }; 18 | 19 | use super::{output_update, WindowOutputUserData}; 20 | 21 | impl WaylandFocus for X11Surface { 22 | #[inline] 23 | fn wl_surface(&self) -> Option> { 24 | self.state.lock().unwrap().wl_surface.clone().map(Cow::Owned) 25 | } 26 | } 27 | 28 | impl SpaceElement for X11Surface { 29 | fn bbox(&self) -> Rectangle { 30 | let geo = X11Surface::geometry(self); 31 | Rectangle::from_size(geo.size) 32 | } 33 | 34 | fn is_in_input_region(&self, point: &Point) -> bool { 35 | let state = self.state.lock().unwrap(); 36 | if let Some(surface) = state.wl_surface.as_ref() { 37 | under_from_surface_tree(surface, *point, (0, 0), WindowSurfaceType::ALL).is_some() 38 | } else { 39 | false 40 | } 41 | } 42 | 43 | fn set_activate(&self, activated: bool) { 44 | let _ = self.set_activated(activated); 45 | } 46 | 47 | fn output_enter(&self, output: &crate::output::Output, overlap: Rectangle) { 48 | self.user_data().insert_if_missing(WindowOutputUserData::default); 49 | { 50 | let mut state = self 51 | .user_data() 52 | .get::() 53 | .unwrap() 54 | .borrow_mut(); 55 | state.output_overlap.insert(output.downgrade(), overlap); 56 | state.output_overlap.retain(|weak, _| weak.is_alive()); 57 | } 58 | self.refresh() 59 | } 60 | 61 | fn output_leave(&self, output: &crate::output::Output) { 62 | if let Some(state) = self.user_data().get::() { 63 | state.borrow_mut().output_overlap.retain(|weak, _| weak != output); 64 | } 65 | 66 | let state = self.state.lock().unwrap(); 67 | let Some(surface) = state.wl_surface.as_ref() else { 68 | return; 69 | }; 70 | output_update(output, None, surface); 71 | } 72 | 73 | fn refresh(&self) { 74 | self.user_data().insert_if_missing(WindowOutputUserData::default); 75 | let wo_state = self.user_data().get::().unwrap().borrow(); 76 | 77 | let state = self.state.lock().unwrap(); 78 | let Some(surface) = state.wl_surface.as_ref() else { 79 | return; 80 | }; 81 | for (weak, overlap) in wo_state.output_overlap.iter() { 82 | if let Some(output) = weak.upgrade() { 83 | output_update(&output, Some(*overlap), surface); 84 | } 85 | } 86 | } 87 | 88 | fn z_index(&self) -> u8 { 89 | if self.is_override_redirect() { 90 | crate::desktop::space::RenderZindex::Overlay as u8 91 | } else { 92 | crate::desktop::space::RenderZindex::Shell as u8 93 | } 94 | } 95 | } 96 | 97 | impl crate::backend::renderer::element::AsRenderElements for X11Surface 98 | where 99 | R: Renderer + ImportAll, 100 | R::TextureId: Clone + 'static, 101 | { 102 | type RenderElement = WaylandSurfaceRenderElement; 103 | 104 | #[profiling::function] 105 | fn render_elements>>( 106 | &self, 107 | renderer: &mut R, 108 | location: Point, 109 | scale: Scale, 110 | alpha: f32, 111 | ) -> Vec { 112 | let state = self.state.lock().unwrap(); 113 | let Some(surface) = state.wl_surface.as_ref() else { 114 | return Vec::new(); 115 | }; 116 | render_elements_from_surface_tree(renderer, surface, location, scale, alpha, Kind::Unspecified) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/desktop/wayland/popup/mod.rs: -------------------------------------------------------------------------------- 1 | mod grab; 2 | mod manager; 3 | 4 | pub use grab::*; 5 | pub use manager::*; 6 | use wayland_server::protocol::wl_surface::WlSurface; 7 | 8 | use crate::{ 9 | utils::{IsAlive, Logical, Point, Rectangle}, 10 | wayland::{ 11 | compositor::with_states, 12 | input_method, 13 | shell::xdg::{self, SurfaceCachedState, XdgPopupSurfaceData}, 14 | }, 15 | }; 16 | 17 | /// Represents a popup surface 18 | #[derive(Debug, Clone, PartialEq)] 19 | pub enum PopupKind { 20 | /// xdg-shell [`PopupSurface`](xdg::PopupSurface) 21 | Xdg(xdg::PopupSurface), 22 | /// input-method [`PopupSurface`](input_method::PopupSurface) 23 | InputMethod(input_method::PopupSurface), 24 | } 25 | 26 | impl IsAlive for PopupKind { 27 | #[inline] 28 | fn alive(&self) -> bool { 29 | match self { 30 | PopupKind::Xdg(ref p) => p.alive(), 31 | PopupKind::InputMethod(ref p) => p.alive(), 32 | } 33 | } 34 | } 35 | 36 | impl From for WlSurface { 37 | #[inline] 38 | fn from(p: PopupKind) -> Self { 39 | p.wl_surface().clone() 40 | } 41 | } 42 | 43 | impl PopupKind { 44 | /// Retrieves the underlying [`WlSurface`] 45 | #[inline] 46 | pub fn wl_surface(&self) -> &WlSurface { 47 | match *self { 48 | PopupKind::Xdg(ref t) => t.wl_surface(), 49 | PopupKind::InputMethod(ref t) => t.wl_surface(), 50 | } 51 | } 52 | 53 | fn parent(&self) -> Option { 54 | match *self { 55 | PopupKind::Xdg(ref t) => t.get_parent_surface(), 56 | PopupKind::InputMethod(ref t) => t.get_parent().map(|parent| parent.surface.clone()), 57 | } 58 | } 59 | 60 | /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` 61 | pub fn geometry(&self) -> Rectangle { 62 | let wl_surface = self.wl_surface(); 63 | match *self { 64 | PopupKind::Xdg(_) => with_states(wl_surface, |states| { 65 | states 66 | .cached_state 67 | .get::() 68 | .current() 69 | .geometry 70 | .unwrap_or_default() 71 | }), 72 | PopupKind::InputMethod(ref t) => t.get_parent().map(|parent| parent.location).unwrap_or_default(), 73 | } 74 | } 75 | 76 | fn send_done(&self) { 77 | match *self { 78 | PopupKind::Xdg(ref t) => t.send_popup_done(), 79 | PopupKind::InputMethod(_) => {} //Nothing to do the IME takes care of this itself 80 | } 81 | } 82 | 83 | fn location(&self) -> Point { 84 | let wl_surface = self.wl_surface(); 85 | 86 | match *self { 87 | PopupKind::Xdg(_) => { 88 | with_states(wl_surface, |states| { 89 | states 90 | .data_map 91 | .get::() 92 | .unwrap() 93 | .lock() 94 | .unwrap() 95 | .current 96 | .geometry 97 | }) 98 | .loc 99 | } 100 | PopupKind::InputMethod(ref t) => t.location(), 101 | } 102 | } 103 | } 104 | 105 | impl From for PopupKind { 106 | #[inline] 107 | fn from(p: xdg::PopupSurface) -> PopupKind { 108 | PopupKind::Xdg(p) 109 | } 110 | } 111 | 112 | impl From for PopupKind { 113 | #[inline] 114 | fn from(p: input_method::PopupSurface) -> PopupKind { 115 | PopupKind::InputMethod(p) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/input/keyboard/keymap_file.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::os::unix::io::{AsFd, BorrowedFd}; 3 | 4 | use sha2::{Digest, Sha256}; 5 | use tracing::error; 6 | use xkbcommon::xkb::{self, Keymap, KEYMAP_FORMAT_TEXT_V1}; 7 | 8 | use crate::utils::SealedFile; 9 | 10 | /// Unique ID for a keymap 11 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 12 | pub struct KeymapFileId([u8; 32]); 13 | 14 | impl KeymapFileId { 15 | fn for_keymap(keymap: &str) -> Self { 16 | // Use a hash, so `keymap` events aren't sent when keymap hasn't changed, particularly 17 | // with `virtual-keyboard-unstable-v1`. 18 | Self(Sha256::digest(keymap).as_slice().try_into().unwrap()) 19 | } 20 | } 21 | 22 | /// Wraps an XKB keymap into a sealed file or stores as just a string for sending to WlKeyboard over an fd 23 | #[derive(Debug)] 24 | pub struct KeymapFile { 25 | sealed: Option, 26 | keymap: String, 27 | id: KeymapFileId, 28 | } 29 | 30 | impl KeymapFile { 31 | /// Turn the keymap into a string using KEYMAP_FORMAT_TEXT_V1, create a sealed file for it, and store the string 32 | pub fn new(keymap: &Keymap) -> Self { 33 | let name = c"smithay-keymap"; 34 | let keymap = keymap.get_as_string(KEYMAP_FORMAT_TEXT_V1); 35 | let sealed = SealedFile::with_content(name, &CString::new(keymap.as_str()).unwrap()); 36 | 37 | if let Err(err) = sealed.as_ref() { 38 | error!("Error when creating sealed keymap file: {}", err); 39 | } 40 | 41 | Self { 42 | id: KeymapFileId::for_keymap(&keymap), 43 | sealed: sealed.ok(), 44 | keymap, 45 | } 46 | } 47 | 48 | #[cfg(feature = "wayland_frontend")] 49 | pub(crate) fn change_keymap(&mut self, keymap: &Keymap) { 50 | let keymap = keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); 51 | 52 | let name = c"smithay-keymap-file"; 53 | let sealed = SealedFile::with_content(name, &CString::new(keymap.clone()).unwrap()); 54 | 55 | if let Err(err) = sealed.as_ref() { 56 | error!("Error when creating sealed keymap file: {}", err); 57 | } 58 | 59 | self.id = KeymapFileId::for_keymap(&keymap); 60 | self.sealed = sealed.ok(); 61 | self.keymap = keymap; 62 | } 63 | 64 | #[cfg(feature = "wayland_frontend")] 65 | /// Run a closure with the file descriptor to ensure safety 66 | pub fn with_fd(&self, supports_sealed: bool, cb: F) -> Result<(), std::io::Error> 67 | where 68 | F: FnOnce(BorrowedFd<'_>, usize), 69 | { 70 | use std::{io::Write, path::PathBuf}; 71 | 72 | if let Some(file) = supports_sealed.then_some(self.sealed.as_ref()).flatten() { 73 | cb(file.as_fd(), file.size()); 74 | } else { 75 | let dir = std::env::var_os("XDG_RUNTIME_DIR") 76 | .map(PathBuf::from) 77 | .unwrap_or_else(std::env::temp_dir); 78 | 79 | let mut file = tempfile::tempfile_in(dir)?; 80 | file.write_all(self.keymap.as_bytes())?; 81 | file.flush()?; 82 | 83 | cb(file.as_fd(), self.keymap.len()); 84 | } 85 | Ok(()) 86 | } 87 | 88 | /// Send the keymap contained within to a WlKeyboard 89 | pub fn send( 90 | &self, 91 | keyboard: &wayland_server::protocol::wl_keyboard::WlKeyboard, 92 | ) -> Result<(), std::io::Error> { 93 | use wayland_server::{protocol::wl_keyboard::KeymapFormat, Resource}; 94 | 95 | self.with_fd(keyboard.version() >= 7, |fd, size| { 96 | keyboard.keymap(KeymapFormat::XkbV1, fd, size as u32); 97 | }) 98 | } 99 | 100 | /// Get this keymap's unique ID. 101 | pub(crate) fn id(&self) -> KeymapFileId { 102 | self.id 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/input/keyboard/xkb_config.rs: -------------------------------------------------------------------------------- 1 | pub use xkbcommon::xkb; 2 | 3 | /// Configuration for xkbcommon. 4 | /// 5 | /// For the fields that are not set ("" or None, as set in the `Default` impl), xkbcommon will use 6 | /// the values from the environment variables `XKB_DEFAULT_RULES`, `XKB_DEFAULT_MODEL`, 7 | /// `XKB_DEFAULT_LAYOUT`, `XKB_DEFAULT_VARIANT` and `XKB_DEFAULT_OPTIONS`. 8 | /// 9 | /// For details, see the [documentation at xkbcommon.org][docs]. 10 | /// 11 | /// [docs]: https://xkbcommon.org/doc/current/structxkb__rule__names.html 12 | #[derive(Clone, Debug, Default)] 13 | pub struct XkbConfig<'a> { 14 | /// The rules file to use. 15 | /// 16 | /// The rules file describes how to interpret the values of the model, layout, variant and 17 | /// options fields. 18 | pub rules: &'a str, 19 | /// The keyboard model by which to interpret keycodes and LEDs. 20 | pub model: &'a str, 21 | /// A comma separated list of layouts (languages) to include in the keymap. 22 | pub layout: &'a str, 23 | /// A comma separated list of variants, one per layout, which may modify or augment the 24 | /// respective layout in various ways. 25 | pub variant: &'a str, 26 | /// A comma separated list of options, through which the user specifies non-layout related 27 | /// preferences, like which key combinations are used for switching layouts, or which key is the 28 | /// Compose key. 29 | pub options: Option, 30 | } 31 | 32 | impl XkbConfig<'_> { 33 | pub(crate) fn compile_keymap(&self, context: &xkb::Context) -> Result { 34 | xkb::Keymap::new_from_names( 35 | context, 36 | self.rules, 37 | self.model, 38 | self.layout, 39 | self.variant, 40 | self.options.clone(), 41 | xkb::KEYMAP_COMPILE_NO_FLAGS, 42 | ) 43 | .ok_or(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/input/pointer/cursor_image.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "wayland_frontend")] 2 | use wayland_server::protocol::wl_surface::WlSurface; 3 | 4 | pub use cursor_icon::CursorIcon; 5 | 6 | use crate::utils::{Logical, Point}; 7 | use std::sync::Mutex; 8 | 9 | /// The role representing a surface set as the pointer cursor 10 | #[derive(Debug, Default, Copy, Clone)] 11 | pub struct CursorImageAttributes { 12 | /// Location of the hotspot of the pointer in the surface 13 | pub hotspot: Point, 14 | } 15 | 16 | /// Data associated with XDG toplevel surface 17 | /// 18 | /// ```no_run 19 | /// # #[cfg(feature = "wayland_frontend")] 20 | /// use smithay::wayland::compositor; 21 | /// use smithay::input::pointer::CursorImageSurfaceData; 22 | /// 23 | /// # let wl_surface = todo!(); 24 | /// # #[cfg(feature = "wayland_frontend")] 25 | /// compositor::with_states(&wl_surface, |states| { 26 | /// states.data_map.get::(); 27 | /// }); 28 | /// ``` 29 | pub type CursorImageSurfaceData = Mutex; 30 | 31 | /// Possible status of a cursor as requested by clients 32 | #[derive(Debug, Clone, PartialEq, Eq)] 33 | pub enum CursorImageStatus { 34 | /// The cursor should be hidden 35 | Hidden, 36 | /// The compositor should draw the given named 37 | /// cursor. 38 | Named(CursorIcon), 39 | // TODO bitmap, dmabuf cursor? Or let the compositor handle everything through "Default" 40 | /// The cursor should be drawn using this surface as an image 41 | #[cfg(feature = "wayland_frontend")] 42 | Surface(WlSurface), 43 | } 44 | 45 | impl CursorImageStatus { 46 | /// Get default `Named` cursor. 47 | pub fn default_named() -> Self { 48 | Self::Named(CursorIcon::Default) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] 3 | // Allow acronyms like EGL 4 | #![allow(clippy::upper_case_acronyms)] 5 | 6 | //! # Smithay: the Wayland compositor smithy 7 | //! 8 | //! This crate is a general framework for building wayland compositors. It currently focuses on low-level, 9 | //! helpers and abstractions, handling most of the system-level and wayland protocol interactions. 10 | //! The window management and drawing logic is however at this time not provided (but helpers for this 11 | //! are planned for future version). 12 | //! 13 | //! ## Structure of the crate 14 | //! 15 | //! The provided helpers are split into two main modules: 16 | //! 17 | //! - [`backend`] contains helpers for interacting with the operating 18 | //! system, such as session management, interactions with the graphic stack 19 | //! and input processing. 20 | //! - [`wayland`] contains helpers for interacting with wayland clients 21 | //! according to the wayland protocol. 22 | //! 23 | //! In addition, the [`xwayland`] module contains helpers for managing an 24 | //! XWayland instance if you want to support it. See the documentation of 25 | //! these respective modules for information about their usage. 26 | //! 27 | //! ## General principles for using Smithay 28 | //! 29 | //! ### The event loop and state handling 30 | //! 31 | //! Smithay is built around [`calloop`], a callback-oriented event loop, which fits naturally with the 32 | //! general behavior of a wayland compositor: waiting for events to occur and react to them (be it 33 | //! client requests, user input, or hardware events such as `vblank`). 34 | //! 35 | //! Using a callback-heavy structure however poses the question of state management: a lot of state needs 36 | //! to be accessed from many different callbacks. To avoid a heavy requirement on shared pointers such 37 | //! as `Rc` and `Arc` and the synchronization they require, [`calloop`] allows you to provide a mutable 38 | //! reference to a value that will be passed down to most callbacks. This structure provides 39 | //! easy access to a centralized mutable state without synchronization (as the callback invocation is 40 | //! *always* sequential), and is the recommended way of structuring your compositor. 41 | //! TODO: Add a section here how this links to wayland-server's `Dispatch` and link to the wayland-server 42 | //! docs, once they exist for 0.30. 43 | //! 44 | //! Several objects, in particular on the wayland clients side, can exist as multiple instances where each 45 | //! instance has its own associated state. For these situations, these objects provide an interface allowing 46 | //! you to associate an arbitrary value to them, that you can access at any time from the object itself 47 | //! (rather than having your own container in which you search for the appropriate value when you need it). 48 | //! 49 | //! ### Logging 50 | //! 51 | //! Smithay makes extensive use of [`tracing`] for its internal logging. 52 | //! 53 | //! For release builds it is recommended to limit the log level during compile time. 54 | //! This can be done by adding a dependency to [`tracing`] and enabling the corresponding features. 55 | //! For example to enable `trace` messages for debug builds, but limit release builds to `debug` add 56 | //! the following in your binary crate `Cargo.toml`: 57 | //! 58 | //! ```toml 59 | //! [dependencies] 60 | //! tracing = { version = "0.1", features = ["max_level_trace", "release_max_level_debug"] } 61 | //! ``` 62 | //! 63 | //! If you do not want to use [`tracing`] for your compositor, refer to [`log compatibility`](tracing#log-compatibility) 64 | //! for how to forward smithays debug output to other `log` compatible frameworks. 65 | 66 | pub mod backend; 67 | #[cfg(feature = "desktop")] 68 | pub mod desktop; 69 | pub mod input; 70 | pub mod output; 71 | pub mod utils; 72 | #[cfg(feature = "wayland_frontend")] 73 | pub mod wayland; 74 | 75 | #[cfg(feature = "xwayland")] 76 | pub mod xwayland; 77 | 78 | pub mod reexports; 79 | -------------------------------------------------------------------------------- /src/reexports.rs: -------------------------------------------------------------------------------- 1 | //! Reexports of crates, that are part of the public api, for convenience 2 | 3 | #[cfg(feature = "backend_vulkan")] 4 | pub use ash; 5 | pub use calloop; 6 | #[cfg(feature = "backend_drm")] 7 | pub use drm; 8 | #[cfg(feature = "backend_gbm")] 9 | pub use gbm; 10 | #[cfg(feature = "renderer_glow")] 11 | pub use glow; 12 | #[cfg(feature = "backend_libinput")] 13 | pub use input; 14 | #[cfg(feature = "renderer_pixman")] 15 | pub use pixman; 16 | pub use rustix; 17 | #[cfg(feature = "backend_udev")] 18 | pub use udev; 19 | #[cfg(feature = "wayland_frontend")] 20 | pub use wayland_protocols; 21 | #[cfg(feature = "wayland_frontend")] 22 | pub use wayland_protocols_misc; 23 | #[cfg(feature = "wayland_frontend")] 24 | pub use wayland_protocols_wlr; 25 | #[cfg(feature = "wayland_frontend")] 26 | pub use wayland_server; 27 | #[cfg(feature = "backend_winit")] 28 | pub use winit; 29 | #[cfg(feature = "x11rb_event_source")] 30 | pub use x11rb; 31 | -------------------------------------------------------------------------------- /src/utils/alive_tracker.rs: -------------------------------------------------------------------------------- 1 | //! Utilities to track object's life cycle 2 | 3 | // All our AliveTracker usage is internal and only 4 | // used, when the wayland_frontend feature is enabled. 5 | // So we need to silence some warnings in other cases. 6 | #![allow(dead_code)] 7 | 8 | use std::sync::atomic::{AtomicBool, Ordering}; 9 | 10 | /// Util to track wayland object's life time 11 | #[derive(Debug)] 12 | pub struct AliveTracker { 13 | is_alive: AtomicBool, 14 | } 15 | 16 | impl Default for AliveTracker { 17 | fn default() -> Self { 18 | Self { 19 | is_alive: AtomicBool::new(true), 20 | } 21 | } 22 | } 23 | 24 | impl AliveTracker { 25 | /// Notify the tracker that object is dead 26 | pub fn destroy_notify(&self) { 27 | self.is_alive.store(false, Ordering::Release); 28 | } 29 | 30 | /// Check if object is alive 31 | #[inline] 32 | pub fn alive(&self) -> bool { 33 | self.is_alive.load(Ordering::Acquire) 34 | } 35 | } 36 | 37 | /// Trait that is implemented on wayland objects tracked by Smithay 38 | pub trait IsAlive { 39 | /// Check if object is alive 40 | fn alive(&self) -> bool; 41 | } 42 | 43 | impl IsAlive for &T { 44 | #[inline] 45 | fn alive(&self) -> bool { 46 | IsAlive::alive(*self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/fd.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_op_in_unsafe_fn)] 2 | 3 | use std::{ 4 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, 5 | path::PathBuf, 6 | sync::Arc, 7 | }; 8 | 9 | /// Ref-counted file descriptor of an open device node 10 | #[derive(Debug, Clone)] 11 | pub struct DeviceFd(Arc); 12 | 13 | impl PartialEq for DeviceFd { 14 | #[inline] 15 | fn eq(&self, other: &Self) -> bool { 16 | self.0.as_raw_fd() == other.0.as_raw_fd() 17 | } 18 | } 19 | 20 | impl AsFd for DeviceFd { 21 | #[inline] 22 | fn as_fd(&self) -> BorrowedFd<'_> { 23 | self.0.as_fd() 24 | } 25 | } 26 | 27 | // TODO: drop impl once not needed anymore by smithay or dependencies 28 | impl AsRawFd for DeviceFd { 29 | #[inline] 30 | fn as_raw_fd(&self) -> RawFd { 31 | self.0.as_raw_fd() 32 | } 33 | } 34 | 35 | impl FromRawFd for DeviceFd { 36 | /// SAFETY: 37 | /// Make sure that `fd` is a valid value! 38 | #[inline] 39 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 40 | DeviceFd(Arc::new(unsafe { OwnedFd::from_raw_fd(fd) })) 41 | } 42 | } 43 | 44 | impl From for DeviceFd { 45 | #[inline] 46 | fn from(fd: OwnedFd) -> Self { 47 | DeviceFd(Arc::new(fd)) 48 | } 49 | } 50 | 51 | impl TryInto for DeviceFd { 52 | type Error = DeviceFd; 53 | 54 | #[inline] 55 | fn try_into(self) -> Result { 56 | Arc::try_unwrap(self.0).map_err(DeviceFd) 57 | } 58 | } 59 | 60 | /// Trait representing open devices that *may* return a `Path` 61 | pub trait DevPath { 62 | /// Returns the path of the open device if possible 63 | fn dev_path(&self) -> Option; 64 | } 65 | 66 | impl DevPath for A { 67 | fn dev_path(&self) -> Option { 68 | use std::fs; 69 | 70 | fs::read_link(format!("/proc/self/fd/{:?}", self.as_fd().as_raw_fd())).ok() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/hook.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | crate::utils::ids::id_gen!(hooks_id); 4 | 5 | /// Unique hook identifier used to unregister commit/descruction hooks 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub struct HookId(Arc); 8 | 9 | pub(crate) struct Hook { 10 | pub id: HookId, 11 | pub cb: Arc, 12 | } 13 | 14 | impl std::fmt::Debug for Hook { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | f.debug_struct("Hook") 17 | .field("id", &self.id) 18 | .finish_non_exhaustive() 19 | } 20 | } 21 | 22 | impl Clone for Hook { 23 | fn clone(&self) -> Self { 24 | Self { 25 | id: self.id.clone(), 26 | cb: self.cb.clone(), 27 | } 28 | } 29 | } 30 | 31 | impl Hook { 32 | pub fn new(cb: Arc) -> Self { 33 | Self { 34 | id: HookId(Arc::new(InnerId::new())), 35 | cb, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug, Eq, PartialEq)] 41 | struct InnerId(usize); 42 | 43 | impl InnerId { 44 | fn new() -> Self { 45 | Self(hooks_id::next()) 46 | } 47 | } 48 | 49 | impl Drop for InnerId { 50 | fn drop(&mut self) { 51 | hooks_id::remove(self.0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/ids.rs: -------------------------------------------------------------------------------- 1 | macro_rules! id_gen { 2 | ($mod_name:ident) => { 3 | mod $mod_name { 4 | use std::{ 5 | collections::HashSet, 6 | sync::{LazyLock, Mutex}, 7 | }; 8 | 9 | static ID_DATA: LazyLock, usize)>> = 10 | LazyLock::new(|| Mutex::new((HashSet::new(), 0))); 11 | 12 | pub(crate) fn next() -> usize { 13 | let (id_set, counter) = &mut *ID_DATA.lock().unwrap(); 14 | 15 | if id_set.len() == usize::MAX { 16 | panic!("Out of ids"); 17 | } 18 | 19 | while !id_set.insert(*counter) { 20 | *counter = counter.wrapping_add(1); 21 | } 22 | 23 | let new_id = *counter; 24 | *counter = counter.wrapping_add(1); 25 | 26 | new_id 27 | } 28 | 29 | pub(crate) fn remove(id: usize) -> bool { 30 | ID_DATA.lock().unwrap().0.remove(&id) 31 | } 32 | } 33 | }; 34 | } 35 | 36 | pub(crate) use id_gen; 37 | -------------------------------------------------------------------------------- /src/utils/iter.rs: -------------------------------------------------------------------------------- 1 | /// Common iterator types 2 | use wayland_server::{backend::ClientId, Resource, Weak}; 3 | 4 | use std::{fmt, sync::MutexGuard}; 5 | 6 | /// Iterator helper over a mutex of client objects 7 | pub struct LockedClientObjsIter<'a, T: 'static, G, F> { 8 | pub iterator: std::iter::FilterMap>, F>, 9 | pub guard: MutexGuard<'a, G>, 10 | } 11 | 12 | pub(crate) fn new_locked_obj_iter_from_vec( 13 | guard: MutexGuard<'_, Vec>>, 14 | client: ClientId, 15 | ) -> impl Iterator + '_ { 16 | new_locked_obj_iter(guard, client, |guard| guard.iter()) 17 | } 18 | 19 | pub(crate) fn new_locked_obj_iter< 20 | 'a, 21 | T: Resource + 'static, 22 | G, 23 | F: for<'b> FnOnce(&'b G) -> std::slice::Iter<'b, Weak>, 24 | >( 25 | guard: MutexGuard<'a, G>, 26 | client: ClientId, 27 | iterator_fn: F, 28 | ) -> impl Iterator + 'a { 29 | let iterator = unsafe { 30 | std::mem::transmute::>, std::slice::Iter<'static, Weak>>(iterator_fn( 31 | &*guard, 32 | )) 33 | }; 34 | 35 | let iterator = iterator.filter_map(move |p| { 36 | let client = &client; 37 | p.upgrade() 38 | .ok() 39 | .filter(|p| p.client().is_some_and(|c| c.id() == *client)) 40 | }); 41 | 42 | LockedClientObjsIter::<'a, T, G, _>::new_internal(iterator, guard) 43 | } 44 | 45 | impl<'a, T, G, F> LockedClientObjsIter<'a, T, G, F> { 46 | fn new_internal( 47 | iterator: std::iter::FilterMap>, F>, 48 | guard: MutexGuard<'a, G>, 49 | ) -> Self { 50 | LockedClientObjsIter { iterator, guard } 51 | } 52 | } 53 | 54 | impl Iterator for LockedClientObjsIter<'_, T, G, F> 55 | where 56 | F: FnMut(&'static Weak) -> Option, 57 | { 58 | type Item = T; 59 | 60 | fn next(&mut self) -> Option { 61 | self.iterator.next() 62 | } 63 | } 64 | 65 | impl fmt::Debug for LockedClientObjsIter<'_, T, G, F> { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | f.debug_struct("LockedClientObjsIter") 68 | .field("inner", &self.guard) 69 | .finish() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Various utilities functions and types 2 | 3 | mod geometry; 4 | pub mod signaling; 5 | 6 | #[cfg(feature = "x11rb_event_source")] 7 | pub mod x11rb; 8 | 9 | pub(crate) mod ids; 10 | pub mod user_data; 11 | 12 | pub(crate) mod alive_tracker; 13 | pub use self::alive_tracker::IsAlive; 14 | 15 | #[cfg(feature = "wayland_frontend")] 16 | pub(crate) mod iter; 17 | 18 | mod fd; 19 | pub use fd::*; 20 | 21 | mod sealed_file; 22 | pub use sealed_file::SealedFile; 23 | 24 | #[cfg(feature = "wayland_frontend")] 25 | pub(crate) use self::geometry::Client; 26 | pub use self::geometry::{ 27 | Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Scale, Size, Transform, 28 | }; 29 | 30 | mod serial; 31 | pub use serial::*; 32 | 33 | mod clock; 34 | pub use clock::*; 35 | 36 | #[cfg(feature = "wayland_frontend")] 37 | pub(crate) mod hook; 38 | #[cfg(feature = "wayland_frontend")] 39 | pub use hook::HookId; 40 | 41 | /// This resource is not managed by Smithay 42 | #[derive(Debug)] 43 | pub struct UnmanagedResource; 44 | 45 | impl std::fmt::Display for UnmanagedResource { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | f.write_str("This resource is not managed by Smithay.") 48 | } 49 | } 50 | 51 | impl std::error::Error for UnmanagedResource {} 52 | 53 | /// This resource has been destroyed and can no longer be used. 54 | #[derive(Debug)] 55 | pub struct DeadResource; 56 | 57 | impl std::fmt::Display for DeadResource { 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | f.write_str("This resource has been destroyed and can no longer be used.") 60 | } 61 | } 62 | 63 | impl std::error::Error for DeadResource {} 64 | -------------------------------------------------------------------------------- /src/utils/sealed_file.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | fs::File, 4 | io::Write, 5 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}, 6 | }; 7 | 8 | /// A file whose fd cannot be written by other processes 9 | /// 10 | /// This mechanism is useful for giving clients access to large amounts of 11 | /// information such as keymaps without them being able to write to the handle. 12 | /// 13 | /// On Linux, Android, and FreeBSD, this uses a sealed memfd. On other platforms 14 | /// it creates a POSIX shared memory object with `shm_open`, opens a read-only 15 | /// copy, and unlinks it. 16 | #[derive(Debug)] 17 | pub struct SealedFile { 18 | file: File, 19 | size: usize, 20 | } 21 | 22 | impl SealedFile { 23 | /// Create a `[SealedFile]` with the given nul-terminated C string. 24 | pub fn with_content(name: &CStr, contents: &CStr) -> Result { 25 | Self::with_data(name, contents.to_bytes_with_nul()) 26 | } 27 | 28 | /// Create a `[SealedFile]` with the given binary data. 29 | #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))] 30 | pub fn with_data(name: &CStr, data: &[u8]) -> Result { 31 | use rustix::fs::{MemfdFlags, SealFlags}; 32 | use std::io::Seek; 33 | 34 | let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)?; 35 | 36 | let mut file: File = fd.into(); 37 | file.write_all(data)?; 38 | file.flush()?; 39 | 40 | file.seek(std::io::SeekFrom::Start(0))?; 41 | 42 | rustix::fs::fcntl_add_seals( 43 | &file, 44 | SealFlags::SEAL | SealFlags::SHRINK | SealFlags::GROW | SealFlags::WRITE, 45 | )?; 46 | 47 | Ok(Self { 48 | file, 49 | size: data.len(), 50 | }) 51 | } 52 | 53 | /// Create a `[SealedFile]` with the given binary data. 54 | #[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "android")))] 55 | pub fn with_data(name: &CStr, data: &[u8]) -> Result { 56 | use rand::{distributions::Alphanumeric, Rng}; 57 | use rustix::{ 58 | io::Errno, 59 | shm::{self, Mode}, 60 | }; 61 | 62 | let mut rng = rand::thread_rng(); 63 | 64 | // `memfd_create` isn't available. Instead, try `shm_open` with a randomized name, and 65 | // loop a couple times if it exists. 66 | let mut n = 0; 67 | let (shm_name, mut file) = loop { 68 | let mut shm_name = name.to_bytes().to_owned(); 69 | shm_name.push(b'-'); 70 | shm_name.extend((0..7).map(|_| rng.sample(Alphanumeric))); 71 | let fd = shm::open( 72 | shm_name.as_slice(), 73 | shm::OFlags::RDWR | shm::OFlags::CREATE | shm::OFlags::EXCL, 74 | Mode::RWXU, 75 | ); 76 | if !matches!(fd, Err(Errno::EXIST)) || n > 3 { 77 | break (shm_name, File::from(fd?)); 78 | } 79 | n += 1; 80 | }; 81 | 82 | // Sealing isn't available, so re-open read-only. 83 | let fd_rdonly = shm::open(shm_name.as_slice(), shm::OFlags::RDONLY, Mode::empty())?; 84 | let file_rdonly = File::from(fd_rdonly); 85 | 86 | // Unlink so another process can't open shm file. 87 | let _ = shm::unlink(shm_name.as_slice()); 88 | 89 | file.write_all(data)?; 90 | file.flush()?; 91 | 92 | Ok(Self { 93 | file: file_rdonly, 94 | size: data.len(), 95 | }) 96 | } 97 | 98 | /// Size of the data contained in the sealed file. 99 | pub fn size(&self) -> usize { 100 | self.size 101 | } 102 | } 103 | 104 | impl AsRawFd for SealedFile { 105 | fn as_raw_fd(&self) -> RawFd { 106 | self.file.as_raw_fd() 107 | } 108 | } 109 | 110 | impl AsFd for SealedFile { 111 | fn as_fd(&self) -> BorrowedFd<'_> { 112 | self.file.as_fd() 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/utils/serial.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU32, Ordering}; 2 | 3 | /// A global [`SerialCounter`] for use in your compositor. 4 | /// 5 | /// Is is also used internally by some parts of Smithay. 6 | pub static SERIAL_COUNTER: SerialCounter = SerialCounter::new(); 7 | 8 | /// A serial type, whose comparison takes into account the wrapping-around behavior of the 9 | /// underlying counter. 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct Serial(pub(crate) u32); 12 | 13 | impl PartialEq for Serial { 14 | #[inline] 15 | fn eq(&self, other: &Self) -> bool { 16 | self.0 == other.0 17 | } 18 | } 19 | 20 | impl Eq for Serial {} 21 | 22 | impl PartialOrd for Serial { 23 | #[inline] 24 | fn partial_cmp(&self, other: &Self) -> Option { 25 | let distance = if self.0 > other.0 { 26 | self.0 - other.0 27 | } else { 28 | other.0 - self.0 29 | }; 30 | if distance < u32::MAX / 2 { 31 | self.0.partial_cmp(&other.0) 32 | } else { 33 | // wrap-around occurred, invert comparison 34 | other.0.partial_cmp(&self.0) 35 | } 36 | } 37 | } 38 | 39 | impl From for Serial { 40 | #[inline] 41 | fn from(n: u32) -> Self { 42 | Serial(n) 43 | } 44 | } 45 | 46 | impl From for u32 { 47 | #[inline] 48 | fn from(serial: Serial) -> u32 { 49 | serial.0 50 | } 51 | } 52 | 53 | impl Serial { 54 | /// Checks if a serial was generated after or is equal to another given serial 55 | #[inline] 56 | pub fn is_no_older_than(&self, other: &Serial) -> bool { 57 | other <= self 58 | } 59 | } 60 | 61 | /// A counter for generating serials, for use in the client protocol 62 | /// 63 | /// A global instance of this counter is available as the `SERIAL_COUNTER` 64 | /// static. It is recommended to only use this global counter to ensure the 65 | /// uniqueness of serials. 66 | /// 67 | /// The counter will wrap around on overflow, ensuring it can run for as long 68 | /// as needed. 69 | #[derive(Debug)] 70 | pub struct SerialCounter { 71 | serial: AtomicU32, 72 | } 73 | 74 | impl Default for SerialCounter { 75 | #[inline] 76 | fn default() -> Self { 77 | Self::new() 78 | } 79 | } 80 | 81 | impl SerialCounter { 82 | /// Create a new counter starting at `1` 83 | pub const fn new() -> Self { 84 | Self { 85 | serial: AtomicU32::new(1), 86 | } 87 | } 88 | 89 | /// Retrieve the next serial from the counter 90 | pub fn next_serial(&self) -> Serial { 91 | let _ = self 92 | .serial 93 | .compare_exchange(0, 1, Ordering::AcqRel, Ordering::SeqCst); 94 | Serial(self.serial.fetch_add(1, Ordering::AcqRel)) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | fn create_serial_counter(initial_value: u32) -> SerialCounter { 103 | SerialCounter { 104 | serial: AtomicU32::new(initial_value), 105 | } 106 | } 107 | 108 | #[test] 109 | #[allow(clippy::eq_op)] 110 | fn serial_equals_self() { 111 | let counter = create_serial_counter(0); 112 | let serial = counter.next_serial(); 113 | assert!(serial == serial); 114 | } 115 | 116 | #[test] 117 | fn consecutive_serials() { 118 | let counter = create_serial_counter(0); 119 | let serial1 = counter.next_serial(); 120 | let serial2 = counter.next_serial(); 121 | assert!(serial1 < serial2); 122 | } 123 | 124 | #[test] 125 | fn non_consecutive_serials() { 126 | let skip_serials = 147; 127 | 128 | let counter = create_serial_counter(0); 129 | let serial1 = counter.next_serial(); 130 | for _ in 0..skip_serials { 131 | let _ = counter.next_serial(); 132 | } 133 | let serial2 = counter.next_serial(); 134 | assert!(serial1 < serial2); 135 | } 136 | 137 | #[test] 138 | fn serial_wrap_around() { 139 | let counter = create_serial_counter(u32::MAX); 140 | let serial1 = counter.next_serial(); 141 | let serial2 = counter.next_serial(); 142 | 143 | assert!(serial1 == u32::MAX.into()); 144 | assert!(serial2 == 1.into()); 145 | 146 | assert!(serial1 < serial2); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/wayland/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | //! Buffer management traits. 2 | //! 3 | //! This module provides the [`BufferHandler`] trait to notify compositors that a 4 | //! [`WlBuffer`](wayland_server::protocol::wl_buffer::WlBuffer) managed by 5 | //! Smithay has been destroyed. 6 | 7 | use wayland_server::protocol::wl_buffer; 8 | 9 | /// Handler trait for associating data with a [`WlBuffer`](wayland_server::protocol::wl_buffer::WlBuffer). 10 | /// 11 | /// This trait primarily allows compositors to be told when a buffer is destroyed. 12 | /// 13 | /// # For buffer abstractions 14 | /// 15 | /// Buffer abstractions (such as [`shm`](crate::wayland::shm)) should require this trait in their 16 | /// [`delegate_dispatch`](wayland_server::delegate_dispatch) implementations to notify the compositor when a 17 | /// buffer is destroyed. 18 | pub trait BufferHandler { 19 | /// Called when the client has destroyed the buffer. 20 | /// 21 | /// At this point the buffer is no longer usable by Smithay. 22 | fn buffer_destroyed(&mut self, buffer: &wl_buffer::WlBuffer); 23 | } 24 | -------------------------------------------------------------------------------- /src/wayland/idle_inhibit/inhibitor.rs: -------------------------------------------------------------------------------- 1 | //! idle-inhibit inhibitor. 2 | 3 | use _idle_inhibit::zwp_idle_inhibitor_v1::{Request, ZwpIdleInhibitorV1}; 4 | use wayland_protocols::wp::idle_inhibit::zv1::server as _idle_inhibit; 5 | use wayland_server::protocol::wl_surface::WlSurface; 6 | use wayland_server::{Client, DataInit, Dispatch, DisplayHandle}; 7 | 8 | use crate::wayland::idle_inhibit::{IdleInhibitHandler, IdleInhibitManagerState}; 9 | 10 | /// State of zwp_idle_inhibitor_v1. 11 | #[derive(Debug)] 12 | pub struct IdleInhibitorState { 13 | surface: WlSurface, 14 | } 15 | 16 | impl IdleInhibitorState { 17 | /// Create `zwp_idle_inhibitor_v1` state. 18 | pub fn new(surface: WlSurface) -> Self { 19 | Self { surface } 20 | } 21 | } 22 | 23 | impl Dispatch for IdleInhibitManagerState 24 | where 25 | D: Dispatch, 26 | D: IdleInhibitHandler, 27 | D: 'static, 28 | { 29 | fn request( 30 | state: &mut D, 31 | _client: &Client, 32 | _inhibitor: &ZwpIdleInhibitorV1, 33 | request: Request, 34 | data: &IdleInhibitorState, 35 | _display: &DisplayHandle, 36 | _data_init: &mut DataInit<'_, D>, 37 | ) { 38 | match request { 39 | Request::Destroy => state.uninhibit(data.surface.clone()), 40 | _ => unreachable!(), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/wayland/input_method/input_method_keyboard_grab.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use wayland_protocols_misc::zwp_input_method_v2::server::zwp_input_method_keyboard_grab_v2::{ 7 | self, ZwpInputMethodKeyboardGrabV2, 8 | }; 9 | use wayland_server::backend::ClientId; 10 | use wayland_server::Dispatch; 11 | 12 | use crate::input::{ 13 | keyboard::{ 14 | GrabStartData as KeyboardGrabStartData, KeyboardGrab, KeyboardHandle, KeyboardInnerHandle, 15 | ModifiersState, 16 | }, 17 | SeatHandler, 18 | }; 19 | use crate::wayland::text_input::TextInputHandle; 20 | use crate::{ 21 | backend::input::{KeyState, Keycode}, 22 | utils::Serial, 23 | }; 24 | 25 | use super::InputMethodManagerState; 26 | 27 | #[derive(Default, Debug)] 28 | pub(crate) struct InputMethodKeyboard { 29 | pub grab: Option, 30 | pub text_input_handle: TextInputHandle, 31 | } 32 | 33 | /// Handle to an input method instance 34 | #[derive(Default, Debug, Clone)] 35 | pub struct InputMethodKeyboardGrab { 36 | pub(crate) inner: Arc>, 37 | } 38 | 39 | impl KeyboardGrab for InputMethodKeyboardGrab 40 | where 41 | D: SeatHandler + 'static, 42 | { 43 | fn input( 44 | &mut self, 45 | _data: &mut D, 46 | _handle: &mut KeyboardInnerHandle<'_, D>, 47 | keycode: Keycode, 48 | key_state: KeyState, 49 | modifiers: Option, 50 | serial: Serial, 51 | time: u32, 52 | ) { 53 | let inner = self.inner.lock().unwrap(); 54 | let keyboard = inner.grab.as_ref().unwrap(); 55 | inner 56 | .text_input_handle 57 | .active_text_input_serial_or_default(serial.0, |serial| { 58 | keyboard.key(serial, time, keycode.raw() - 8, key_state.into()); 59 | if let Some(serialized) = modifiers.map(|m| m.serialized) { 60 | keyboard.modifiers( 61 | serial, 62 | serialized.depressed, 63 | serialized.latched, 64 | serialized.locked, 65 | serialized.layout_effective, 66 | ) 67 | } 68 | }); 69 | } 70 | 71 | fn set_focus( 72 | &mut self, 73 | data: &mut D, 74 | handle: &mut KeyboardInnerHandle<'_, D>, 75 | focus: Option<::KeyboardFocus>, 76 | serial: crate::utils::Serial, 77 | ) { 78 | handle.set_focus(data, focus, serial) 79 | } 80 | 81 | fn start_data(&self) -> &KeyboardGrabStartData { 82 | &KeyboardGrabStartData { focus: None } 83 | } 84 | 85 | fn unset(&mut self, _data: &mut D) {} 86 | } 87 | 88 | /// User data of ZwpInputKeyboardGrabV2 object 89 | pub struct InputMethodKeyboardUserData { 90 | pub(super) handle: InputMethodKeyboardGrab, 91 | pub(crate) keyboard_handle: KeyboardHandle, 92 | } 93 | 94 | impl fmt::Debug for InputMethodKeyboardUserData { 95 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 96 | f.debug_struct("InputMethodKeyboardUserData") 97 | .field("handle", &self.handle) 98 | .field("keyboard_handle", &self.keyboard_handle) 99 | .finish() 100 | } 101 | } 102 | 103 | impl Dispatch, D> 104 | for InputMethodManagerState 105 | { 106 | fn destroyed( 107 | state: &mut D, 108 | _client: ClientId, 109 | _object: &ZwpInputMethodKeyboardGrabV2, 110 | data: &InputMethodKeyboardUserData, 111 | ) { 112 | data.handle.inner.lock().unwrap().grab = None; 113 | data.keyboard_handle.unset_grab(state); 114 | } 115 | 116 | fn request( 117 | _state: &mut D, 118 | _client: &wayland_server::Client, 119 | _resource: &ZwpInputMethodKeyboardGrabV2, 120 | request: zwp_input_method_keyboard_grab_v2::Request, 121 | _data: &InputMethodKeyboardUserData, 122 | _dhandle: &wayland_server::DisplayHandle, 123 | _data_init: &mut wayland_server::DataInit<'_, D>, 124 | ) { 125 | match request { 126 | zwp_input_method_keyboard_grab_v2::Request::Release => { 127 | // Nothing to do 128 | } 129 | _ => unreachable!(), 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/wayland/mod.rs: -------------------------------------------------------------------------------- 1 | //! Protocol-related utilities 2 | //! 3 | //! This module contains several handlers to manage the Wayland protocol 4 | //! and the clients. 5 | //! 6 | //! ## General structure 7 | //! 8 | //! Most utilities provided in this module work in the same way: 9 | //! 10 | //! - A module specific `*State` struct will take the wayland display as argument and 11 | //! insert one or more globals into it through its constructor. 12 | //! - The module-`State` will have to be stored inside your global compositor state. 13 | //! (The same type you parametrized [`wayland_server::Display`] over.) 14 | //! - You need to implement a module-specific `*Handler`-trait for your compositor state. 15 | //! This implementation will be called when wayland events require custom handling. 16 | //! - Call the matching `delegate_*!` macro from smithay on your state to implement 17 | //! some required `wayland_server` traits. 18 | //! - If you want to remove a previously inserted global, just drop the `*State`. 19 | //! 20 | //! ## Provided helpers 21 | //! 22 | //! ### Core functionality 23 | //! 24 | //! The most fundamental module is the [`compositor`] module, which provides the necessary 25 | //! logic to handle the fundamental component by which clients build their windows: surfaces. 26 | //! Following this, the [`shell`] module contains the logic allowing clients to use their 27 | //! surface to build concrete windows with the usual interactions. Different kind of shells 28 | //! exist, but in general you will want to support at least the [`xdg`](shell::xdg) variant, 29 | //! which is the standard used by most applications. 30 | //! 31 | //! Then, the [`seat`] module contains logic related to input handling. These helpers are used 32 | //! to forward input (such as pointer action or keystrokes) to clients, and manage the input 33 | //! focus of clients. Tightly coupled with it is the [`selection`] module, which handles 34 | //! cross-client interactions such as accessing the clipboard, or drag'n'drop actions. 35 | //! 36 | //! The [`shm`] module provides the necessary logic for client to provide buffers defining the 37 | //! contents of their windows using shared memory. This is the main mechanism used by clients 38 | //! that are not hardware accelerated. As a complement, the [`dmabuf`] module provides support 39 | //! hardware-accelerated clients; it is tightly linked to the 40 | //! [`backend::allocator`](crate::backend::allocator) module. 41 | //! 42 | //! The [`output`] module helps forwarding to clients information about the display monitors that 43 | //! are available. This notably plays a key role in HiDPI handling, and more generally notifying 44 | //! clients about whether they are currently visible or not (allowing them to stop drawing if they 45 | //! are not, for example). 46 | //! 47 | 48 | pub mod alpha_modifier; 49 | pub mod buffer; 50 | pub mod commit_timing; 51 | pub mod compositor; 52 | pub mod content_type; 53 | pub mod cursor_shape; 54 | pub mod dmabuf; 55 | #[cfg(feature = "backend_drm")] 56 | pub mod drm_lease; 57 | #[cfg(feature = "backend_drm")] 58 | pub mod drm_syncobj; 59 | pub mod fifo; 60 | pub mod foreign_toplevel_list; 61 | pub mod fractional_scale; 62 | pub mod idle_inhibit; 63 | pub mod idle_notify; 64 | pub mod input_method; 65 | pub mod keyboard_shortcuts_inhibit; 66 | pub mod output; 67 | pub mod pointer_constraints; 68 | pub mod pointer_gestures; 69 | pub mod presentation; 70 | pub mod relative_pointer; 71 | pub mod seat; 72 | pub mod security_context; 73 | pub mod selection; 74 | pub mod session_lock; 75 | pub mod shell; 76 | pub mod shm; 77 | pub mod single_pixel_buffer; 78 | pub mod socket; 79 | pub mod tablet_manager; 80 | pub mod text_input; 81 | pub mod viewporter; 82 | pub mod virtual_keyboard; 83 | pub mod xdg_activation; 84 | pub mod xdg_foreign; 85 | pub mod xdg_system_bell; 86 | pub mod xdg_toplevel_icon; 87 | pub mod xdg_toplevel_tag; 88 | #[cfg(feature = "xwayland")] 89 | pub mod xwayland_keyboard_grab; 90 | #[cfg(feature = "xwayland")] 91 | pub mod xwayland_shell; 92 | -------------------------------------------------------------------------------- /src/wayland/security_context/listener_source.rs: -------------------------------------------------------------------------------- 1 | // TODO calloop source 2 | // - poll POLLHUP/POLLIN on close_fd 3 | // - poll for accept 4 | 5 | use calloop::{ 6 | generic::Generic, EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory, 7 | }; 8 | use std::{ 9 | io, 10 | os::unix::{ 11 | io::OwnedFd, 12 | net::{UnixListener, UnixStream}, 13 | }, 14 | }; 15 | 16 | /// Security context listener event source. 17 | /// 18 | /// This implements [`EventSource`] and may be inserted into an event loop. 19 | #[derive(Debug)] 20 | pub struct SecurityContextListenerSource { 21 | listen_fd: Generic, 22 | close_fd: Generic, 23 | } 24 | 25 | impl SecurityContextListenerSource { 26 | pub(super) fn new(listen_fd: UnixListener, close_fd: OwnedFd) -> io::Result { 27 | listen_fd.set_nonblocking(true)?; 28 | let listen_fd = Generic::new(listen_fd, Interest::READ, Mode::Level); 29 | // close_fd.set_nonblocking(true); 30 | // XXX POLLHUP 31 | let close_fd = Generic::new(close_fd, Interest::READ, Mode::Level); 32 | Ok(Self { listen_fd, close_fd }) 33 | } 34 | } 35 | 36 | impl EventSource for SecurityContextListenerSource { 37 | type Event = UnixStream; 38 | type Metadata = (); 39 | type Ret = (); 40 | type Error = io::Error; 41 | 42 | fn process_events( 43 | &mut self, 44 | readiness: Readiness, 45 | token: Token, 46 | mut callback: F, 47 | ) -> io::Result { 48 | self.listen_fd.process_events(readiness, token, |_, socket| { 49 | loop { 50 | match socket.accept() { 51 | Ok((stream, _)) => callback(stream, &mut ()), 52 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 53 | break; 54 | } 55 | Err(e) => { 56 | return Err(e); 57 | } 58 | } 59 | } 60 | Ok(PostAction::Continue) 61 | })?; 62 | 63 | self.close_fd 64 | .process_events(readiness, token, |_, _fd| Ok(PostAction::Remove)) 65 | } 66 | 67 | fn register(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> calloop::Result<()> { 68 | self.listen_fd.register(poll, token_factory)?; 69 | self.close_fd.register(poll, token_factory) 70 | } 71 | 72 | fn reregister(&mut self, poll: &mut Poll, token_factory: &mut TokenFactory) -> calloop::Result<()> { 73 | self.listen_fd.reregister(poll, token_factory)?; 74 | self.close_fd.reregister(poll, token_factory) 75 | } 76 | 77 | fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { 78 | self.close_fd.unregister(poll)?; 79 | self.listen_fd.unregister(poll) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/wayland/selection/data_device/source.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | use tracing::error; 3 | 4 | use wayland_server::{ 5 | backend::ClientId, 6 | protocol::wl_data_source::{self}, 7 | protocol::{wl_data_device_manager::DndAction, wl_data_source::WlDataSource}, 8 | Dispatch, DisplayHandle, Resource, 9 | }; 10 | 11 | use crate::utils::{alive_tracker::AliveTracker, IsAlive}; 12 | 13 | use super::{DataDeviceHandler, DataDeviceState}; 14 | 15 | /// The metadata describing a data source 16 | #[derive(Debug, Clone)] 17 | pub struct SourceMetadata { 18 | /// The MIME types supported by this source 19 | pub mime_types: Vec, 20 | /// The Drag'n'Drop actions supported by this source 21 | pub dnd_action: DndAction, 22 | } 23 | 24 | impl Default for SourceMetadata { 25 | fn default() -> Self { 26 | Self { 27 | mime_types: Vec::new(), 28 | dnd_action: DndAction::None, 29 | } 30 | } 31 | } 32 | 33 | #[doc(hidden)] 34 | #[derive(Debug)] 35 | pub struct DataSourceUserData { 36 | pub(crate) inner: Mutex, 37 | alive_tracker: AliveTracker, 38 | } 39 | 40 | impl DataSourceUserData { 41 | pub(super) fn new() -> Self { 42 | Self { 43 | inner: Default::default(), 44 | alive_tracker: Default::default(), 45 | } 46 | } 47 | } 48 | 49 | impl Dispatch for DataDeviceState 50 | where 51 | D: Dispatch, 52 | D: DataDeviceHandler, 53 | D: 'static, 54 | { 55 | fn request( 56 | _state: &mut D, 57 | _client: &wayland_server::Client, 58 | _resource: &WlDataSource, 59 | request: wl_data_source::Request, 60 | data: &DataSourceUserData, 61 | _dhandle: &DisplayHandle, 62 | _data_init: &mut wayland_server::DataInit<'_, D>, 63 | ) { 64 | let mut data = data.inner.lock().unwrap(); 65 | 66 | match request { 67 | wl_data_source::Request::Offer { mime_type } => { 68 | data.mime_types.push(mime_type); 69 | } 70 | wl_data_source::Request::SetActions { dnd_actions } => match dnd_actions { 71 | wayland_server::WEnum::Value(dnd_actions) => { 72 | data.dnd_action = dnd_actions; 73 | } 74 | wayland_server::WEnum::Unknown(action) => { 75 | error!("Unknown dnd_action: {:?}", action); 76 | } 77 | }, 78 | wl_data_source::Request::Destroy => {} 79 | _ => unreachable!(), 80 | } 81 | } 82 | 83 | fn destroyed(_state: &mut D, _client: ClientId, _resource: &WlDataSource, data: &DataSourceUserData) { 84 | data.alive_tracker.destroy_notify(); 85 | } 86 | } 87 | 88 | impl IsAlive for WlDataSource { 89 | #[inline] 90 | fn alive(&self) -> bool { 91 | let data: &DataSourceUserData = self.data().unwrap(); 92 | data.alive_tracker.alive() 93 | } 94 | } 95 | 96 | /// Access the metadata of a data source 97 | pub fn with_source_metadata T>( 98 | source: &WlDataSource, 99 | f: F, 100 | ) -> Result { 101 | match source.data::() { 102 | Some(data) => Ok(f(&data.inner.lock().unwrap())), 103 | None => Err(crate::utils::UnmanagedResource), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/wayland/selection/ext_data_control/device.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::{ 4 | self, ExtDataControlDeviceV1, 5 | }; 6 | use wayland_server::protocol::wl_seat::WlSeat; 7 | use wayland_server::{Client, Dispatch, DisplayHandle}; 8 | 9 | use crate::input::Seat; 10 | use crate::wayland::selection::device::SelectionDevice; 11 | use crate::wayland::selection::offer::OfferReplySource; 12 | use crate::wayland::selection::seat_data::SeatData; 13 | use crate::wayland::selection::source::SelectionSourceProvider; 14 | use crate::wayland::selection::{SelectionSource, SelectionTarget}; 15 | 16 | use super::{DataControlHandler, DataControlState}; 17 | 18 | #[doc(hidden)] 19 | #[derive(Debug)] 20 | pub struct ExtDataControlDeviceUserData { 21 | pub(crate) primary: bool, 22 | pub(crate) wl_seat: WlSeat, 23 | } 24 | 25 | impl Dispatch for DataControlState 26 | where 27 | D: Dispatch, 28 | D: DataControlHandler, 29 | D: 'static, 30 | { 31 | fn request( 32 | handler: &mut D, 33 | _client: &Client, 34 | resource: &ExtDataControlDeviceV1, 35 | request: ::Request, 36 | data: &ExtDataControlDeviceUserData, 37 | dh: &DisplayHandle, 38 | _: &mut wayland_server::DataInit<'_, D>, 39 | ) { 40 | let seat = match Seat::::from_resource(&data.wl_seat) { 41 | Some(seat) => seat, 42 | None => return, 43 | }; 44 | 45 | match request { 46 | ext_data_control_device_v1::Request::SetSelection { source, .. } => { 47 | seat.user_data() 48 | .insert_if_missing(|| RefCell::new(SeatData::::new())); 49 | 50 | let source = source.map(SelectionSourceProvider::ExtDataControl); 51 | 52 | handler.new_selection( 53 | SelectionTarget::Clipboard, 54 | source.clone().map(|provider| SelectionSource { provider }), 55 | seat.clone(), 56 | ); 57 | 58 | seat.user_data() 59 | .get::>>() 60 | .unwrap() 61 | .borrow_mut() 62 | .set_clipboard_selection::(dh, source.map(OfferReplySource::Client)); 63 | } 64 | ext_data_control_device_v1::Request::SetPrimarySelection { source, .. } => { 65 | // When the primary selection is disabled, we should simply ignore the requests. 66 | if !data.primary { 67 | return; 68 | } 69 | 70 | seat.user_data() 71 | .insert_if_missing(|| RefCell::new(SeatData::::new())); 72 | 73 | let source = source.map(SelectionSourceProvider::ExtDataControl); 74 | 75 | handler.new_selection( 76 | SelectionTarget::Primary, 77 | source.clone().map(|provider| SelectionSource { provider }), 78 | seat.clone(), 79 | ); 80 | 81 | seat.user_data() 82 | .get::>>() 83 | .unwrap() 84 | .borrow_mut() 85 | .set_primary_selection::(dh, source.map(OfferReplySource::Client)); 86 | } 87 | ext_data_control_device_v1::Request::Destroy => seat 88 | .user_data() 89 | .get::>>() 90 | .unwrap() 91 | .borrow_mut() 92 | .retain_devices(|ndd| match ndd { 93 | SelectionDevice::ExtDataControl(ndd) => ndd != resource, 94 | _ => true, 95 | }), 96 | 97 | _ => unreachable!(), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/wayland/selection/ext_data_control/source.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use wayland_server::backend::ClientId; 4 | use wayland_server::{Dispatch, DisplayHandle, Resource}; 5 | 6 | use crate::utils::alive_tracker::AliveTracker; 7 | use crate::utils::IsAlive; 8 | 9 | use wayland_protocols::ext::data_control::v1::server::ext_data_control_source_v1::{ 10 | self, ExtDataControlSourceV1, 11 | }; 12 | 13 | use super::{DataControlHandler, DataControlState}; 14 | 15 | #[doc(hidden)] 16 | #[derive(Default, Debug)] 17 | pub struct ExtDataControlSourceUserData { 18 | pub(crate) inner: Mutex, 19 | alive_tracker: AliveTracker, 20 | } 21 | 22 | impl ExtDataControlSourceUserData { 23 | pub(crate) fn new() -> Self { 24 | Self::default() 25 | } 26 | } 27 | 28 | /// The metadata describing a data source 29 | #[derive(Debug, Default, Clone)] 30 | pub struct SourceMetadata { 31 | /// The MIME types supported by this source 32 | pub mime_types: Vec, 33 | } 34 | 35 | impl Dispatch for DataControlState 36 | where 37 | D: Dispatch, 38 | D: DataControlHandler, 39 | D: 'static, 40 | { 41 | fn request( 42 | _state: &mut D, 43 | _client: &wayland_server::Client, 44 | _resource: &ExtDataControlSourceV1, 45 | request: ::Request, 46 | data: &ExtDataControlSourceUserData, 47 | _dhandle: &DisplayHandle, 48 | _data_init: &mut wayland_server::DataInit<'_, D>, 49 | ) { 50 | match request { 51 | ext_data_control_source_v1::Request::Offer { mime_type } => { 52 | let mut data = data.inner.lock().unwrap(); 53 | data.mime_types.push(mime_type); 54 | } 55 | ext_data_control_source_v1::Request::Destroy => (), 56 | _ => unreachable!(), 57 | } 58 | } 59 | 60 | fn destroyed( 61 | _state: &mut D, 62 | _client: ClientId, 63 | _resource: &ExtDataControlSourceV1, 64 | data: &ExtDataControlSourceUserData, 65 | ) { 66 | data.alive_tracker.destroy_notify(); 67 | } 68 | } 69 | 70 | impl IsAlive for ExtDataControlSourceV1 { 71 | #[inline] 72 | fn alive(&self) -> bool { 73 | let data: &ExtDataControlSourceUserData = self.data().unwrap(); 74 | data.alive_tracker.alive() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/wayland/selection/primary_selection/device.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use tracing::debug; 4 | use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::{ 5 | self as primary_device, ZwpPrimarySelectionDeviceV1 as PrimaryDevice, 6 | }; 7 | use wayland_server::{protocol::wl_seat::WlSeat, Client, DataInit, Dispatch, DisplayHandle, Resource}; 8 | 9 | use crate::{ 10 | input::{Seat, SeatHandler}, 11 | wayland::{ 12 | seat::WaylandFocus, 13 | selection::{ 14 | device::SelectionDevice, 15 | offer::OfferReplySource, 16 | seat_data::SeatData, 17 | source::{SelectionSource, SelectionSourceProvider}, 18 | SelectionHandler, SelectionTarget, 19 | }, 20 | }, 21 | }; 22 | 23 | use super::PrimarySelectionState; 24 | 25 | #[doc(hidden)] 26 | #[derive(Debug)] 27 | pub struct PrimaryDeviceUserData { 28 | pub(crate) wl_seat: WlSeat, 29 | } 30 | 31 | impl Dispatch for PrimarySelectionState 32 | where 33 | D: Dispatch, 34 | D: SelectionHandler, 35 | D: SeatHandler, 36 | ::KeyboardFocus: WaylandFocus, 37 | D: 'static, 38 | { 39 | fn request( 40 | handler: &mut D, 41 | client: &Client, 42 | resource: &PrimaryDevice, 43 | request: primary_device::Request, 44 | data: &PrimaryDeviceUserData, 45 | dh: &DisplayHandle, 46 | _data_init: &mut DataInit<'_, D>, 47 | ) { 48 | let seat = match Seat::::from_resource(&data.wl_seat) { 49 | Some(seat) => seat, 50 | None => return, 51 | }; 52 | 53 | match request { 54 | primary_device::Request::SetSelection { source, .. } => { 55 | let seat_data = match seat.get_keyboard() { 56 | Some(keyboard) if keyboard.client_of_object_has_focus(&resource.id()) => seat 57 | .user_data() 58 | .get::>>() 59 | .unwrap(), 60 | _ => { 61 | debug!( 62 | client = ?client, 63 | "denying setting selection by a non-focused client" 64 | ); 65 | return; 66 | } 67 | }; 68 | 69 | let source = source.map(SelectionSourceProvider::Primary); 70 | 71 | handler.new_selection( 72 | SelectionTarget::Primary, 73 | source.clone().map(|provider| SelectionSource { provider }), 74 | seat.clone(), 75 | ); 76 | 77 | // The client has kbd focus, it can set the selection 78 | seat_data 79 | .borrow_mut() 80 | .set_primary_selection::(dh, source.map(OfferReplySource::Client)); 81 | } 82 | primary_device::Request::Destroy => seat 83 | .user_data() 84 | .get::>>() 85 | .unwrap() 86 | .borrow_mut() 87 | .retain_devices(|ndd| match ndd { 88 | SelectionDevice::Primary(ndd) => ndd != resource, 89 | _ => true, 90 | }), 91 | _ => unreachable!(), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/wayland/selection/primary_selection/source.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::{ 4 | self as primary_source, ZwpPrimarySelectionSourceV1 as PrimarySource, 5 | }; 6 | use wayland_server::{backend::ClientId, Dispatch, DisplayHandle, Resource}; 7 | 8 | use crate::utils::{alive_tracker::AliveTracker, IsAlive}; 9 | 10 | use super::{PrimarySelectionHandler, PrimarySelectionState}; 11 | 12 | /// The metadata describing a data source 13 | #[derive(Debug, Default, Clone)] 14 | pub struct SourceMetadata { 15 | /// The MIME types supported by this source 16 | pub mime_types: Vec, 17 | } 18 | 19 | #[doc(hidden)] 20 | #[derive(Debug)] 21 | pub struct PrimarySourceUserData { 22 | pub(crate) inner: Mutex, 23 | alive_tracker: AliveTracker, 24 | } 25 | 26 | impl PrimarySourceUserData { 27 | pub(super) fn new() -> Self { 28 | Self { 29 | inner: Default::default(), 30 | alive_tracker: Default::default(), 31 | } 32 | } 33 | } 34 | 35 | impl Dispatch for PrimarySelectionState 36 | where 37 | D: Dispatch, 38 | D: PrimarySelectionHandler, 39 | D: 'static, 40 | { 41 | fn request( 42 | state: &mut D, 43 | _client: &wayland_server::Client, 44 | _resource: &PrimarySource, 45 | request: primary_source::Request, 46 | data: &PrimarySourceUserData, 47 | _dhandle: &DisplayHandle, 48 | _data_init: &mut wayland_server::DataInit<'_, D>, 49 | ) { 50 | let _primary_selection_state = state.primary_selection_state(); 51 | let mut data = data.inner.lock().unwrap(); 52 | 53 | match request { 54 | primary_source::Request::Offer { mime_type } => { 55 | data.mime_types.push(mime_type); 56 | } 57 | primary_source::Request::Destroy => {} 58 | _ => unreachable!(), 59 | } 60 | } 61 | 62 | fn destroyed(_state: &mut D, _client: ClientId, _resource: &PrimarySource, data: &PrimarySourceUserData) { 63 | data.alive_tracker.destroy_notify(); 64 | } 65 | } 66 | 67 | impl IsAlive for PrimarySource { 68 | #[inline] 69 | fn alive(&self) -> bool { 70 | let data: &PrimarySourceUserData = self.data().unwrap(); 71 | data.alive_tracker.alive() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/wayland/selection/wlr_data_control/device.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::sync::Arc; 3 | 4 | use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::{ 5 | self, ZwlrDataControlDeviceV1, 6 | }; 7 | use wayland_server::protocol::wl_seat::WlSeat; 8 | use wayland_server::{Client, Dispatch, DisplayHandle}; 9 | 10 | use crate::input::Seat; 11 | use crate::wayland::selection::device::SelectionDevice; 12 | use crate::wayland::selection::offer::OfferReplySource; 13 | use crate::wayland::selection::seat_data::SeatData; 14 | use crate::wayland::selection::source::SelectionSourceProvider; 15 | use crate::wayland::selection::{SelectionSource, SelectionTarget}; 16 | 17 | use super::{DataControlHandler, DataControlState}; 18 | 19 | #[allow(missing_debug_implementations)] 20 | #[doc(hidden)] 21 | pub struct DataControlDeviceUserData { 22 | pub(crate) primary_selection_filter: Arc Fn(&'c Client) -> bool + Send + Sync>>, 23 | pub(crate) wl_seat: WlSeat, 24 | } 25 | 26 | impl Dispatch for DataControlState 27 | where 28 | D: Dispatch, 29 | D: DataControlHandler, 30 | D: 'static, 31 | { 32 | fn request( 33 | handler: &mut D, 34 | client: &Client, 35 | resource: &ZwlrDataControlDeviceV1, 36 | request: ::Request, 37 | data: &DataControlDeviceUserData, 38 | dh: &DisplayHandle, 39 | _: &mut wayland_server::DataInit<'_, D>, 40 | ) { 41 | let seat = match Seat::::from_resource(&data.wl_seat) { 42 | Some(seat) => seat, 43 | None => return, 44 | }; 45 | 46 | match request { 47 | zwlr_data_control_device_v1::Request::SetSelection { source, .. } => { 48 | seat.user_data() 49 | .insert_if_missing(|| RefCell::new(SeatData::::new())); 50 | 51 | let source = source.map(SelectionSourceProvider::WlrDataControl); 52 | 53 | handler.new_selection( 54 | SelectionTarget::Clipboard, 55 | source.clone().map(|provider| SelectionSource { provider }), 56 | seat.clone(), 57 | ); 58 | 59 | seat.user_data() 60 | .get::>>() 61 | .unwrap() 62 | .borrow_mut() 63 | .set_clipboard_selection::(dh, source.map(OfferReplySource::Client)); 64 | } 65 | zwlr_data_control_device_v1::Request::SetPrimarySelection { source, .. } => { 66 | // When the primary selection is disabled, we should simply ignore the requests. 67 | if !(*data.primary_selection_filter)(client) { 68 | return; 69 | } 70 | 71 | seat.user_data() 72 | .insert_if_missing(|| RefCell::new(SeatData::::new())); 73 | 74 | let source = source.map(SelectionSourceProvider::WlrDataControl); 75 | 76 | handler.new_selection( 77 | SelectionTarget::Primary, 78 | source.clone().map(|provider| SelectionSource { provider }), 79 | seat.clone(), 80 | ); 81 | 82 | seat.user_data() 83 | .get::>>() 84 | .unwrap() 85 | .borrow_mut() 86 | .set_primary_selection::(dh, source.map(OfferReplySource::Client)); 87 | } 88 | zwlr_data_control_device_v1::Request::Destroy => seat 89 | .user_data() 90 | .get::>>() 91 | .unwrap() 92 | .borrow_mut() 93 | .retain_devices(|ndd| match ndd { 94 | SelectionDevice::WlrDataControl(ndd) => ndd != resource, 95 | _ => true, 96 | }), 97 | 98 | _ => unreachable!(), 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/wayland/selection/wlr_data_control/source.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_source_v1::{ 4 | self, ZwlrDataControlSourceV1, 5 | }; 6 | use wayland_server::backend::ClientId; 7 | use wayland_server::{Dispatch, DisplayHandle, Resource}; 8 | 9 | use crate::utils::alive_tracker::AliveTracker; 10 | use crate::utils::IsAlive; 11 | 12 | use super::{DataControlHandler, DataControlState}; 13 | 14 | #[doc(hidden)] 15 | #[derive(Default, Debug)] 16 | pub struct DataControlSourceUserData { 17 | pub(crate) inner: Mutex, 18 | alive_tracker: AliveTracker, 19 | } 20 | 21 | impl DataControlSourceUserData { 22 | pub(crate) fn new() -> Self { 23 | Self::default() 24 | } 25 | } 26 | 27 | /// The metadata describing a data source 28 | #[derive(Debug, Default, Clone)] 29 | pub struct SourceMetadata { 30 | /// The MIME types supported by this source 31 | pub mime_types: Vec, 32 | } 33 | 34 | impl Dispatch for DataControlState 35 | where 36 | D: Dispatch, 37 | D: DataControlHandler, 38 | D: 'static, 39 | { 40 | fn request( 41 | _state: &mut D, 42 | _client: &wayland_server::Client, 43 | _resource: &ZwlrDataControlSourceV1, 44 | request: ::Request, 45 | data: &DataControlSourceUserData, 46 | _dhandle: &DisplayHandle, 47 | _data_init: &mut wayland_server::DataInit<'_, D>, 48 | ) { 49 | match request { 50 | zwlr_data_control_source_v1::Request::Offer { mime_type } => { 51 | let mut data = data.inner.lock().unwrap(); 52 | data.mime_types.push(mime_type); 53 | } 54 | zwlr_data_control_source_v1::Request::Destroy => (), 55 | _ => unreachable!(), 56 | } 57 | } 58 | 59 | fn destroyed( 60 | _state: &mut D, 61 | _client: ClientId, 62 | _resource: &ZwlrDataControlSourceV1, 63 | data: &DataControlSourceUserData, 64 | ) { 65 | data.alive_tracker.destroy_notify(); 66 | } 67 | } 68 | 69 | impl IsAlive for ZwlrDataControlSourceV1 { 70 | #[inline] 71 | fn alive(&self) -> bool { 72 | let data: &DataControlSourceUserData = self.data().unwrap(); 73 | data.alive_tracker.alive() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/wayland/shell/kde/handlers.rs: -------------------------------------------------------------------------------- 1 | //! Handlers for KDE decoration events. 2 | use tracing::trace; 3 | 4 | use wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration::{ 5 | OrgKdeKwinServerDecoration, Request, 6 | }; 7 | use wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager::{ 8 | OrgKdeKwinServerDecorationManager, Request as ManagerRequest, 9 | }; 10 | use wayland_server::protocol::wl_surface::WlSurface; 11 | use wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource}; 12 | 13 | use crate::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState}; 14 | 15 | use super::decoration::KdeDecorationManagerGlobalData; 16 | 17 | impl GlobalDispatch 18 | for KdeDecorationState 19 | where 20 | D: GlobalDispatch 21 | + Dispatch 22 | + Dispatch 23 | + KdeDecorationHandler 24 | + 'static, 25 | { 26 | fn bind( 27 | state: &mut D, 28 | _dh: &DisplayHandle, 29 | _client: &Client, 30 | resource: New, 31 | _global_data: &KdeDecorationManagerGlobalData, 32 | data_init: &mut DataInit<'_, D>, 33 | ) { 34 | let kde_decoration_manager = data_init.init(resource, ()); 35 | 36 | // Set default decoration mode. 37 | let default_mode = state.kde_decoration_state().default_mode; 38 | kde_decoration_manager.default_mode(default_mode); 39 | 40 | trace!("Bound decoration manager global"); 41 | } 42 | 43 | fn can_view(client: Client, global_data: &KdeDecorationManagerGlobalData) -> bool { 44 | (global_data.filter)(&client) 45 | } 46 | } 47 | 48 | impl Dispatch for KdeDecorationState 49 | where 50 | D: Dispatch 51 | + Dispatch 52 | + Dispatch 53 | + KdeDecorationHandler 54 | + 'static, 55 | { 56 | fn request( 57 | state: &mut D, 58 | _client: &Client, 59 | _kde_decoration_manager: &OrgKdeKwinServerDecorationManager, 60 | request: ManagerRequest, 61 | _data: &(), 62 | _dh: &DisplayHandle, 63 | data_init: &mut DataInit<'_, D>, 64 | ) { 65 | let (id, surface) = match request { 66 | ManagerRequest::Create { id, surface } => (id, surface), 67 | _ => unreachable!(), 68 | }; 69 | 70 | let kde_decoration = data_init.init(id, surface); 71 | 72 | let surface = kde_decoration.data().unwrap(); 73 | state.new_decoration(surface, &kde_decoration); 74 | 75 | trace!(surface = ?surface, "Created decoration object for surface"); 76 | } 77 | } 78 | 79 | impl Dispatch for KdeDecorationState 80 | where 81 | D: Dispatch + KdeDecorationHandler + 'static, 82 | { 83 | fn request( 84 | state: &mut D, 85 | _client: &Client, 86 | kde_decoration: &OrgKdeKwinServerDecoration, 87 | request: Request, 88 | surface: &WlSurface, 89 | _dh: &DisplayHandle, 90 | _data_init: &mut DataInit<'_, D>, 91 | ) { 92 | trace!( 93 | surface = ?surface, 94 | request = ?request, 95 | "Decoration request for surface" 96 | ); 97 | 98 | match request { 99 | Request::RequestMode { mode } => state.request_mode(surface, kde_decoration, mode), 100 | Request::Release => state.release(kde_decoration, surface), 101 | _ => unreachable!(), 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/wayland/shell/kde/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handler utilities for KDE shell protocols. 2 | 3 | pub mod decoration; 4 | mod handlers; 5 | -------------------------------------------------------------------------------- /src/wayland/shell/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handler utilities for the various shell protocols 2 | //! 3 | //! Wayland, via its different protocol extensions, supports different kind of 4 | //! shells. Here a shell represent the logic associated to displaying windows and 5 | //! arranging them on the screen. 6 | //! 7 | //! The shell protocols thus define what kind of interactions a client can have with 8 | //! the compositor to properly display its contents on the screen. 9 | //! 10 | //! Smithay currently provides three of them: 11 | //! 12 | //! - The [`xdg`](xdg/index.html) module provides handlers for the `xdg_shell` protocol, which is 13 | //! the current standard for desktop apps 14 | //! - The [`wlr_layer`](wlr_layer/index.html) module provides handlers for the `wlr_layer_shell` 15 | //! protocol, which is for windows rendering above/below normal XDG windows 16 | //! - The [`kde`](kde/index.html) module provides handlers for KDE-specific protocols 17 | 18 | use crate::{utils::Serial, wayland::compositor}; 19 | use thiserror::Error; 20 | use wayland_server::protocol::wl_surface::WlSurface; 21 | use xdg::XdgToplevelSurfaceData; 22 | 23 | pub mod kde; 24 | pub mod wlr_layer; 25 | pub mod xdg; 26 | 27 | /// Represents the possible errors returned from 28 | /// a surface ping 29 | #[derive(Debug, Error)] 30 | pub enum PingError { 31 | /// The operation failed because the underlying surface has been destroyed 32 | #[error("the ping failed cause the underlying surface has been destroyed")] 33 | DeadSurface, 34 | /// There is already a pending ping 35 | #[error("there is already a ping pending `{0:?}`")] 36 | PingAlreadyPending(Serial), 37 | } 38 | 39 | /// Returns true if the surface is toplevel equivalent. 40 | /// 41 | /// Currently is method only checks if the surface roles is `xdg_toplevel`, 42 | /// but may be extended to other shell-protocols in the future, if applicable. 43 | pub fn is_toplevel_equivalent(surface: &WlSurface) -> bool { 44 | // xdg_toplevel is toplevel like, so verify if the role matches. 45 | let role = compositor::get_role(surface); 46 | 47 | // When changing this, don't forget to change the check in is_valid_parent() below. 48 | matches!(role, Some(xdg::XDG_TOPLEVEL_ROLE)) 49 | } 50 | 51 | /// Returns true if the `parent` is valid to set for `child`. 52 | /// 53 | /// This will check that `parent` is toplevel equivalent, then make sure that it doesn't introduce 54 | /// a parent loop. 55 | pub fn is_valid_parent(child: &WlSurface, parent: &WlSurface) -> bool { 56 | if !is_toplevel_equivalent(parent) { 57 | return false; 58 | } 59 | 60 | // Check that we're not making a parent loop. 61 | let mut next_parent = Some(parent.clone()); 62 | while let Some(parent) = next_parent.clone() { 63 | // Did we find a cycle? 64 | if *child == parent { 65 | return false; 66 | } 67 | 68 | compositor::with_states(&parent, |states| { 69 | if let Some(data) = states.data_map.get::() { 70 | // Get xdg-toplevel parent. 71 | let role = data.lock().unwrap(); 72 | next_parent = role.parent.clone(); 73 | } else { 74 | // Reached a surface we don't know how to get a parent of. 75 | next_parent = None; 76 | } 77 | }); 78 | } 79 | 80 | true 81 | } 82 | -------------------------------------------------------------------------------- /src/wayland/shell/xdg/handlers.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | PopupConfigure, PositionerState, ShellClient, ShellClientData, SurfaceCachedState, ToplevelConfigure, 3 | XdgPopupSurfaceRoleAttributes, XdgShellHandler, XdgShellState, XdgToplevelSurfaceRoleAttributes, 4 | }; 5 | 6 | mod wm_base; 7 | pub use wm_base::XdgWmBaseUserData; 8 | 9 | mod positioner; 10 | pub use positioner::XdgPositionerUserData; 11 | 12 | mod surface; 13 | pub(in crate::wayland::shell) use surface::make_popup_handle; 14 | pub(super) use surface::{get_parent, send_popup_configure, send_toplevel_configure}; 15 | pub use surface::{XdgShellSurfaceUserData, XdgSurfaceUserData}; 16 | -------------------------------------------------------------------------------- /src/wayland/shell/xdg/handlers/positioner.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use crate::{utils::Rectangle, utils::Serial}; 4 | 5 | use wayland_protocols::xdg::shell::server::{xdg_positioner, xdg_positioner::XdgPositioner}; 6 | 7 | use wayland_server::{DataInit, Dispatch, DisplayHandle, Resource, WEnum}; 8 | 9 | use super::{PositionerState, XdgShellHandler, XdgShellState}; 10 | 11 | /* 12 | * xdg_positioner 13 | */ 14 | 15 | /// User data for Xdg Positioner 16 | #[derive(Default, Debug)] 17 | pub struct XdgPositionerUserData { 18 | pub(crate) inner: Mutex, 19 | } 20 | 21 | impl Dispatch for XdgShellState 22 | where 23 | D: Dispatch, 24 | D: XdgShellHandler, 25 | D: 'static, 26 | { 27 | fn request( 28 | _state: &mut D, 29 | _client: &wayland_server::Client, 30 | positioner: &XdgPositioner, 31 | request: xdg_positioner::Request, 32 | data: &XdgPositionerUserData, 33 | _dh: &DisplayHandle, 34 | _data_init: &mut DataInit<'_, D>, 35 | ) { 36 | let mut state = data.inner.lock().unwrap(); 37 | match request { 38 | xdg_positioner::Request::SetSize { width, height } => { 39 | if width < 1 || height < 1 { 40 | positioner.post_error( 41 | xdg_positioner::Error::InvalidInput, 42 | "Invalid size for positioner.", 43 | ); 44 | } else { 45 | state.rect_size = (width, height).into(); 46 | } 47 | } 48 | xdg_positioner::Request::SetAnchorRect { x, y, width, height } => { 49 | if width < 1 || height < 1 { 50 | positioner.post_error( 51 | xdg_positioner::Error::InvalidInput, 52 | "Invalid size for positioner's anchor rectangle.", 53 | ); 54 | } else { 55 | state.anchor_rect = Rectangle::new((x, y).into(), (width, height).into()); 56 | } 57 | } 58 | xdg_positioner::Request::SetAnchor { anchor } => { 59 | if let WEnum::Value(anchor) = anchor { 60 | state.anchor_edges = anchor; 61 | } 62 | } 63 | xdg_positioner::Request::SetGravity { gravity } => { 64 | if let WEnum::Value(gravity) = gravity { 65 | state.gravity = gravity; 66 | } 67 | } 68 | xdg_positioner::Request::SetConstraintAdjustment { 69 | constraint_adjustment, 70 | } => { 71 | if let WEnum::Value(constraint_adjustment) = constraint_adjustment { 72 | state.constraint_adjustment = constraint_adjustment; 73 | } 74 | } 75 | xdg_positioner::Request::SetOffset { x, y } => { 76 | state.offset = (x, y).into(); 77 | } 78 | xdg_positioner::Request::SetReactive => { 79 | state.reactive = true; 80 | } 81 | xdg_positioner::Request::SetParentSize { 82 | parent_width, 83 | parent_height, 84 | } => { 85 | state.parent_size = Some((parent_width, parent_height).into()); 86 | } 87 | xdg_positioner::Request::SetParentConfigure { serial } => { 88 | state.parent_configure = Some(Serial::from(serial)); 89 | } 90 | xdg_positioner::Request::Destroy => { 91 | // handled by destructor 92 | } 93 | _ => unreachable!(), 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/wayland/single_pixel_buffer/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::wayland::buffer::BufferHandler; 2 | 3 | use super::{SinglePixelBufferState, SinglePixelBufferUserData}; 4 | use wayland_protocols::wp::single_pixel_buffer::v1::server::wp_single_pixel_buffer_manager_v1::{ 5 | self, WpSinglePixelBufferManagerV1, 6 | }; 7 | use wayland_server::{ 8 | protocol::wl_buffer::{self, WlBuffer}, 9 | DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, 10 | }; 11 | 12 | impl GlobalDispatch for SinglePixelBufferState 13 | where 14 | D: GlobalDispatch, 15 | D: Dispatch, 16 | D: 'static, 17 | { 18 | fn bind( 19 | _state: &mut D, 20 | _dh: &DisplayHandle, 21 | _client: &wayland_server::Client, 22 | resource: New, 23 | _global_data: &(), 24 | data_init: &mut DataInit<'_, D>, 25 | ) { 26 | data_init.init(resource, ()); 27 | } 28 | } 29 | 30 | impl Dispatch for SinglePixelBufferState 31 | where 32 | D: Dispatch, 33 | D: Dispatch, 34 | D: 'static, 35 | { 36 | fn request( 37 | _state: &mut D, 38 | _client: &wayland_server::Client, 39 | _manager: &WpSinglePixelBufferManagerV1, 40 | request: wp_single_pixel_buffer_manager_v1::Request, 41 | _data: &(), 42 | _dh: &DisplayHandle, 43 | data_init: &mut DataInit<'_, D>, 44 | ) { 45 | match request { 46 | wp_single_pixel_buffer_manager_v1::Request::CreateU32RgbaBuffer { 47 | id: buffer, 48 | r, 49 | g, 50 | b, 51 | a, 52 | } => { 53 | data_init.init(buffer, SinglePixelBufferUserData { r, g, b, a }); 54 | } 55 | wp_single_pixel_buffer_manager_v1::Request::Destroy => {} 56 | _ => todo!(), 57 | } 58 | } 59 | } 60 | 61 | impl Dispatch for SinglePixelBufferState 62 | where 63 | D: Dispatch, 64 | D: BufferHandler, 65 | { 66 | fn request( 67 | data: &mut D, 68 | _client: &wayland_server::Client, 69 | buffer: &wl_buffer::WlBuffer, 70 | request: wl_buffer::Request, 71 | _udata: &SinglePixelBufferUserData, 72 | _dh: &DisplayHandle, 73 | _data_init: &mut DataInit<'_, D>, 74 | ) { 75 | match request { 76 | wl_buffer::Request::Destroy => { 77 | data.buffer_destroyed(buffer); 78 | } 79 | _ => unreachable!(), 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/wayland/tablet_manager/tablet.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | path::PathBuf, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use wayland_protocols::wp::tablet::zv2::server::{ 7 | zwp_tablet_seat_v2::ZwpTabletSeatV2, 8 | zwp_tablet_v2::{self, ZwpTabletV2}, 9 | }; 10 | use wayland_server::{ 11 | backend::ClientId, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, Resource, 12 | Weak, 13 | }; 14 | 15 | use crate::backend::input::Device; 16 | 17 | use super::TabletManagerState; 18 | 19 | /// Description of graphics tablet device 20 | #[derive(Debug, Clone, Hash, Eq, PartialEq)] 21 | pub struct TabletDescriptor { 22 | /// Tablet device name 23 | pub name: String, 24 | /// Tablet device USB (product,vendor) id 25 | pub usb_id: Option<(u32, u32)>, 26 | /// Path to the device 27 | pub syspath: Option, 28 | } 29 | 30 | impl From<&D> for TabletDescriptor { 31 | #[inline] 32 | fn from(device: &D) -> Self { 33 | TabletDescriptor { 34 | name: device.name(), 35 | syspath: device.syspath(), 36 | usb_id: device.usb_id(), 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug, Default)] 42 | struct Tablet { 43 | instances: Vec>, 44 | } 45 | 46 | /// Handle to a tablet device 47 | /// 48 | /// Tablet represents one graphics tablet device 49 | #[derive(Debug, Default, Clone)] 50 | pub struct TabletHandle { 51 | inner: Arc>, 52 | } 53 | 54 | impl TabletHandle { 55 | pub(super) fn new_instance( 56 | &mut self, 57 | client: &Client, 58 | dh: &DisplayHandle, 59 | seat: &ZwpTabletSeatV2, 60 | tablet: &TabletDescriptor, 61 | ) where 62 | D: Dispatch, 63 | D: 'static, 64 | { 65 | let wl_tablet = client 66 | .create_resource::(dh, seat.version(), TabletUserData { handle: self.clone() }) 67 | .unwrap(); 68 | 69 | seat.tablet_added(&wl_tablet); 70 | 71 | wl_tablet.name(tablet.name.clone()); 72 | 73 | if let Some((id_product, id_vendor)) = tablet.usb_id { 74 | wl_tablet.id(id_product, id_vendor); 75 | } 76 | 77 | if let Some(syspath) = tablet.syspath.as_ref().and_then(|p| p.to_str()) { 78 | wl_tablet.path(syspath.to_owned()); 79 | } 80 | 81 | wl_tablet.done(); 82 | 83 | self.inner.lock().unwrap().instances.push(wl_tablet.downgrade()); 84 | } 85 | 86 | pub(super) fn with_focused_tablet(&self, focus: &WlSurface, cb: F) 87 | where 88 | F: Fn(&ZwpTabletV2), 89 | { 90 | if let Some(instance) = self 91 | .inner 92 | .lock() 93 | .unwrap() 94 | .instances 95 | .iter() 96 | .find(|i| i.id().same_client_as(&focus.id())) 97 | .and_then(|t| t.upgrade().ok()) 98 | { 99 | cb(&instance); 100 | } 101 | } 102 | } 103 | 104 | /// User data of ZwpTabletV2 object 105 | #[derive(Debug)] 106 | pub struct TabletUserData { 107 | handle: TabletHandle, 108 | } 109 | 110 | impl Dispatch for TabletManagerState 111 | where 112 | D: Dispatch, 113 | D: 'static, 114 | { 115 | fn request( 116 | _state: &mut D, 117 | _client: &Client, 118 | _tablet: &ZwpTabletV2, 119 | _request: zwp_tablet_v2::Request, 120 | _data: &TabletUserData, 121 | _dh: &DisplayHandle, 122 | _data_init: &mut DataInit<'_, D>, 123 | ) { 124 | } 125 | 126 | fn destroyed(_state: &mut D, _client: ClientId, tablet: &ZwpTabletV2, data: &TabletUserData) { 127 | data.handle 128 | .inner 129 | .lock() 130 | .unwrap() 131 | .instances 132 | .retain(|i| i.id() != Resource::id(tablet)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/wayland/xdg_system_bell.rs: -------------------------------------------------------------------------------- 1 | //! XDG System Bell 2 | //! 3 | //! This protocol enables clients to ring the system bell. 4 | //! 5 | //! In order to advertise system bell global call [`XdgSystemBellState::new`] and delegate 6 | //! events to it with [`delegate_xdg_system_bell`][crate::delegate_xdg_system_bell]. 7 | //! 8 | //! ``` 9 | //! use smithay::wayland::xdg_system_bell::{XdgSystemBellState, XdgSystemBellHandler}; 10 | //! use wayland_server::protocol::wl_surface::WlSurface; 11 | //! use smithay::delegate_xdg_system_bell; 12 | //! 13 | //! # struct State; 14 | //! # let mut display = wayland_server::Display::::new().unwrap(); 15 | //! 16 | //! XdgSystemBellState::new::( 17 | //! &display.handle(), 18 | //! ); 19 | //! 20 | //! // provide the necessary trait implementations 21 | //! impl XdgSystemBellHandler for State { 22 | //! fn ring(&mut self, surface: Option) { 23 | //! println!("Ring got called"); 24 | //! } 25 | //! } 26 | //! 27 | //! delegate_xdg_system_bell!(State); 28 | //! ``` 29 | 30 | use wayland_protocols::xdg::system_bell::v1::server::xdg_system_bell_v1::{self, XdgSystemBellV1}; 31 | use wayland_server::{ 32 | backend::GlobalId, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, 33 | GlobalDispatch, New, 34 | }; 35 | 36 | /// Handler for xdg ring request 37 | pub trait XdgSystemBellHandler: 38 | GlobalDispatch + Dispatch + 'static 39 | { 40 | /// Ring the system bell 41 | fn ring(&mut self, surface: Option); 42 | } 43 | 44 | /// State of the xdg system bell 45 | #[derive(Debug)] 46 | pub struct XdgSystemBellState { 47 | global_id: GlobalId, 48 | } 49 | 50 | impl XdgSystemBellState { 51 | /// Register new [XdgSystemBellV1] global 52 | pub fn new(display: &DisplayHandle) -> Self { 53 | let global_id = display.create_global::(1, ()); 54 | Self { global_id } 55 | } 56 | 57 | /// [XdgSystemBellV1] GlobalId getter 58 | pub fn global(&self) -> GlobalId { 59 | self.global_id.clone() 60 | } 61 | } 62 | 63 | impl GlobalDispatch for XdgSystemBellState { 64 | fn bind( 65 | _state: &mut D, 66 | _handle: &DisplayHandle, 67 | _client: &Client, 68 | resource: New, 69 | _global_data: &(), 70 | data_init: &mut DataInit<'_, D>, 71 | ) { 72 | data_init.init(resource, ()); 73 | } 74 | } 75 | 76 | impl Dispatch for XdgSystemBellState { 77 | fn request( 78 | state: &mut D, 79 | _client: &wayland_server::Client, 80 | _resource: &XdgSystemBellV1, 81 | request: xdg_system_bell_v1::Request, 82 | _data: &(), 83 | _dhandle: &DisplayHandle, 84 | _data_init: &mut DataInit<'_, D>, 85 | ) { 86 | match request { 87 | xdg_system_bell_v1::Request::Ring { surface } => { 88 | state.ring(surface); 89 | } 90 | xdg_system_bell_v1::Request::Destroy => {} 91 | _ => unreachable!(), 92 | } 93 | } 94 | } 95 | 96 | /// Macro to delegate implementation of the xdg system bell to [`XdgSystemBellState`]. 97 | /// 98 | /// You must also implement [`XdgSystemBellHandler`] to use this. 99 | #[macro_export] 100 | macro_rules! delegate_xdg_system_bell { 101 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { 102 | $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ 103 | $crate::reexports::wayland_protocols::xdg::system_bell::v1::server::xdg_system_bell_v1::XdgSystemBellV1: () 104 | ] => $crate::wayland::xdg_system_bell::XdgSystemBellState); 105 | 106 | $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ 107 | $crate::reexports::wayland_protocols::xdg::system_bell::v1::server::xdg_system_bell_v1::XdgSystemBellV1: () 108 | ] => $crate::wayland::xdg_system_bell::XdgSystemBellState); 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /src/xwayland/mod.rs: -------------------------------------------------------------------------------- 1 | //! XWayland utilities 2 | //! 3 | //! This module contains helpers to manage XWayland from your compositor, in order 4 | //! to support running X11 apps. 5 | //! 6 | //! The starting point is the [`XWayland`] struct, which represents the 7 | //! running XWayland instance. Dropping it will shutdown XWayland. 8 | //! 9 | //! You need to provide an implementation of a X11 Window Manager for XWayland to 10 | //! function properly. You'll need to treat XWayland (and all its X11 apps) as one 11 | //! special client, and play the role of an X11 Window Manager. 12 | //! 13 | //! Smithay does not provide any helper for doing that yet, but it is planned. 14 | mod x11_sockets; 15 | mod xserver; 16 | pub mod xwm; 17 | 18 | pub use self::xserver::{XWayland, XWaylandClientData, XWaylandEvent}; 19 | pub use self::xwm::{X11Surface, X11Wm, XwmHandler}; 20 | -------------------------------------------------------------------------------- /test_clients/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_clients" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | [dependencies] 8 | smithay-client-toolkit = "0.19.2" 9 | tracing = { version = "0.1.37" } 10 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 11 | -------------------------------------------------------------------------------- /test_clients/src/lib.rs: -------------------------------------------------------------------------------- 1 | use smithay_client_toolkit::reexports::{ 2 | calloop, 3 | calloop_wayland_source::WaylandSource, 4 | client::{self as wayland_client, globals::GlobalList}, 5 | }; 6 | 7 | use calloop::EventLoop; 8 | use wayland_client::{ 9 | globals::registry_queue_init, globals::GlobalListContents, protocol::wl_registry::WlRegistry, Connection, 10 | Dispatch, QueueHandle, 11 | }; 12 | 13 | pub fn init_logging() { 14 | if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { 15 | tracing_subscriber::fmt() 16 | .compact() 17 | .with_env_filter(env_filter) 18 | .init(); 19 | } else { 20 | tracing_subscriber::fmt().compact().init(); 21 | } 22 | } 23 | 24 | pub fn init_connection() -> (EventLoop<'static, APP>, GlobalList, QueueHandle) 25 | where 26 | APP: Dispatch + 'static, 27 | { 28 | let conn = Connection::connect_to_env().unwrap(); 29 | 30 | let (globals, event_queue) = registry_queue_init(&conn).unwrap(); 31 | let qh = event_queue.handle(); 32 | let event_loop: EventLoop = EventLoop::try_new().unwrap(); 33 | let loop_handle = event_loop.handle(); 34 | WaylandSource::new(conn.clone(), event_queue) 35 | .insert(loop_handle) 36 | .unwrap(); 37 | 38 | (event_loop, globals, qh) 39 | } 40 | -------------------------------------------------------------------------------- /test_gbm_bo_create_with_modifiers2.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void test() { 4 | gbm_bo_create_with_modifiers2(NULL, 0, 0, 0, NULL, 0, 0); 5 | } -------------------------------------------------------------------------------- /test_gbm_bo_get_fd_for_plane.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void test() { 4 | gbm_bo_get_fd_for_plane(NULL, 0); 5 | } -------------------------------------------------------------------------------- /wlcs_anvil/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wlcs_anvil" 3 | version = "0.0.1" 4 | authors = ["Victor Berger ", "Drakulix (Victoria Brekenfeld)"] 5 | license = "MIT" 6 | publish = false 7 | edition = "2018" 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | smithay = { path = "..", default-features=false, features=["wayland_frontend", "backend_egl", "use_system_lib", "renderer_test"] } 14 | anvil = { path = "../anvil", default-features=false } 15 | wayland-sys = { version = "0.31.1", features = ["client", "server"] } 16 | wlcs = "0.1" 17 | libc = "0.2" 18 | memoffset = "0.9" 19 | cgmath = "0.18" 20 | --------------------------------------------------------------------------------