├── .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 | [](https://crates.io/crates/smithay)
6 | [](https://docs.rs/smithay)
7 | [](https://github.com/Smithay/smithay/actions)
8 | [](https://matrix.to/#/#smithay:matrix.org)
9 | 
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